From 35af3fd4ad059fbecf49d85b9c0d3524e941c92c Mon Sep 17 00:00:00 2001 From: mjkhan21 Date: Thu, 25 Apr 2024 17:13:48 +0900 Subject: [PATCH] =?UTF-8?q?DownloadView,=20XLSView,=20XLSWriter=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cokr/xit/base/file/web/DownloadView.java | 183 --- .../java/cokr/xit/base/file/web/XLSView.java | 49 - .../java/cokr/xit/base/file/xls/JXLS.java | 68 -- src/main/java/cokr/xit/base/file/xls/XLS.java | 125 -- .../cokr/xit/base/file/xls/XLSWriter.java | 1038 ----------------- 5 files changed, 1463 deletions(-) delete mode 100644 src/main/java/cokr/xit/base/file/web/DownloadView.java delete mode 100644 src/main/java/cokr/xit/base/file/web/XLSView.java delete mode 100644 src/main/java/cokr/xit/base/file/xls/JXLS.java delete mode 100644 src/main/java/cokr/xit/base/file/xls/XLS.java delete mode 100644 src/main/java/cokr/xit/base/file/xls/XLSWriter.java diff --git a/src/main/java/cokr/xit/base/file/web/DownloadView.java b/src/main/java/cokr/xit/base/file/web/DownloadView.java deleted file mode 100644 index e8518cb..0000000 --- a/src/main/java/cokr/xit/base/file/web/DownloadView.java +++ /dev/null @@ -1,183 +0,0 @@ -package cokr.xit.base.file.web; - -import java.io.File; -import java.io.FileInputStream; -import java.io.InputStream; -import java.net.URLConnection; -import java.net.URLEncoder; -import java.util.Map; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.springframework.web.servlet.view.AbstractView; - -import cokr.xit.base.file.FileInfo; -import cokr.xit.foundation.Assert; -import cokr.xit.foundation.Downloadable; - -/**파일의 다운로드를 처리하기 위한 {@link org.springframework.web.servlet.View} - *

DownloadView가 파일을 다운로드하기 위해서는 {@link org.springframework.web.servlet.ModelAndView}로 다음과 같은 속성의 값을 지정해야 한다. - *

- * @author mjkhan - */ -public class DownloadView extends AbstractView { - @Override - protected void renderMergedOutputModel(Map model, HttpServletRequest hreq, HttpServletResponse hresp) throws Exception { - if (model.containsKey("download")) { - render((Downloadable)model.get("download"), hresp); - return; - - } - InputStream input = getInputStream(model); - if (input == null) { - hresp.setStatus(HttpServletResponse.SC_NOT_FOUND); - hresp.sendError(HttpServletResponse.SC_NOT_FOUND); - return; - } - - try (InputStream stream = input) { - String filename = getFilename(model); - if (Assert.isEmpty(filename)) - throw new IllegalArgumentException("Unable to determine the filename"); - - String contentType = getContentType(model); - if (Assert.isEmpty(contentType)) { - contentType = Assert.ifEmpty(URLConnection.guessContentTypeFromName(filename), "application/octet-stream"); - } - String disposition = Assert.ifEmpty((String)model.get("disposition"), "attachment"); - - String charset = getCharset(model); - hresp.setCharacterEncoding(charset); - hresp.setContentType(contentType); - hresp.setHeader("Content-Disposition", disposition + "; filename=\"" + URLEncoder.encode(filename, charset) +"\""); - Number length = getLength(model); - if (length != null) - hresp.setContentLengthLong(length.longValue()); - - stream.transferTo(hresp.getOutputStream()); - } finally { - clear(model); - } - } - - private static String getCharset(Map model) { - Object obj = model.get("charset"); - return obj != null ? obj.toString() : "UTF-8"; - } - - private static InputStream getInputStream(Map model) throws Exception { - Object obj = model.get("file"); - if (obj == null) return null; - - if (obj instanceof InputStream) - return (InputStream)obj; - if (obj instanceof File) { - File file = (File)obj; - return new FileInputStream(file); - } - if (obj instanceof FileInfo) { - FileInfo fileInfo = (FileInfo)obj; - return fileInfo.getInputStream(); - } - - throw new IllegalArgumentException("'file' must be either a " + File.class.getName() + ", a " + InputStream.class.getName() + ", or a " + FileInfo.class.getName()); - } - - public static String getFilename(Map model) { - String filename = (String)model.get("filename"); - if (!Assert.isEmpty(filename)) return filename; - - Object obj = model.get("file"); - if (obj instanceof File) { - File file = (File)obj; - return file.getName(); - } - if (obj instanceof FileInfo) { - FileInfo fileInfo = (FileInfo)obj; - return fileInfo.getName(); - } - - obj = model.get("download"); - if (obj instanceof Downloadable) { - Downloadable downloadable = (Downloadable)obj; - return downloadable.getFilename(); - } - return null; - } - - private static String getContentType(Map model) throws Exception { - String contentType = (String)model.get("contentType"); - if (!Assert.isEmpty(contentType)) return contentType; - - Object obj = model.get("file"); - if (obj instanceof InputStream) { - InputStream input = (InputStream)obj; - return URLConnection.guessContentTypeFromStream(input); - } - if (obj instanceof File) { - File file = (File)obj; - return URLConnection.guessContentTypeFromName(file.getName()); - } - if (obj instanceof FileInfo) { - FileInfo fileInfo = (FileInfo)obj; - return fileInfo.getMimeType(); - } - return null; - } - - private static Number getLength(Map model) { - Number length = (Number)model.get("length"); - if (!Assert.isEmpty(length)) return length; - - Object obj = model.get("file"); - if (obj instanceof File) { - File file = (File)obj; - return file.length(); - } - if (obj instanceof FileInfo) { - FileInfo fileInfo = (FileInfo)obj; - return fileInfo.getSize(); - } - return null; - } - - private static void clear(Map model) { - Object obj = model.get("file"); - if (!(obj instanceof File)) return; - - Boolean delete = (Boolean)model.get("delete"); - if (Boolean.FALSE.equals(delete)) return; - - File file = (File)obj; - if (file.exists()) - file.delete(); - } - - protected void render(Downloadable downloadable, HttpServletResponse hresp) throws Exception { - if (downloadable == null) { - hresp.setStatus(HttpServletResponse.SC_NOT_FOUND); - hresp.sendError(HttpServletResponse.SC_NOT_FOUND); - return; - } - String filename = downloadable.getFilename(); - if (Assert.isEmpty(filename)) - throw new IllegalArgumentException("Unable to determine the filename"); - - String charset = downloadable.getCharset(); - hresp.setCharacterEncoding(charset); - hresp.setContentType(downloadable.getContentType()); - hresp.setHeader("Content-Disposition", downloadable.getDisposition() + "; filename=\"" + URLEncoder.encode(filename, charset) +"\""); - Long length = downloadable.getLength(); - if (length != null) - hresp.setContentLengthLong(length.longValue()); - - downloadable.write(hresp.getOutputStream()); - } -} \ No newline at end of file diff --git a/src/main/java/cokr/xit/base/file/web/XLSView.java b/src/main/java/cokr/xit/base/file/web/XLSView.java deleted file mode 100644 index 0208660..0000000 --- a/src/main/java/cokr/xit/base/file/web/XLSView.java +++ /dev/null @@ -1,49 +0,0 @@ -package cokr.xit.base.file.web; - -import java.net.URLEncoder; -import java.util.Map; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.springframework.web.servlet.view.AbstractView; - -import cokr.xit.base.file.xls.XLS; - -/**사용자가 요청하는 데이터를 엑셀파일로 제공하는 뷰. - * 사용 순서는 다음과 같다. - * 엑셀 템플릿파일 작성. 템플릿 작성법은 - * https://jxls.sourceforge.net - * https://ddoriya.tistory.com/entry/JXLS-POI-JAVA%EC%97%90%EC%84%9C-Excel-%EC%82%AC%EC%9A%A9%ED%95%98%EB%8A%94-%EA%B5%AC%ED%98%84-%EB%B0%A9%EB%B2%95-%EB%B0%8F-%EC%A2%85%EB%A5%98-%EB%B9%84%EA%B5%90 - * 스프링 파일에 'xlsView' Bean 설정 - * 콘트롤러(*Controller.java) - * ModelAndView의 이름을 'xlsView'로 지정 - * ModelAndView에 - * 엑셀 처리기 ('xls') 설정 - * DatasetControl (JSP / *.js) - * 파라미터 설정 - * download() 호출 - * - * @author mjkhan - */ -public class XLSView extends AbstractView { - @Override - protected void renderMergedOutputModel(Map model, HttpServletRequest hreq, HttpServletResponse hresp) throws Exception { - XLS xls = (XLS)model.get("xls"); - - String - charset = "UTF-8", - filename = xls.getFilename(); - - hresp.setCharacterEncoding(charset); - hresp.setContentType(XLS.MIME_TYPE); - hresp.setHeader("Content-Disposition", "attachment; filename=\"" + URLEncoder.encode(filename, charset) +"\""); - - xls.write(hresp.getOutputStream()); - } - - public static String getFilename(Map model) { - XLS xls = (XLS)model.get("xls"); - return xls != null ? xls.getFilename() : null; - } -} \ No newline at end of file diff --git a/src/main/java/cokr/xit/base/file/xls/JXLS.java b/src/main/java/cokr/xit/base/file/xls/JXLS.java deleted file mode 100644 index 57df6c7..0000000 --- a/src/main/java/cokr/xit/base/file/xls/JXLS.java +++ /dev/null @@ -1,68 +0,0 @@ -package cokr.xit.base.file.xls; - -import java.io.InputStream; -import java.io.OutputStream; -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.Map; - -import org.jxls.common.Context; -import org.jxls.util.JxlsHelper; - -public class JXLS extends XLS { - private Context ctx; - private Map data; - - @Override - public JXLS setTemplate(String template) { - super.setTemplate(template); - return this; - } - - @Override - public JXLS setFilename(String filename) { - super.setFilename(filename); - return this; - } - - /**엑셀 파일에 저장할 데이터들을 반환한다. - * @return 엑셀 파일에 저장할 데이터 - */ - public Map getData() { - return data != null ? data : Collections.emptyMap(); - } - - /**엑셀 파일에 저장할 데이터들을 설정한다. - * @param map 엑셀 파일에 저장할 데이터 - * @return 현재 XLS - */ - public JXLS setData(Map map) { - data = map; - return this; - } - - /**엑셀 파일에 저장할 데이터들을 설정한다. - * @param key 데이터 키 - * @param obj 데이터 - * @return 현재 XLS - */ - public JXLS setData(String key, Object obj) { - if (data == null) { - data = new LinkedHashMap<>(); - } - data.put(key, obj); - return this; - } - - @Override - public void write(OutputStream out) { - ctx = new Context(); - getData().forEach(ctx::putVar); - - try (InputStream input = loadTemplate();) { - JxlsHelper.getInstance().processTemplate(input, out, ctx); - } catch (Exception e) { - throw runtimeException(e); - } - } -} \ No newline at end of file diff --git a/src/main/java/cokr/xit/base/file/xls/XLS.java b/src/main/java/cokr/xit/base/file/xls/XLS.java deleted file mode 100644 index a11a80b..0000000 --- a/src/main/java/cokr/xit/base/file/xls/XLS.java +++ /dev/null @@ -1,125 +0,0 @@ -package cokr.xit.base.file.xls; - -import java.io.InputStream; -import java.io.OutputStream; -import java.text.SimpleDateFormat; -import java.util.Date; - -import org.springframework.core.io.ClassPathResource; - -import cokr.xit.foundation.AbstractComponent; - -/**엑셀 스프레드시트의 읽기나 쓰기를 돕는 유틸리티 - * @author mjkhan - */ -public abstract class XLS extends AbstractComponent { - /**엑셀 파일(*.xlsx)의 mime type */ - public static final String MIME_TYPE = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"; - - private String - template, - filename; - - /**템플릿 파일경로를 반환한다. - * @return 템플릿 파일경로 - */ - public String getTemplate() { - return template; - } - - protected InputStream loadTemplate() throws Exception { - if (isEmpty(template)) - return null; - return new ClassPathResource(template).getInputStream(); - } - - /**템플릿 파일경로를 설정한다. - * @param template 템플릿 파일경로 - * @return 현재 XLS - */ - public XLS setTemplate(String template) { - this.template = template; - return this; - } - - /**저장할 파일이름을 반환한다. - * 디폴트는 'yyyyMMdd-HHmmss.xlsx' - * @return 저장할 파일이름 - */ - public String getFilename() { - return ifEmpty(filename, () -> new SimpleDateFormat("yyyyMMdd-HHmmss").format(new Date()) + ".xlsx"); - } - - /**저장할 파일이름을 설정한다. - * 디폴트는 'yyyyMMdd-HHmmss.xlsx' - * @param filename 저장할 파일이름 - * @return 현재 XLS - */ - public XLS setFilename(String filename) { - this.filename = filename; - return this; - } - - /**설정된 데이터를 엑셀 파일 포맷으로 out에 저장한다. - * @param out OutputStream - */ - public abstract void write(OutputStream out); - - /**yyyyMMdd, 또는 yyMMdd 포맷의 문자열을 yyyy-MM-dd 포맷으로 변환하여 반환한다. - * @param obj yyyyMMdd, 또는 yyMMdd 포맷의 문자열 - * @return yyyy-MM-dd 포맷 문자열 - */ - public final String str2date(Object obj) { - if (isEmpty(obj)) return ""; - - String str = obj.toString(); - int length = str.length(), - pos = length - 2; - - String - day = str.substring(pos), - month = str.substring(pos = pos - 2, pos + 2), - year = str.substring(0, pos); - return String.format("%s-%s-%s", year, month, day); - } - - /**HHmmss 포맷의 문자열을 HH:mm:ss 포맷으로 변환하여 반환한다. - * @param obj HHmmss 포맷의 문자열 - * @return HH:mm:ss 포맷 문자열 - */ - public final String str2time(Object obj) { - if (isEmpty(obj)) return ""; - - String str = obj.toString(); - int length = str.length(), - pos = length - 2; - - String - ss = str.substring(pos), - mm = str.substring(pos = pos - 2, pos + 2), - hh = str.substring(0, pos); - return String.format("%s:%s:%s", hh, mm, ss); - } - - /**yyyyMMddHHmmss, 또는 yyMMddHHmmss 포맷의 문자열을 yyyy-MM-dd HH:mm:ss 포맷으로 변환하여 반환한다. - * @param obj yyyyMMddHHmmss, 또는 yyMMddHHmmss 포맷의 문자열 - * @return yyyy-MM-dd HH:mm:ss 포맷 문자열 - */ - public final String str2datetime(Object obj) { - if (isEmpty(obj)) return ""; - - String str = obj.toString(); - int length = str.length(), - pos = length - 6; - - try { - String - date = str.substring(0, pos), - time = str.substring(pos); - - return str2date(date) + " " + str2time(time); - } catch (Exception e) { - return str; - } - } -} \ No newline at end of file diff --git a/src/main/java/cokr/xit/base/file/xls/XLSWriter.java b/src/main/java/cokr/xit/base/file/xls/XLSWriter.java deleted file mode 100644 index ba9b0c2..0000000 --- a/src/main/java/cokr/xit/base/file/xls/XLSWriter.java +++ /dev/null @@ -1,1038 +0,0 @@ -package cokr.xit.base.file.xls; - -import java.io.FileInputStream; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Date; -import java.util.List; -import java.util.Map; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.function.Supplier; -import java.util.stream.Stream; - -import org.apache.poi.ooxml.POIXMLDocumentPart.RelationPart; -import org.apache.poi.ss.usermodel.BorderStyle; -import org.apache.poi.ss.usermodel.CellStyle; -import org.apache.poi.ss.usermodel.ClientAnchor; -import org.apache.poi.ss.usermodel.CreationHelper; -import org.apache.poi.ss.usermodel.FillPatternType; -import org.apache.poi.ss.usermodel.Font; -import org.apache.poi.ss.usermodel.HorizontalAlignment; -import org.apache.poi.ss.usermodel.VerticalAlignment; -import org.apache.poi.ss.util.CellRangeAddress; -import org.apache.poi.xssf.streaming.SXSSFCell; -import org.apache.poi.xssf.streaming.SXSSFDrawing; -import org.apache.poi.xssf.streaming.SXSSFRow; -import org.apache.poi.xssf.streaming.SXSSFSheet; -import org.apache.poi.xssf.streaming.SXSSFWorkbook; -import org.apache.poi.xssf.usermodel.XSSFComment; -import org.apache.poi.xssf.usermodel.XSSFPictureData; -import org.apache.poi.xssf.usermodel.XSSFRelation; -import org.apache.poi.xssf.usermodel.XSSFVMLDrawing; -import org.apache.poi.xssf.usermodel.XSSFWorkbook; -import org.openxmlformats.schemas.officeDocument.x2006.sharedTypes.STTrueFalse; -import org.springframework.util.FileCopyUtils; - -import com.fasterxml.jackson.core.type.TypeReference; -import com.microsoft.schemas.vml.CTFill; -import com.microsoft.schemas.vml.CTShape; -import com.microsoft.schemas.vml.STFillType; - -/**대용량 데이터를 엑셀 파일로 저장하는 유틸리티. - * XLSWriter는 엑셀 템플릿 파일을 사용하도록 할 수 있다. - *
 XLSWriter xlsx = new XLSWriter().setTemplate("...");
- * XLSWriter가 데이터를 저장한 엑셀 파일을 다운로드할 때 사용할 이름을 지정할 수 있다. - *
 XLSWriter xlsx = new XLSWriter()
- *     .setTemplate("...")
- *     .setFilename("...");
- * XLSWriter가 데이터를 엑셀로 저장하려면 워크시트와 작업셀을 지정해야 한다. - *
 xlsx.worksheet(0) //워크시트 설정
- *     .row(0)       //첫번째 행
- *     .col(0);      //첫번째 열의 셀을 작업셀로 설정
- * 엑셀로 저장할 데이터를 준비한다. - *
 List<DataObject> dataset = ...;
- * XLSWriter에 데이터를 설정한다. - *
 xlsx.values(dataset);
- * 위 코드는 dataset의 모든 필드값들을 설정한다. - * dataset의 특정 필드의 값들을 원하는 순서대로 설정하려면 해당 필드들의 이름을 순서대로 나열한다. - *
 xlsx.values(dataset, "RCPT_ID", "RCPT_DT", "PAYER_NM", "RCPT_AMT");
- * dataset의 특정 필드의 값들에 스타일을 적용하려면 {@link Formatter}을 표시한다. - *
 CellStyle
- *     datetime = xlsx.yyyy_mm_dd_hh_mm_ss(),
- *     numeric =  xlsx.n_nn0();
- * xlsx.values(dataset, "RCPT_ID", xlsx.style("RCPT_DT", datetime), "PAYER_NM", xlsx.style("RCPT_AMT", numeric));
- * @author mjkhan - */ -public class XLSWriter extends XLS { - private int rowAccessWindowSize = 128; - private SXSSFWorkbook workbook; - private SXSSFSheet worksheet; - private SXSSFRow row; - private SXSSFCell cell; - - @Override - public XLSWriter setTemplate(String template) { - super.setTemplate(template); - return this; - } - - @Override - public XLSWriter setFilename(String filename) { - super.setFilename(filename); - return this; - } - - @Override - public void write(OutputStream out) { - try { - workbook.write(out); - workbook.dispose(); - } catch (Exception e) { - throw runtimeException(e); - } finally { - clear(); - } - } - - public SXSSFWorkbook workbook() { - if (workbook != null) - return workbook; - - try { - InputStream template = loadTemplate(); - if (template != null) { - XSSFWorkbook xssf = new XSSFWorkbook(template); - workbook = new SXSSFWorkbook(xssf, rowAccessWindowSize); - } else { - workbook = new SXSSFWorkbook(rowAccessWindowSize); - } - workbook.setCompressTempFiles(true); - return workbook; - } catch (Exception e) { - throw runtimeException(e); - } - } - - /**지정한 인덱스의 워크시트를 반환한다. - * 워크시트가 없으면 해당 인덱스로 워크시트를 생성 후 반환한다. - * @param index 워크시트 인덱스(0부터 시작) - * @return 워크시트 - */ - public XLSWriter worksheet(int index) { - try { - worksheet = workbook().getSheetAt(index); - } catch (Exception e) { - worksheet = workbook().createSheet(); - } - worksheet.setRandomAccessWindowSize(rowAccessWindowSize); - return this; - } - - /**지정한 이름의 워크시트를 반환한다. - * 워크시트가 없으면 해당 이름의 워크시트를 생성 후 반환한다. - * @param name 워크시트 이름 - * @return 워크시트 - */ - public XLSWriter worksheet(String name) { - try { - SXSSFWorkbook wb = workbook(); - worksheet = ifEmpty(wb.getSheet(name), () -> wb.createSheet(name)); - worksheet.setRandomAccessWindowSize(rowAccessWindowSize); - return this; - } catch (Exception e) { - throw runtimeException(e); - } - } - - /**지정한 인덱스의 행(row)을 작업행으로 설정한다. - * 해당 행이 없으면 생성 후 작업행으로 설정한다. - * @param index 행 인덱스(0부터 시작) - * @return 현재 XLSWriter - */ - public XLSWriter row(int index) { - row = ifEmpty(worksheet.getRow(index), () -> worksheet.createRow(index)); - return this; - } - - /**현재 작업행의 지정한 인덱스의 열(column)을 작업셀로 설정한다. - * 해당 셀이 없으면 생성 후 작업셀로 설정한다. - * @param index 열 인덱스(0부터 시작) - * @return 현재 XLSWriter - */ - public XLSWriter col(int index) { - cell = ifEmpty(row.getCell(index), () -> row.createCell(index)); - return this; - } - - /**지정한 행과 열 인덱스의 셀을 작업셀로 설정한다. - * @param row 행 인덱스 - * @param col 열 인덱스 - * @return 현재 XLSWriter - */ - public XLSWriter cell(int row, int col) { - return row(row).col(col); - } - - /**지정한 범위의 셀들을 병합한다. - * @param firstRow 시작행 인덱스 - * @param lastRow 끝행 인덱스 - * @param firstCol 시작열 인덱스 - * @param lastCol 끝열 인덱스 - * @return 현재 XLSWriter - */ - public XLSWriter merge(int firstRow, int lastRow, int firstCol, int lastCol) { - worksheet.addMergedRegion(new CellRangeAddress(firstRow, lastRow, firstCol, lastCol)); - return this; - } - - /**현재 행의 지정한 열의 셀들을 병합한다. - * @param firstCol 시작열 인덱스 - * @param lastCol 끝열 인덱스 - * @return 현재 XLSWriter - */ - public XLSWriter merge(int firstCol, int lastCol) { - int index = row.getRowNum(); - return merge(index, index, firstCol, lastCol); - } - - /**지정한 값을 현재 작업셀에 설정하고 스타일을 적용한다. - * @param val 설정값 - * @param style 적용할 스타일(CellStyle, Styler, 또는 KeyStyle) - * @return 현재 XLSWriter - */ - public XLSWriter value(Object val, Object style) { - if (!isEmpty(val)) { - if (val instanceof String) { - cell.setCellValue((String)val); - } else if (val instanceof Number) { - Number number = (Number)val; - cell.setCellValue(number.doubleValue()); - } else if (val instanceof Date) { - cell.setCellValue((Date)val); - } else if (val instanceof Boolean) { - Boolean b = (Boolean)val; - cell.setCellValue(b.booleanValue()); - } else - throw new IllegalArgumentException(val.getClass().getName() + ": " + val); - } - setCellStyle(style); - - return this; - } - - /**지정한 값을 현재 작업셀에 설정한다. - * @param val 설정값 - * @return 현재 XLSWriter - */ - public XLSWriter value(Object val) { - return value(val, null); - } - - /**지정한 값들을 현재 작업셀부터 행으로 순서대로 설정한다.
- * 값이 설정된 셀에 스타일을 적용하려면 해당값의 바로 뒤에 스타일을 지정한다.
- * 다음은 사용례다. - *
 XLSWriter xlsx = new XLSWriter()
-	 *     .setTemplate("...") // 템플릿 사용 시 지정
-	 *     .setFilename("...") // 다운로드 시 파일이름 지정
-	 *     .worksheet(0)       // 워크시트 지정
-	 *     .cell(0, 0)           // 작업셀 지정
-	 *     .rowValues(List.of("하나", 10000, new Date()));
- * 두번째 값에는 숫자포맷을, 세번째 값에는 날짜포맷을 지정하려면 다음과 같이 한다. - *
 CellStyle numeric = xlsx.n_nn0(); //{@link #cellStyle(Styler)} 참고
-	 * CellStyle datetime = xlsx.yyyy_mm_dd_hh_mm_ss();
-	 * ...
-	 * xlsx.rowValues(List.of("하나", 10000, numeric, new Date(), datetime));
- * @param vals 설정값(스타일 포함) - * @return 현재 XLSWriter - */ - public XLSWriter rowValues(Iterable vals) { - if (!isEmpty(vals)) { - int start = cell.getColumnIndex(), - x = start; - for (Object val: vals) { - if (val instanceof Styler) { - setCellStyle(val); - } else if (val instanceof CellStyle) { - setCellStyle(val); - } else if (val instanceof Formatter) { - setCellStyle(val); - } else if (val instanceof Runnable) { - ((Runnable)val).run(); - } else { - col(x).value(val); - ++x; - } - } - col(start); - } - return this; - } - - private void setCellStyle(Object obj) { - Styler styler = null; - CellStyle style = null; - - if (obj instanceof CellStyle) { - style = (CellStyle)obj; - } else if (obj instanceof Styler) { - styler = (Styler)obj; - } else if (obj instanceof Formatter) { - Formatter formatter = (Formatter)obj; - styler = formatter.styler; - style = formatter.style; - } - - if (styler != null) { - style = cellStyle(styler); - if (styler.width != null) { - if (styler.width < 256) - worksheet.setColumnWidth(cell.getColumnIndex(), styler.width * 256); - else - worksheet.autoSizeColumn(cell.getColumnIndex()); - } - if (styler.height != null) - row.setHeight(styler.height); - } - if (style != null) - cell.setCellStyle(style); - } - - private Iterable vals(Map map, Object[] keysAndStyles) { - if (isEmpty(map)) - return Collections.emptyList(); - - if (isEmpty(keysAndStyles)) - return map.values(); - - ArrayList vals = new ArrayList<>(); - for (Object obj: keysAndStyles) { - if (obj instanceof Formatter) { - Formatter formatter = (Formatter)obj; - Object val = null; - if (formatter.key != null) { - val = map.get(formatter.key); - } else if (formatter.convert != null){ - val = formatter.convert.apply(map); - } - vals.add(val); - if (formatter.onCell != null) - vals.add(formatter.onCell.apply(map)); - - Styler styler = formatter.styler; - CellStyle style = formatter.style; - if (styler != null) - style = cellStyle(styler); - if (style != null) - vals.add(style); - } else { - vals.add(map.get(obj)); - } - } - return vals; - } - - /**지정한 map의 값들을 현재 작업셀부터 행으로 설정한다.
- * 설정할 값과 순서를 정하려면 해당 값들의 키를 지정한다.
- * 값이 설정된 셀에 스타일을 적용하려면 {@link Formatter}로 키와 스타일을 지정한다.
- * 다음은 map의 모든 값들을 설정하는 예다. - *
 DataObject rec = new DataObject().set("NO", 1).set("PAYER", "홍길동").set("RCPT_AMT", 10000).set("RCPT_DT", new Date());
-	 * XLSWriter xlsx = new XLSWriter()
-	 *     .setTemplate("...") // 템플릿 사용 시 지정
-	 *     .setFilename("...") // 다운로드 시 파일이름 지정
-	 *     .worksheet(0)       // 워크시트 지정
-	 *     .cell(0, 0)           // 작업셀 지정
-	 *     .rowValues(rec);
- * 설정할 값들을 지정하려면 다음과 같이 한다. - *
 xlsx.rowValues(rec, "PAYER", "RCPT_AMT", "RCPT_DT");
- * RCPT_AMT, RCPT_DT에 숫자포맷과 날짜포맷을 적용하려면 다음과 같이 한다. - *
 CellStyle numeric = xlsx.n_nn0(); //{@link #cellStyle(Styler)} 참고
-	 * CellStyle datetime = xlsx.yyyy_mm_dd_hh_mm_ss();
-	 * ...
-	 * xlsx.rowValues(rec, "PAYER", xlsx.keyStyle("RCPT_AMT", numeric), xlsx.keyStyle("RCPT_DT", datetime));
- * @param map 맵 - * @param keysAndStyles 셀에 설정할 값들의 키, 또는 {@link Formatter 키와 스타일} - * @return 현재 XLSWriter - */ - public XLSWriter rowValues(Map map, Object... keysAndStyles) { - return rowValues(vals(map, keysAndStyles)); - } - - /**지정한 데이터셋의 값들을 현재 작업셀부터 행과 열로 설정한다. - * 설정할 값과 순서를 정하려면 해당 값들의 키를 지정한다.
- * 값이 설정된 셀에 스타일을 적용하려면 {@link Formatter}로 키와 스타일을 지정한다.
- * 다음은 데이터셋의 모든 값들을 설정하는 예다. - *
 List<DataObject> dataset = ...;
-	 * XLSWriter xlsx = new XLSWriter()
-	 *     .setTemplate("...") // 템플릿 사용 시 지정
-	 *     .setFilename("...") // 다운로드 시 파일이름 지정
-	 *     .worksheet(0)       // 워크시트 지정
-	 *     .cell(0, 0)           // 작업셀 지정
-	 *     .rowValues(dataset);
- * 설정할 값들을 지정하려면 다음과 같이 한다. - *
 xlsx.rowValues(dataset, "PAYER", "RCPT_AMT", "RCPT_DT");
- * RCPT_AMT, RCPT_DT에 숫자포맷과 날짜포맷을 적용하려면 다음과 같이 한다. - *
 CellStyle numeric = xlsx.n_nn0(); //{@link #cellStyle(Styler)} 참고
-	 * CellStyle datetime = xlsx.yyyy_mm_dd_hh_mm_ss();
-	 * ...
-	 * xlsx.rowValues(dataset, "PAYER", xlsx.style("RCPT_AMT", numeric), xlsx.style("RCPT_DT", datetime));
- * @param dataset 데이터셋 - * @param keysAndStyles 셀에 설정할 값들의 키, 또는 {@link Formatter 키와 스타일} - * @return 현재 XLSWriter - */ - public XLSWriter values(Iterable> dataset, Object... keysAndStyles) { - if (!isEmpty(dataset)) { - int c = cell.getColumnIndex(), - r = cell.getRowIndex(); - Object[] kas = Stream.of(keysAndStyles) - .map(obj -> !(obj instanceof Styler) ? obj : cellStyle((Styler)obj)) - .toList() - .toArray(); - - for (Map rec: dataset) { - cell(r, c); - rowValues(rec, kas); - ++r; - } - } - return this; - } - - /**스타일 정보에서 셀스타일을 생성하여 반환한다. - * @param styler 스타일 정보 - * @return 셀스타일 - */ - public CellStyle cellStyle(Styler styler) { - if (styler == null) - return null; - - CellStyle cellStyle = workbook.createCellStyle(); - styler.set(cellStyle); - if (styler.dataFormat != null) { - cellStyle.setDataFormat(workbook.createDataFormat().getFormat(styler.dataFormat)); - } - return cellStyle; - } - - /**지정한 키와 셀스타일을 연결한 KeyStyle을 반환한다. - * @param key 키 - * @param style 셀스타일 - * @return KeyStyle - */ - public Formatter formatter(Object key) { - return new Formatter().key(key); - } - - /**데이터 포맷 function을 설정한다. - * @param func 데이터 포맷 function - * @return Formatter - */ - public Formatter format(Function, Object> func) { - return formatter(null).format(func); - } - - /**지정한 데이터 키의 스타일(CellStyle, 또는 Styler)를 설정한다. - * @param key 데이터 키 - * @param style 스타일(CellStyle, 또는 Styler) - * @return Formatter - */ - public Formatter style(Object key, Object style) { - Formatter fmt = formatter(key); - if (style instanceof CellStyle) - fmt.style((CellStyle)style); - else if (style instanceof Styler) - fmt.styler((Styler)style); - return fmt; - } - - /**현재 XLSWriter의 내부상태를 초기화하여 반환한다. - * @return 현재 XLSWriter - */ - public XLSWriter clear() { - cell = null; - row = null; - worksheet = null; - workbook = null; - rowAccessWindowSize = 128; - return this; - } - - /**숫자 스타일(오른쪽 정렬, 천단위 구분)을 반환한다. - * @return 숫자 스타일(오른쪽 정렬, 천단위 구분) - */ - public CellStyle n_nn0() { - return cellStyle(new Styler().alignment(HorizontalAlignment.RIGHT).dataFormat("#,##0")); - } - - /**숫자 스타일(오른쪽 정렬, 천단위 구분, 소수점 2자리 표기)을 반환한다. - * @return 숫자 스타일(오른쪽 정렬, 천단위 구분, 소수점 2자리 표기) - */ - public CellStyle n_nn0_2() { - return cellStyle(new Styler().dataFormat("#,###.00").merge(Styler.RIGHT)); - } - - /**날짜 포맷(yyyy-MM-dd HH:mm:ss)의 셀스타일을 반환한다. - * @return 날짜 포맷(yyyy-MM-dd HH:mm:ss)의 셀스타일 - */ - public CellStyle yyyy_mm_dd_hh_mm_ss() { - return cellStyle(new Styler().dataFormat("yyyy-MM-dd HH:mm:ss").merge(Styler.CENTER)); - } - - /**날짜 포맷(yyyy-MM-dd)의 셀스타일을 반환한다. - * @return 날짜 포맷(yyyy-MM-dd)의 셀스타일 - */ - public CellStyle yyyy_mm_dd() { - return cellStyle(new Styler().dataFormat("yyyy-MM-dd").merge(Styler.CENTER)); - } - - /**시간 포맷(HH:mm:ss)의 셀스타일을 반환한다. - * @return 시간 포맷(HH:mm:ss)의 셀스타일 - */ - public CellStyle hh_mm_ss() { - return cellStyle(new Styler().dataFormat("HH:mm:ss").merge(Styler.CENTER)); - } - - /**셀스타일 정보. {@link #set(CellStyle) CellStyle을 설정}하는데 사용. - * @author mjkhan - */ - public static class Styler { - /** 빈 스타일 */ - public static final Styler NONE = new Styler().seal(); - /** 좌측 정렬 */ - public static final Styler LEFT = new Styler().alignment(HorizontalAlignment.LEFT).seal(); - /** 가운데 정렬 */ - public static final Styler CENTER = new Styler().alignment(HorizontalAlignment.CENTER).seal(); - /** 우측 정렬 */ - public static final Styler RIGHT = new Styler().alignment(HorizontalAlignment.RIGHT).seal(); - - private boolean sealed; - - private Integer width; - private Short height; - - private HorizontalAlignment horizontalAlignment; - private VerticalAlignment verticalAlignment; - - private Border - borderTop, - borderRight, - borderBottom, - borderLeft; - - private Short foregroundColor; - - private Font font; - private String dataFormat; - - /**열의 폭을 설정한다. - * @param chars 열의 폭. 한번에 보여지는 문자수 - * @return 현재 Styler - */ - public Styler width(int chars) { - this.width = chars; - return this; - } - - /**행의 높이를 설정한다. - * @param height 행의 높이 - * @return 현재 Styler - */ - public Styler height(short height) { - this.height = height; - return this; - } - - /**가로정렬을 설정한다. - * @param alignment 가로정렬 - * @return 현재 Styler - */ - public Styler alignment(HorizontalAlignment alignment) { - ensureNotSealed(); - horizontalAlignment = alignment; - return this; - } - - /**세로정렬을 설정한다. - * @param alignment 세로정렬 - * @return 현재 Styler - */ - public Styler alignment(VerticalAlignment alignment) { - ensureNotSealed(); - verticalAlignment = alignment; - return this; - } - - /**윗쪽 테두리 스타일을 설정한다. - * @param border 테두리 스타일 정보 - * @return 현재 Styler - */ - public Styler borderTop(Border border) { - ensureNotSealed(); - borderTop = border; - return this; - } - - /**오른쪽 테두리 스타일을 설정한다. - * @param border 테두리 스타일 정보 - * @return 현재 Styler - */ - public Styler borderRight(Border border) { - ensureNotSealed(); - borderRight = border; - return this; - } - - /**아래쪽 테두리 스타일을 설정한다. - * @param border 테두리 스타일 정보 - * @return 현재 Styler - */ - public Styler borderBottom(Border border) { - ensureNotSealed(); - borderBottom = border; - return this; - } - - /**왼쪽 테두리 스타일을 설정한다. - * @param border 테두리 스타일 정보 - * @return 현재 Styler - */ - public Styler borderLeft(Border border) { - ensureNotSealed(); - borderLeft = border; - return this; - } - - /**배경색을 설정한다. - * @param color - * @return 현재 Styler - */ - public Styler foregroundColor(short color) { - ensureNotSealed(); - foregroundColor = color; - return this; - } - - /**폰트를 설정한다. - * @param font 폰트 - * @return 현재 Styler - */ - public Styler font(Font font) { - ensureNotSealed(); - this.font = font; - return this; - } - - /**데이터 포맷을 설정한다. - * @param dataFormat 데이터 포맷 - * @return 현재 Styler - */ - public Styler dataFormat(String dataFormat) { - ensureNotSealed(); - this.dataFormat = dataFormat; - return this; - } - - /**셀스타일에 현재 스타일 정보를 설정한다. - * @param style 셀스타일 - */ - public void set(CellStyle style) { - if (style == null) return; - - if (horizontalAlignment != null) - style.setAlignment(horizontalAlignment); - if (verticalAlignment != null) - style.setVerticalAlignment(verticalAlignment); - - if (borderTop != null) { - if (borderTop.style != null) - style.setBorderTop(borderTop.style); - if (borderTop.color != null) - style.setTopBorderColor(borderTop.color); - } - if (borderRight != null) { - if (borderRight.style != null) - style.setBorderRight(borderRight.style); - if (borderRight.color != null) - style.setRightBorderColor(borderRight.color); - } - if (borderBottom != null) { - if (borderBottom.style != null) - style.setBorderBottom(borderBottom.style); - if (borderBottom.color != null) - style.setBottomBorderColor(borderBottom.color); - } - if (borderLeft != null) { - if (borderLeft.style != null) - style.setBorderLeft(borderLeft.style); - if (borderLeft.color != null) - style.setLeftBorderColor(borderLeft.color); - } - if (foregroundColor != null) { - style.setFillForegroundColor(foregroundColor); - style.setFillPattern(FillPatternType.SOLID_FOREGROUND); - } - - if (font != null) - style.setFont(font); - } - - public Styler configure(Consumer configurer) { - ensureNotSealed(); - if (configurer != null) - configurer.accept(this); - return this; - } - - public Styler merge(Styler other) { - Styler copy = new Styler(); - - copy.width = this.width; - copy.height = this.height; - - copy.horizontalAlignment = this.horizontalAlignment; - copy.verticalAlignment = this.verticalAlignment; - - copy.borderTop = this.borderTop; - copy.borderRight = this.borderRight; - copy.borderBottom = this.borderBottom; - copy.borderLeft = this.borderLeft; - - copy.foregroundColor = this.foregroundColor; - copy.font = this.font; - copy.dataFormat = this.dataFormat; - - if (other.width != null) - copy.width = other.width; - if (other.height != null) - copy.height = other.height; - - if (other.horizontalAlignment != null) - copy.horizontalAlignment = other.horizontalAlignment; - if (other.verticalAlignment != null) - copy.verticalAlignment = other.verticalAlignment; - - if (other.borderTop != null) - copy.borderTop = other.borderTop; - if (other.borderRight != null) - copy.borderRight = other.borderRight; - if (other.borderBottom != null) - copy.borderBottom = other.borderBottom; - if (other.borderLeft != null) - copy.borderLeft = other.borderLeft; - - if (other.foregroundColor != null) - copy.foregroundColor = other.foregroundColor; - if (other.font != null) - copy.font = other.font; - if (other.dataFormat != null) - copy.dataFormat = other.dataFormat; - - return copy; - } - - private Styler seal() { - sealed = true; - return this; - } - - private void ensureNotSealed() { - if (sealed) - throw new RuntimeException("The Style is sealed and unmodifieable"); - } - } - - /**테두리 스타일 정보 - * @author mjkhan - */ - public static class Border { - private BorderStyle style; - private Short color; - - /**테두리 스타일 정보를 설정한다. - * @param style 테두리 스타일 정보 - * @return 현재 테두리 스타일 정보 - */ - public Border style(BorderStyle style) { - this.style = style; - return this; - } - - /**테두리 색깔을 설정한다. - * @param color 테두리 색깔 - * @return 현재 테두리 스타일 정보 - */ - public Border color(short color) { - this.color = color; - return this; - } - } - - /**데이터 포맷, 셀스타일을 특정키와 연결하는 정보 - * @author mjkhan - */ - public static class Formatter { - private Object key; - - private Function, Object> convert; - - private CellStyle style; - private Styler styler; - - private Function, Runnable> onCell; - - /**데이터 키를 설정한다. - * @param key 데이터 키 - * @return 현재 Formatter - */ - public Formatter key(Object key) { - this.key = key; - return this; - } - - /**데이터 포맷 function을 설정한다. - * @param func 데이터 포맷 설정 - * @return 현재 Formatter - */ - public Formatter format(Function, Object> func) { - convert = func; - return this; - } - - /**셀스타일을 설정한다. - * @param style 셀스타일 - * @return 현재 Formatter - */ - public Formatter style(CellStyle style) { - this.style = style; - return this; - } - - /**스타일러를 설정한다. - * @param styler 스타일러 - * @return 현재 Formatter - */ - public Formatter styler(Styler styler) { - this.styler = styler; - return this; - } - - public Formatter onCell(Function, Runnable> func) { - onCell = func; - return this; - } - } - - /**셀 값과 스타일 설정에 대한 정의 - * @author mjkhan - */ - public static class CellDef { - /**valueMap에서 CellDef의 필드이름 또는 스타일을 설정한다. - * @param defs CellDef 목록 - * @param valueMap 레이블별 필드이름 또는 스타일 - */ - public static void setValues(List defs, Map valueMap) { - defs.forEach(def -> { - Object val = valueMap.get(def.label); - if (val == null) - val = def.field; - if (isEmpty(val)) - throw new RuntimeException("Value or style not found for " + def.label); - - def.value = val; - }); - } - - /**CellDef 목록에서 셀헤더 이름과 스타일을 반환한다. - * @param defs CellDef 목록 - * @param factory 헤더 스타일을 제공하는 function - * @return 셀헤더 이름과 스타일 목록 - */ - public static List header(List defs, Supplier factory) { - ArrayList result = new ArrayList<>(); - - for (CellDef def: defs) { - result.add(def.label); - - Styler styler = factory.get(); - styler.width(def.width); - result.add(styler); - } - - return result; - } - - /**CellDef 목록에서 셀값의 필드이름 또는 스타일 목록을 반환한다. - * @param defs CellDef 목록 - * @return 셀값의 필드이름 또는 스타일 목록 - */ - public static Object[] values(List defs) { - return defs.stream().map(CellDef::getValue).toList().toArray(); - } - - public static TypeReference> listType() { - return new TypeReference>() {}; - } - - private String - label, - field; - private int width; - private Object value; - - /**레이블을 반환한다. - * @return 레이블 - */ - public String getLabel() { - return label; - } - - /**레이블을 설정한다. - * @param label 레이블 - * @return 현재 CellDef - */ - public CellDef setLabel(String label) { - this.label = label; - return this; - } - - /**필드이름을 반환한다. - * @return 필드이름 - */ - public String getField() { - return field; - } - - /**필드이름을 설정한다. - * @param field 필드이름 - * @return 현재 CellDef - */ - public CellDef setField(String field) { - this.field = field; - return this; - } - - /**넓이를 반환한다. - * @return 넓이 - * @return 현재 CellDef - */ - public int getWidth() { - return width; - } - - /**넓이를 설정한다. - * @param width 넓이 - * @return 현재 CellDef - */ - public CellDef setWidth(int width) { - this.width = width; - return this; - } - - /**필드이름, 또는 스타일을 반환한다. - * @return 필드이름, 또는 스타일 - */ - public Object getValue() { - return value; - } - - /**필드이름, 또는 스타일을 설정한다. - * @param value 필드이름, 또는 스타일 - * @return 현재 CellDef - */ - public CellDef setValue(Object value) { - this.value = value; - return this; - } - } - - public static class CommentSupport { - private XLSWriter writer; - private XSSFWorkbook workbook; - private CreationHelper factory; - private ClientAnchor anchor; - private Image image; - - public CommentSupport(XLSWriter writer) { - this.writer = writer; - workbook = this.writer.workbook.getXSSFWorkbook(); - factory = workbook.getCreationHelper(); - anchor = factory.createClientAnchor(); - image = new Image(this.writer); - } - - public XSSFComment create(String str) { - int row = writer.cell.getRowIndex(); - anchor.setRow1(row); - anchor.setRow2(row + 17); - int col = writer.cell.getColumnIndex(); - anchor.setCol1(col); - anchor.setCol2(col + 6); - - SXSSFDrawing drawing = writer.worksheet.createDrawingPatriarch(); - XSSFComment comment = (XSSFComment)drawing.createCellComment(anchor); - comment.setString(str); - writer.cell.setCellComment(comment); - return comment; - } - - public void setBackgroundImage(String title, String path) { - int picIndex = image.add(path); - XSSFPictureData pic = workbook.getAllPictures().get(picIndex); - XSSFVMLDrawing vml = writer.worksheet.getVMLDrawing(true); - RelationPart rp = vml.addRelation(null, XSSFRelation.IMAGES, pic); - - int row = writer.cell.getRowIndex(); - int col = writer.cell.getColumnIndex(); - CTShape shape = vml.findCommentShape(row, col); - CTFill fill = shape.getFillArray(0); - fill.setColor2(fill.getColor()); - fill.unsetColor(); - fill.setRelid(rp.getRelationship().getId()); - fill.setTitle(title); - fill.setRecolor(STTrueFalse.T); - fill.setRotate(STTrueFalse.T); - fill.setType(STFillType.FRAME); - } - - public void setImageComment(String path) { - create(""); - setBackgroundImage("", path); - } - - public Runnable getImageCommenter(String path) { - return () -> setImageComment(path); - } - } - - public static class Image { - private XLSWriter writer; - - public Image(XLSWriter writer) { - this.writer = writer; - } - - public int add(InputStream input, int imgType) { - try { - return writer.workbook.addPicture( - FileCopyUtils.copyToByteArray(input), - imgType - ); - } catch (Exception e) { - throw runtimeException(e); - } - } - - public int add(String path) { - String str = path.toLowerCase(); - int imgType = str.endsWith(".png") ? SXSSFWorkbook.PICTURE_TYPE_PNG : SXSSFWorkbook.PICTURE_TYPE_JPEG; - - try (FileInputStream input = new FileInputStream(path)) { - return add(input, imgType); - } catch (Exception e) { - throw runtimeException(e); - } - } - } -} \ No newline at end of file