diff --git a/README.md b/README.md index 1251ebd2..72cc0164 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ DEMO代码地址:[https://github.com/alibaba/easyexcel/blob/master/src/test/ja /** * 最简单的读 *

1. 创建excel对应的实体对象 参照{@link DemoData} - *

2. 由于默认异步读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoDataListener} + *

2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoDataListener} *

3. 直接读即可 */ @Test @@ -81,7 +81,7 @@ DEMO代码地址:[https://github.com/alibaba/easyexcel/blob/master/src/test/ja /** * 文件上传 *

1. 创建excel对应的实体对象 参照{@link UploadData} - *

2. 由于默认异步读取excel,所以需要创建excel一行一行的回调监听器,参照{@link UploadDataListener} + *

2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link UploadDataListener} *

3. 直接读即可 */ @PostMapping("upload") diff --git a/src/main/java/com/alibaba/excel/constant/BuiltinFormats.java b/src/main/java/com/alibaba/excel/constant/BuiltinFormats.java new file mode 100644 index 00000000..7b3c5bb8 --- /dev/null +++ b/src/main/java/com/alibaba/excel/constant/BuiltinFormats.java @@ -0,0 +1,204 @@ +package com.alibaba.excel.constant; + +import com.alibaba.excel.util.StringUtils; + +/** + * Excel's built-in format conversion.Currently only supports Chinese. + * + *

+ * If it is not Chinese, it is recommended to directly modify the builtinFormats, which will better support + * internationalization in the future. + * + *

+ * Specific correspondence please see: + * https://docs.microsoft.com/en-us/dotnet/api/documentformat.openxml.spreadsheet.numberingformat?view=openxml-2.8.1 + * + * @author Jiaju Zhuang + **/ +public class BuiltinFormats { + + public static String[] builtinFormats = { + // 0 + "General", + // 1 + "0", + // 2 + "0.00", + // 3 + "#,##0", + // 4 + "#,##0.00", + // 5 + "\"$\"#,##0_);(\"$\"#,##0)", + // 6 + "\"$\"#,##0_);[Red](\"$\"#,##0)", + // 7 + "\"$\"#,##0.00_);(\"$\"#,##0.00)", + // 8 + "\"$\"#,##0.00_);[Red](\"$\"#,##0.00)", + // 9 + "0%", + // 10 + "0.00%", + // 11 + "0.00E+00", + // 12 + "# ?/?", + // 13 + "# ??/??", + // 14 + "m/d/yy", + // 15 + "d-mmm-yy", + // 16 + "d-mmm", + // 17 + "mmm-yy", + // 18 + "h:mm AM/PM", + // 19 + "h:mm:ss AM/PM", + // 20 + "h:mm", + // 21 + "h:mm:ss", + // 22 + "m/d/yy h:mm", + // 23-26 No specific correspondence found in the official documentation. + // 23 + null, + // 24 + null, + // 25 + null, + // 26 + null, + // 27 + "yyyy\"5E74\"m\"6708\"", + // 28 + "m\"6708\"d\"65E5\"", + // 29 + "m\"6708\"d\"65E5\"", + // 30 + "m-d-yy", + // 31 + "yyyy\"5E74\"m\"6708\"d\"65E5\"", + // 32 + "h\"65F6\"mm\"5206\"", + // 33 + "h\"65F6\"mm\"5206\"ss\"79D2\"", + // 34 + "4E0A5348/4E0B5348h\"65F6\"mm\"5206\"", + // 35 + "4E0A5348/4E0B5348h\"65F6\"mm\"5206\"ss\"79D2\"", + // 36 + "yyyy\"5E74\"m\"6708\"", + // 37 + "#,##0_);(#,##0)", + // 38 + "#,##0_);[Red](#,##0)", + // 39 + "#,##0.00_);(#,##0.00)", + // 40 + "#,##0.00_);[Red](#,##0.00)", + // 41 + "_(* #,##0_);_(* (#,##0);_(* \"-\"_);_(@_)", + // 42 + "_(\"$\"* #,##0_);_(\"$\"* (#,##0);_(\"$\"* \"-\"_);_(@_)", + // 43 + "_(* #,##0.00_);_(* (#,##0.00);_(* \"-\"??_);_(@_)", + // 44 + "_(\"$\"* #,##0.00_);_(\"$\"* (#,##0.00);_(\"$\"* \"-\"??_);_(@_)", + // 45 + "mm:ss", + // 46 + "[h]:mm:ss", + // 47 + "mm:ss.0", + // 48 + "##0.0E+0", + // 49 + "@", + // 50 + "yyyy\"5E74\"m\"6708\"", + // 51 + "m\"6708\"d\"65E5\"", + // 52 + "yyyy\"5E74\"m\"6708\"", + // 53 + "m\"6708\"d\"65E5\"", + // 54 + "m\"6708\"d\"65E5\"", + // 55 + "4E0A5348/4E0B5348h\"65F6\"mm\"5206\"", + // 56 + "4E0A5348/4E0B5348h\"65F6\"mm\"5206\"ss\"79D2\"", + // 57 + "yyyy\"5E74\"m\"6708\"", + // 58 + "m\"6708\"d\"65E5\"", + // 59 + "t0", + // 60 + "t0.00", + // 61 + "t#,##0", + // 62 + "t#,##0.00", + // 63-66 No specific correspondence found in the official documentation. + // 63 + null, + // 64 + null, + // 65 + null, + // 66 + null, + // 67 + "t0%", + // 68 + "t0.00%", + // 69 + "t# ?/?", + // 70 + "t# ??/??", + // 71 + "0E27/0E14/0E1B0E1B0E1B0E1B", + // 72 + "0E27-0E140E140E14-0E1B0E1B", + // 73 + "0E27-0E140E140E14", + // 74 + "0E140E140E14-0E1B0E1B", + // 75 + "0E0A:0E190E19", + // 76 + "0E0A:0E190E19:0E170E17", + // 77 + "0E27/0E14/0E1B0E1B0E1B0E1B 0E0A:0E190E19", + // 78 + "0E190E19:0E170E17", + // 79 + "[0E0A]:0E190E19:0E170E17", + // 80 + "0E190E19:0E170E17.0", + // 81 + "d/m/bb", + // end + }; + + public static String getBuiltinFormat(Integer index) { + if (index == null || index < 0 || index >= builtinFormats.length) { + return null; + } + return builtinFormats[index]; + } + + public static String getFormat(Integer index, String format) { + if (!StringUtils.isEmpty(format)) { + return format; + } + return getBuiltinFormat(index); + } + +} diff --git a/src/main/java/com/alibaba/excel/util/DateUtils.java b/src/main/java/com/alibaba/excel/util/DateUtils.java index a581a955..dfc8d20a 100644 --- a/src/main/java/com/alibaba/excel/util/DateUtils.java +++ b/src/main/java/com/alibaba/excel/util/DateUtils.java @@ -1,10 +1,15 @@ package com.alibaba.excel.util; +import java.text.Format; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; -import com.alibaba.excel.exception.ExcelDataConvertException; +import org.apache.poi.ss.formula.ConditionalFormattingEvaluator; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.DateUtil; +import org.apache.poi.ss.usermodel.ExcelNumberFormat; +import org.apache.poi.ss.usermodel.ExcelStyleDateFormatter; /** * Date utils @@ -12,6 +17,9 @@ import com.alibaba.excel.exception.ExcelDataConvertException; * @author Jiaju Zhuang **/ public class DateUtils { + + + public static final String DATE_FORMAT_10 = "yyyy-MM-dd"; public static final String DATE_FORMAT_14 = "yyyyMMddHHmmss"; public static final String DATE_FORMAT_17 = "yyyyMMdd HH:mm:ss"; @@ -101,4 +109,194 @@ public class DateUtils { } return new SimpleDateFormat(dateFormat).format(date); } + +// +// /** +// * Determine if it is a date format. +// * +// * @param dataFormat +// * @param dataFormatString +// * @return +// */ +// public static boolean isDateFormatted(Integer dataFormat, String dataFormatString) { +// if (cell == null) { +// return false; +// } +// boolean isDate = false; +// +// double d = cell.getNumericCellValue(); +// if (DateUtil.isValidExcelDate(d)) { +// ExcelNumberFormat nf = ExcelNumberFormat.from(cell, cfEvaluator); +// if (nf == null) { +// return false; +// } +// bDate = isADateFormat(nf); +// } +// return bDate; +// } +// +// private String getFormattedDateString(Cell cell, ConditionalFormattingEvaluator cfEvaluator) { +// if (cell == null) { +// return null; +// } +// Format dateFormat = getFormat(cell, cfEvaluator); +// synchronized (dateFormat) { +// if(dateFormat instanceof ExcelStyleDateFormatter) { +// // Hint about the raw excel value +// ((ExcelStyleDateFormatter)dateFormat).setDateToBeFormatted( +// cell.getNumericCellValue() +// ); +// } +// Date d = cell.getDateCellValue(); +// return performDateFormatting(d, dateFormat); +// } +// } +// +// +// public static boolean isADateFormat(int formatIndex, String formatString) { +// // First up, is this an internal date format? +// if (isInternalDateFormat(formatIndex)) { +// return true; +// } +// if (StringUtils.isEmpty(formatString)) { +// return false; +// } +// +// // check the cache first +// if (isCached(formatString, formatIndex)) { +// return lastCachedResult.get(); +// } +// +// String fs = formatString; +// /*if (false) { +// // Normalize the format string. The code below is equivalent +// // to the following consecutive regexp replacements: +// +// // Translate \- into just -, before matching +// fs = fs.replaceAll("\\\\-","-"); +// // And \, into , +// fs = fs.replaceAll("\\\\,",","); +// // And \. into . +// fs = fs.replaceAll("\\\\\\.","."); +// // And '\ ' into ' ' +// fs = fs.replaceAll("\\\\ "," "); +// +// // If it end in ;@, that's some crazy dd/mm vs mm/dd +// // switching stuff, which we can ignore +// fs = fs.replaceAll(";@", ""); +// +// // The code above was reworked as suggested in bug 48425: +// // simple loop is more efficient than consecutive regexp replacements. +// }*/ +// final int length = fs.length(); +// StringBuilder sb = new StringBuilder(length); +// for (int i = 0; i < length; i++) { +// char c = fs.charAt(i); +// if (i < length - 1) { +// char nc = fs.charAt(i + 1); +// if (c == '\\') { +// switch (nc) { +// case '-': +// case ',': +// case '.': +// case ' ': +// case '\\': +// // skip current '\' and continue to the next char +// continue; +// } +// } else if (c == ';' && nc == '@') { +// i++; +// // skip ";@" duplets +// continue; +// } +// } +// sb.append(c); +// } +// fs = sb.toString(); +// +// // short-circuit if it indicates elapsed time: [h], [m] or [s] +// if (date_ptrn4.matcher(fs).matches()) { +// cache(formatString, formatIndex, true); +// return true; +// } +// // If it starts with [DBNum1] or [DBNum2] or [DBNum3] +// // then it could be a Chinese date +// fs = date_ptrn5.matcher(fs).replaceAll(""); +// // If it starts with [$-...], then could be a date, but +// // who knows what that starting bit is all about +// fs = date_ptrn1.matcher(fs).replaceAll(""); +// // If it starts with something like [Black] or [Yellow], +// // then it could be a date +// fs = date_ptrn2.matcher(fs).replaceAll(""); +// // You're allowed something like dd/mm/yy;[red]dd/mm/yy +// // which would place dates before 1900/1904 in red +// // For now, only consider the first one +// final int separatorIndex = fs.indexOf(';'); +// if (0 < separatorIndex && separatorIndex < fs.length() - 1) { +// fs = fs.substring(0, separatorIndex); +// } +// +// // Ensure it has some date letters in it +// // (Avoids false positives on the rest of pattern 3) +// if (!date_ptrn3a.matcher(fs).find()) { +// return false; +// } +// +// // If we get here, check it's only made up, in any case, of: +// // y m d h s - \ / , . : [ ] T +// // optionally followed by AM/PM +// +// boolean result = date_ptrn3b.matcher(fs).matches(); +// cache(formatString, formatIndex, result); +// return result; +// } +// +// /** +// * Given a format ID this will check whether the format represents an internal excel date format or not. +// * +// * @see #isADateFormat(int, java.lang.String) +// */ +// public static boolean isInternalDateFormat(int format) { +// switch (format) { +// // Internal Date Formats as described on page 427 in +// // Microsoft Excel Dev's Kit... +// // 14-22 +// case 0x0e: +// case 0x0f: +// case 0x10: +// case 0x11: +// case 0x12: +// case 0x13: +// case 0x14: +// case 0x15: +// case 0x16: +// // 27-36 +// case 0x1b: +// case 0x1c: +// case 0x1d: +// case 0x1e: +// case 0x1f: +// case 0x20: +// case 0x21: +// case 0x22: +// case 0x23: +// case 0x24: +// // 45-47 +// case 0x2d: +// case 0x2e: +// case 0x2f: +// // 50-58 +// case 0x32: +// case 0x33: +// case 0x34: +// case 0x35: +// case 0x36: +// case 0x37: +// case 0x38: +// case 0x39: +// case 0x3a: +// return true; +// } +// return false; +// } } diff --git a/src/main/java/com/alibaba/excel/util/NumberDataFormatterUtils.java b/src/main/java/com/alibaba/excel/util/NumberDataFormatterUtils.java new file mode 100644 index 00000000..6a4b9c55 --- /dev/null +++ b/src/main/java/com/alibaba/excel/util/NumberDataFormatterUtils.java @@ -0,0 +1,154 @@ +//package com.alibaba.excel.util; +// +//import java.text.Format; +// +//import org.apache.poi.ss.format.CellFormat; +//import org.apache.poi.ss.formula.ConditionalFormattingEvaluator; +//import org.apache.poi.ss.usermodel.Cell; +//import org.apache.poi.ss.usermodel.DataFormatter; +//import org.apache.poi.ss.usermodel.DateUtil; +//import org.apache.poi.ss.usermodel.ExcelNumberFormat; +//import org.apache.poi.ss.usermodel.ExcelStyleDateFormatter; +//import org.apache.poi.util.POILogger; +// +///** +// * Convert number data, including date. +// * +// * @author Jiaju Zhuang +// **/ +//public class NumberDataFormatterUtils { +// +// /** +// * +// * @param data +// * Not null. +// * @param dataFormatString +// * Not null. +// * @return +// */ +// public String format(Double data, Integer dataFormat, String dataFormatString) { +// +// if (DateUtil.isCellDateFormatted(cell, cfEvaluator)) { +// return getFormattedDateString(cell, cfEvaluator); +// } +// return getFormattedNumberString(cell, cfEvaluator); +// +// } +// +// private String getFormattedDateString(Double data,String dataFormatString) { +// +// +// if (cell == null) { +// return null; +// } +// Format dateFormat = getFormat(cell, cfEvaluator); +// synchronized (dateFormat) { +// if (dateFormat instanceof ExcelStyleDateFormatter) { +// // Hint about the raw excel value +// ((ExcelStyleDateFormatter)dateFormat).setDateToBeFormatted(cell.getNumericCellValue()); +// } +// Date d = cell.getDateCellValue(); +// return performDateFormatting(d, dateFormat); +// } +// } +// +// +// /** +// * Return a Format for the given cell if one exists, otherwise try to +// * create one. This method will return null if the any of the +// * following is true: +// *

+// * +// * @param cell The cell to retrieve a Format for +// * @return A Format for the format String +// */ +// private Format getFormat(Cell cell, ConditionalFormattingEvaluator cfEvaluator) { +// if (cell == null) return null; +// +// ExcelNumberFormat numFmt = ExcelNumberFormat.from(cell, cfEvaluator); +// +// if ( numFmt == null) { +// return null; +// } +// +// int formatIndex = numFmt.getIdx(); +// String formatStr = numFmt.getFormat(); +// if(formatStr == null || formatStr.trim().length() == 0) { +// return null; +// } +// return getFormat(cell.getNumericCellValue(), formatIndex, formatStr, isDate1904(cell)); +// } +// +// private boolean isDate1904(Cell cell) { +// if ( cell != null && cell.getSheet().getWorkbook() instanceof Date1904Support) { +// return ((Date1904Support)cell.getSheet().getWorkbook()).isDate1904(); +// +// } +// return false; +// } +// +// private Format getFormat(double cellValue, int formatIndex, String formatStrIn, boolean use1904Windowing) { +// localeChangedObservable.checkForLocaleChange(); +// +// // Might be better to separate out the n p and z formats, falling back to p when n and z are not set. +// // That however would require other code to be re factored. +// // String[] formatBits = formatStrIn.split(";"); +// // int i = cellValue > 0.0 ? 0 : cellValue < 0.0 ? 1 : 2; +// // String formatStr = (i < formatBits.length) ? formatBits[i] : formatBits[0]; +// +// String formatStr = formatStrIn; +// +// // Excel supports 2+ part conditional data formats, eg positive/negative/zero, +// // or (>1000),(>0),(0),(negative). As Java doesn't handle these kinds +// // of different formats for different ranges, just +ve/-ve, we need to +// // handle these ourselves in a special way. +// // For now, if we detect 2+ parts, we call out to CellFormat to handle it +// // TODO Going forward, we should really merge the logic between the two classes +// if (formatStr.contains(";") && +// (formatStr.indexOf(';') != formatStr.lastIndexOf(';') +// || rangeConditionalPattern.matcher(formatStr).matches() +// ) ) { +// try { +// // Ask CellFormat to get a formatter for it +// CellFormat cfmt = CellFormat.getInstance(locale, formatStr); +// // CellFormat requires callers to identify date vs not, so do so +// Object cellValueO = Double.valueOf(cellValue); +// if (DateUtil.isADateFormat(formatIndex, formatStr) && +// // don't try to handle Date value 0, let a 3 or 4-part format take care of it +// ((Double)cellValueO).doubleValue() != 0.0) { +// cellValueO = DateUtil.getJavaDate(cellValue, use1904Windowing); +// } +// // Wrap and return (non-cachable - CellFormat does that) +// return new DataFormatter.CellFormatResultWrapper( cfmt.apply(cellValueO) ); +// } catch (Exception e) { +// logger.log(POILogger.WARN, "Formatting failed for format " + formatStr + ", falling back", e); +// } +// } +// +// // Excel's # with value 0 will output empty where Java will output 0. This hack removes the # from the format. +// if (emulateCSV && cellValue == 0.0 && formatStr.contains("#") && !formatStr.contains("0")) { +// formatStr = formatStr.replaceAll("#", ""); +// } +// +// // See if we already have it cached +// Format format = formats.get(formatStr); +// if (format != null) { +// return format; +// } +// +// // Is it one of the special built in types, General or @? +// if ("General".equalsIgnoreCase(formatStr) || "@".equals(formatStr)) { +// return generalNumberFormat; +// } +// +// // Build a formatter, and cache it +// format = createFormat(cellValue, formatIndex, formatStr); +// formats.put(formatStr, format); +// return format; +// } +// +//} diff --git a/src/test/java/com/alibaba/easyexcel/test/demo/read/ReadTest.java b/src/test/java/com/alibaba/easyexcel/test/demo/read/ReadTest.java index 0dc22aa7..c3f4ada5 100644 --- a/src/test/java/com/alibaba/easyexcel/test/demo/read/ReadTest.java +++ b/src/test/java/com/alibaba/easyexcel/test/demo/read/ReadTest.java @@ -33,7 +33,7 @@ public class ReadTest { *

* 1. 创建excel对应的实体对象 参照{@link DemoData} *

- * 2. 由于默认异步读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoDataListener} + * 2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoDataListener} *

* 3. 直接读即可 */ @@ -60,7 +60,7 @@ public class ReadTest { *

* 1. 创建excel对应的实体对象,并使用{@link ExcelProperty}注解. 参照{@link IndexOrNameData} *

- * 2. 由于默认异步读取excel,所以需要创建excel一行一行的回调监听器,参照{@link IndexOrNameDataListener} + * 2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link IndexOrNameDataListener} *

* 3. 直接读即可 */ @@ -76,7 +76,7 @@ public class ReadTest { *

* 1. 创建excel对应的实体对象 参照{@link DemoData} *

- * 2. 由于默认异步读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoDataListener} + * 2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoDataListener} *

* 3. 直接读即可 */ @@ -108,7 +108,7 @@ public class ReadTest { *

* 1. 创建excel对应的实体对象 参照{@link ConverterData}.里面可以使用注解{@link DateTimeFormat}、{@link NumberFormat}或者自定义注解 *

- * 2. 由于默认异步读取excel,所以需要创建excel一行一行的回调监听器,参照{@link ConverterDataListener} + * 2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link ConverterDataListener} *

* 3. 直接读即可 */ @@ -130,7 +130,7 @@ public class ReadTest { *

* 1. 创建excel对应的实体对象 参照{@link DemoData} *

- * 2. 由于默认异步读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoDataListener} + * 2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoDataListener} *

* 3. 设置headRowNumber参数,然后读。 这里要注意headRowNumber如果不指定, 会根据你传入的class的{@link ExcelProperty#value()}里面的表头的数量来决定行数, * 如果不传入class则默认为1.当然你指定了headRowNumber不管是否传入class都是以你传入的为准。 @@ -150,7 +150,7 @@ public class ReadTest { *

* 1. 创建excel对应的实体对象 参照{@link DemoData} *

- * 2. 由于默认异步读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoHeadDataListener} + * 2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoHeadDataListener} *

* 3. 直接读即可 */ @@ -167,7 +167,7 @@ public class ReadTest { *

* 1. 创建excel对应的实体对象 参照{@link DemoData} *

- * 2. 由于默认异步读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoHeadDataListener} + * 2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoHeadDataListener} *

* 3. 直接读即可 */ @@ -184,7 +184,7 @@ public class ReadTest { *

* 1. 创建excel对应的实体对象 参照{@link ExceptionDemoData} *

- * 2. 由于默认异步读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoExceptionListener} + * 2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoExceptionListener} *

* 3. 直接读即可 */ diff --git a/src/test/java/com/alibaba/easyexcel/test/demo/web/WebTest.java b/src/test/java/com/alibaba/easyexcel/test/demo/web/WebTest.java index 98f400a4..b80022d9 100644 --- a/src/test/java/com/alibaba/easyexcel/test/demo/web/WebTest.java +++ b/src/test/java/com/alibaba/easyexcel/test/demo/web/WebTest.java @@ -85,7 +85,7 @@ public class WebTest { *

* 1. 创建excel对应的实体对象 参照{@link UploadData} *

- * 2. 由于默认异步读取excel,所以需要创建excel一行一行的回调监听器,参照{@link UploadDataListener} + * 2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link UploadDataListener} *

* 3. 直接读即可 */ diff --git a/src/test/java/com/alibaba/easyexcel/test/temp/Lock2Test.java b/src/test/java/com/alibaba/easyexcel/test/temp/Lock2Test.java index 052b60b6..1682aa17 100644 --- a/src/test/java/com/alibaba/easyexcel/test/temp/Lock2Test.java +++ b/src/test/java/com/alibaba/easyexcel/test/temp/Lock2Test.java @@ -29,7 +29,7 @@ public class Lock2Test { @Test public void test() throws Exception { - File file = new File("D:\\test\\000001.xlsx"); + File file = new File("D:\\test\\headt1.xlsx"); List list = EasyExcel.read(file).sheet().headRowNumber(0).doReadSync(); LOGGER.info("数据:{}", list.size()); diff --git a/src/test/java/com/alibaba/easyexcel/test/temp/dataformat/DataFormatData.java b/src/test/java/com/alibaba/easyexcel/test/temp/dataformat/DataFormatData.java new file mode 100644 index 00000000..d057d8e7 --- /dev/null +++ b/src/test/java/com/alibaba/easyexcel/test/temp/dataformat/DataFormatData.java @@ -0,0 +1,16 @@ +package com.alibaba.easyexcel.test.temp.dataformat; + +import com.alibaba.excel.metadata.CellData; + +import lombok.Data; + +/** + * TODO + * + * @author 罗成 + **/ +@Data +public class DataFormatData { + private CellData date; + private CellData num; +} diff --git a/src/test/java/com/alibaba/easyexcel/test/temp/dataformat/DataFormatTest.java b/src/test/java/com/alibaba/easyexcel/test/temp/dataformat/DataFormatTest.java new file mode 100644 index 00000000..d9c7bb23 --- /dev/null +++ b/src/test/java/com/alibaba/easyexcel/test/temp/dataformat/DataFormatTest.java @@ -0,0 +1,127 @@ +package com.alibaba.easyexcel.test.temp.dataformat; + +import java.io.File; +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.DataFormatter; +import org.apache.poi.ss.usermodel.DateUtil; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.junit.Ignore; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.alibaba.easyexcel.test.temp.Lock2Test; +import com.alibaba.excel.EasyExcel; +import com.alibaba.fastjson.JSON; + +/** + * 格式测试 + * + * @author Jiaju Zhuang + **/ +@Ignore +public class DataFormatTest { + private static final Logger LOGGER = LoggerFactory.getLogger(Lock2Test.class); + + @Test + public void test() throws Exception { + File file = new File("D:\\test\\dataformat.xlsx"); + + List list = + EasyExcel.read(file, DataFormatData.class, null).sheet().headRowNumber(0).doReadSync(); + LOGGER.info("数据:{}", list.size()); + for (DataFormatData data : list) { + Integer dataFormat = data.getDate().getDataFormat(); + + String dataFormatString = data.getDate().getDataFormatString(); + + if (dataFormat == null || dataFormatString == null) { + + } else { + LOGGER.info("格式化:{};{}:{}", dataFormat, dataFormatString, + DateUtil.isADateFormat(dataFormat, dataFormatString)); + } + + LOGGER.info("返回数据:{}", JSON.toJSONString(data)); + } + } + + @Test + public void testxls() throws Exception { + File file = new File("D:\\test\\dataformat.xls"); + + List list = + EasyExcel.read(file, DataFormatData.class, null).sheet().headRowNumber(0).doReadSync(); + LOGGER.info("数据:{}", list.size()); + for (DataFormatData data : list) { + Integer dataFormat = data.getDate().getDataFormat(); + + String dataFormatString = data.getDate().getDataFormatString(); + + if (dataFormat == null || dataFormatString == null) { + + } else { + LOGGER.info("格式化:{};{}:{}", dataFormat, dataFormatString, + DateUtil.isADateFormat(dataFormat, dataFormatString)); + } + + LOGGER.info("返回数据:{}", JSON.toJSONString(data)); + } + } + + @Test + public void test3() throws IOException { + String file = "D:\\test\\dataformat1.xlsx"; + XSSFWorkbook xssfWorkbook = new XSSFWorkbook(file); + Sheet xssfSheet = xssfWorkbook.getSheetAt(0); + Cell cell = xssfSheet.getRow(0).getCell(0); + DataFormatter d = new DataFormatter(); + System.out.println(d.formatCellValue(cell)); + } + + @Test + public void test31() throws IOException { + System.out.println(DateUtil.isADateFormat(181, "[DBNum1][$-404]m\"\u6708\"d\"\u65e5\";@")); + } + + @Test + public void test43() throws IOException { + SimpleDateFormat s = new SimpleDateFormat("yyyy'年'm'月'd'日' h'点'mm'哈哈哈m'"); + System.out.println(s.format(new Date())); + } + + @Test + public void test463() throws IOException { + SimpleDateFormat s = new SimpleDateFormat("[$-804]yyyy年m月"); + System.out.println(s.format(new Date())); + } + + @Test + public void test1() throws Exception { + System.out.println(DateUtil.isADateFormat(181, "yyyy\"年啊\"m\"月\"d\"日\"\\ h")); + System.out.println(DateUtil.isADateFormat(180, "yyyy\"年\"m\"月\"d\"日\"\\ h\"点\"")); + } + + @Test + public void test2() throws Exception { + List list1 = new ArrayList(3000); + long start = System.currentTimeMillis(); + for (int i = 0; i < 10000; i++) { + list1.clear(); + } + System.out.println("end:" + (System.currentTimeMillis() - start)); + start = System.currentTimeMillis(); + for (int i = 0; i < 10000; i++) { + list1 = new ArrayList(3000); + } + System.out.println("end:" + (System.currentTimeMillis() - start)); + } + +} diff --git a/src/test/java/com/alibaba/easyexcel/test/temp/dataformat/DataFormatter1.java b/src/test/java/com/alibaba/easyexcel/test/temp/dataformat/DataFormatter1.java new file mode 100644 index 00000000..0239ed5b --- /dev/null +++ b/src/test/java/com/alibaba/easyexcel/test/temp/dataformat/DataFormatter1.java @@ -0,0 +1,1292 @@ +/* + * ==================================================================== Licensed to the Apache Software Foundation (ASF) + * under one or more contributor license agreements. See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the + * License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * 2012 - Alfresco Software, Ltd. Alfresco Software has modified source of this file The details of changes as svn diff + * can be found in svn at location root/projects/3rd-party/src + * ==================================================================== + */ +package com.alibaba.easyexcel.test.temp.dataformat; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.text.DateFormat; +import java.text.DateFormatSymbols; +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; +import java.text.FieldPosition; +import java.text.Format; +import java.text.ParsePosition; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Observable; +import java.util.Observer; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.poi.ss.format.CellFormat; +import org.apache.poi.ss.format.CellFormatResult; +import org.apache.poi.ss.formula.ConditionalFormattingEvaluator; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.CellType; +import org.apache.poi.ss.usermodel.DataFormatter; +import org.apache.poi.ss.usermodel.DateUtil; +import org.apache.poi.ss.usermodel.ExcelGeneralNumberFormat; +import org.apache.poi.ss.usermodel.ExcelNumberFormat; +import org.apache.poi.ss.usermodel.ExcelStyleDateFormatter; +import org.apache.poi.ss.usermodel.FormulaError; +import org.apache.poi.ss.usermodel.FormulaEvaluator; +import org.apache.poi.ss.usermodel.FractionFormat; +import org.apache.poi.ss.util.DateFormatConverter; +import org.apache.poi.ss.util.NumberToTextConverter; +import org.apache.poi.util.LocaleUtil; +import org.apache.poi.util.POILogFactory; +import org.apache.poi.util.POILogger; + +/** + * DataFormatter contains methods for formatting the value stored in an Cell. This can be useful for reports and GUI + * presentations when you need to display data exactly as it appears in Excel. Supported formats include currency, SSN, + * percentages, decimals, dates, phone numbers, zip codes, etc. + *

+ * Internally, formats will be implemented using subclasses of {@link Format} such as {@link DecimalFormat} and + * {@link java.text.SimpleDateFormat}. Therefore the formats used by this class must obey the same pattern rules as + * these Format subclasses. This means that only legal number pattern characters ("0", "#", ".", "," etc.) may appear in + * number formats. Other characters can be inserted before or after the number pattern to form a + * prefix or suffix. + *

+ *

+ * For example the Excel pattern "$#,##0.00 "USD"_);($#,##0.00 "USD")" + * will be correctly formatted as "$1,000.00 USD" or "($1,000.00 USD)". However the pattern + * "00-00-00" is incorrectly formatted by DecimalFormat as "000000--". For Excel formats that are not + * compatible with DecimalFormat, you can provide your own custom {@link Format} implementation via + * DataFormatter.addFormat(String,Format). The following custom formats are already provided by this class: + *

+ * + *
+ * 
  • SSN "000-00-0000"
  • + *
  • Phone Number "(###) ###-####"
  • + *
  • Zip plus 4 "00000-0000"
  • + *
+ *
+ *

+ * If the Excel format pattern cannot be parsed successfully, then a default format will be used. The default number + * format will mimic the Excel General format: "#" for whole numbers and "#.##########" for decimal numbers. You can + * override the default format pattern with + * DataFormatter.setDefaultNumberFormat(Format). Note: the default format will only be used when a Format + * cannot be created from the cell's data format string. + * + *

+ * Note that by default formatted numeric values are trimmed. Excel formats can contain spacers and padding and the + * default behavior is to strip them off. + *

+ *

+ * Example: + *

+ *

+ * Consider a numeric cell with a value 12.343 and format "##.##_ ". The trailing underscore + * and space ("_ ") in the format adds a space to the end and Excel formats this cell as "12.34 ", but + * DataFormatter trims the formatted value and returns "12.34". + *

+ * You can enable spaces by passing the emulateCSV=true flag in the DateFormatter cosntructor. + * If set to true, then the output tries to conform to what you get when you take an xls or xlsx in Excel and Save As + * CSV file: + *
    + *
  • returned values are not trimmed
  • + *
  • Invalid dates are formatted as 255 pound signs ("#")
  • + *
  • simulate Excel's handling of a format string of all # when the value is 0. Excel will output "", + * DataFormatter will output "0". + *
+ *

+ * Some formats are automatically "localized" by Excel, eg show as mm/dd/yyyy when loaded in Excel in some Locales but + * as dd/mm/yyyy in others. These are always returned in the "default" (US) format, as stored in the file. Some format + * strings request an alternate locale, eg [$-809]d/m/yy h:mm AM/PM which explicitly requests UK locale. + * These locale directives are (currently) ignored. You can use {@link DateFormatConverter} to do some of this + * localisation if you need it. + */ +public class DataFormatter1 implements Observer { + private static final String defaultFractionWholePartFormat = "#"; + private static final String defaultFractionFractionPartFormat = "#/##"; + /** Pattern to find a number format: "0" or "#" */ + private static final Pattern numPattern = Pattern.compile("[0#]+"); + + /** Pattern to find days of week as text "ddd...." */ + private static final Pattern daysAsText = Pattern.compile("([d]{3,})", Pattern.CASE_INSENSITIVE); + + /** Pattern to find "AM/PM" marker */ + private static final Pattern amPmPattern = Pattern.compile("((A|P)[M/P]*)", Pattern.CASE_INSENSITIVE); + + /** Pattern to find formats with condition ranges e.g. [>=100] */ + private static final Pattern rangeConditionalPattern = + Pattern.compile(".*\\[\\s*(>|>=|<|<=|=)\\s*[0-9]*\\.*[0-9].*"); + + /** + * A regex to find locale patterns like [$$-1009] and [$?-452]. Note that we don't currently process these into + * locales + */ + private static final Pattern localePatternGroup = Pattern.compile("(\\[\\$[^-\\]]*-[0-9A-Z]+\\])"); + + /** + * A regex to match the colour formattings rules. Allowed colours are: Black, Blue, Cyan, Green, Magenta, Red, + * White, Yellow, "Color n" (1<=n<=56) + */ + private static final Pattern colorPattern = Pattern.compile("(\\[BLACK\\])|(\\[BLUE\\])|(\\[CYAN\\])|(\\[GREEN\\])|" + + "(\\[MAGENTA\\])|(\\[RED\\])|(\\[WHITE\\])|(\\[YELLOW\\])|" + + "(\\[COLOR\\s*\\d\\])|(\\[COLOR\\s*[0-5]\\d\\])", Pattern.CASE_INSENSITIVE); + + /** + * A regex to identify a fraction pattern. This requires that replaceAll("\\?", "#") has already been called + */ + private static final Pattern fractionPattern = Pattern.compile("(?:([#\\d]+)\\s+)?(#+)\\s*\\/\\s*([#\\d]+)"); + + /** + * A regex to strip junk out of fraction formats + */ + private static final Pattern fractionStripper = Pattern.compile("(\"[^\"]*\")|([^ \\?#\\d\\/]+)"); + + /** + * A regex to detect if an alternate grouping character is used in a numeric format + */ + private static final Pattern alternateGrouping = Pattern.compile("([#0]([^.#0])[#0]{3})"); + + /** + * Cells formatted with a date or time format and which contain invalid date or time values show 255 pound signs + * ("#"). + */ + private static final String invalidDateTimeString; + static { + StringBuilder buf = new StringBuilder(); + for (int i = 0; i < 255; i++) + buf.append('#'); + invalidDateTimeString = buf.toString(); + } + + /** + * The decimal symbols of the locale used for formatting values. + */ + private DecimalFormatSymbols decimalSymbols; + + /** + * The date symbols of the locale used for formatting values. + */ + private DateFormatSymbols dateSymbols; + + /** + * A default date format, if no date format was given + */ + private DateFormat defaultDateformat; + + /** General format for numbers. */ + private Format generalNumberFormat; + + /** A default format to use when a number pattern cannot be parsed. */ + private Format defaultNumFormat; + + /** + * A map to cache formats. Map formats + */ + private final Map formats = new HashMap(); + + private final boolean emulateCSV; + + /** stores the locale valid it the last formatting call */ + private Locale locale; + + /** stores if the locale should change according to {@link LocaleUtil#getUserLocale()} */ + private boolean localeIsAdapting; + + private class LocaleChangeObservable extends Observable { + void checkForLocaleChange() { + checkForLocaleChange(LocaleUtil.getUserLocale()); + } + + void checkForLocaleChange(Locale newLocale) { + if (!localeIsAdapting) + return; + if (newLocale.equals(locale)) + return; + super.setChanged(); + notifyObservers(newLocale); + } + } + + /** the Observable to notify, when the locale has been changed */ + private final LocaleChangeObservable localeChangedObservable = new LocaleChangeObservable(); + + /** For logging any problems we find */ + private static POILogger logger = POILogFactory.getLogger(DataFormatter.class); + + /** + * Creates a formatter using the {@link Locale#getDefault() default locale}. + */ + public DataFormatter1() { + this(false); + } + + /** + * Creates a formatter using the {@link Locale#getDefault() default locale}. + * + * @param emulateCSV + * whether to emulate CSV output. + */ + public DataFormatter1(boolean emulateCSV) { + this(LocaleUtil.getUserLocale(), true, emulateCSV); + } + + /** + * Creates a formatter using the given locale. + */ + public DataFormatter1(Locale locale) { + this(locale, false); + } + + /** + * Creates a formatter using the given locale. + * + * @param emulateCSV + * whether to emulate CSV output. + */ + public DataFormatter1(Locale locale, boolean emulateCSV) { + this(locale, false, emulateCSV); + } + + /** + * Creates a formatter using the given locale. + * + * @param localeIsAdapting + * (true only if locale is not user-specified) + * @param emulateCSV + * whether to emulate CSV output. + */ + private DataFormatter1(Locale locale, boolean localeIsAdapting, boolean emulateCSV) { + this.localeIsAdapting = true; + localeChangedObservable.addObserver(this); + // localeIsAdapting must be true prior to this first checkForLocaleChange call. + localeChangedObservable.checkForLocaleChange(locale); + // set localeIsAdapting so subsequent checks perform correctly + // (whether a specific locale was provided to this DataFormatter or DataFormatter should + // adapt to the current user locale as the locale changes) + this.localeIsAdapting = localeIsAdapting; + this.emulateCSV = emulateCSV; + } + + /** + * Return a Format for the given cell if one exists, otherwise try to create one. This method will return + * null if the any of the following is true: + *

    + *
  • the cell's style is null
  • + *
  • the style's data format string is null or empty
  • + *
  • the format string cannot be recognized as either a number or date
  • + *
+ * + * @param cell + * The cell to retrieve a Format for + * @return A Format for the format String + */ + private Format getFormat(Cell cell, ConditionalFormattingEvaluator cfEvaluator) { + if (cell == null) + return null; + + ExcelNumberFormat numFmt = ExcelNumberFormat.from(cell, cfEvaluator); + + if (numFmt == null) { + return null; + } + + int formatIndex = numFmt.getIdx(); + String formatStr = numFmt.getFormat(); + if (formatStr == null || formatStr.trim().length() == 0) { + return null; + } + return getFormat(cell.getNumericCellValue(), formatIndex, formatStr); + } + + private Format getFormat(double cellValue, int formatIndex, String formatStrIn) { + localeChangedObservable.checkForLocaleChange(); + + // Might be better to separate out the n p and z formats, falling back to p when n and z are not set. + // That however would require other code to be re factored. + // String[] formatBits = formatStrIn.split(";"); + // int i = cellValue > 0.0 ? 0 : cellValue < 0.0 ? 1 : 2; + // String formatStr = (i < formatBits.length) ? formatBits[i] : formatBits[0]; + + String formatStr = formatStrIn; + + // Excel supports 2+ part conditional data formats, eg positive/negative/zero, + // or (>1000),(>0),(0),(negative). As Java doesn't handle these kinds + // of different formats for different ranges, just +ve/-ve, we need to + // handle these ourselves in a special way. + // For now, if we detect 2+ parts, we call out to CellFormat to handle it + // TODO Going forward, we should really merge the logic between the two classes + if (formatStr.contains(";") && (formatStr.indexOf(';') != formatStr.lastIndexOf(';') + || rangeConditionalPattern.matcher(formatStr).matches())) { + try { + // Ask CellFormat to get a formatter for it + CellFormat cfmt = CellFormat.getInstance(locale, formatStr); + // CellFormat requires callers to identify date vs not, so do so + Object cellValueO = Double.valueOf(cellValue); + if (DateUtil.isADateFormat(formatIndex, formatStr) && + // don't try to handle Date value 0, let a 3 or 4-part format take care of it + ((Double)cellValueO).doubleValue() != 0.0) { + cellValueO = DateUtil.getJavaDate(cellValue); + } + // Wrap and return (non-cachable - CellFormat does that) + return new CellFormatResultWrapper(cfmt.apply(cellValueO)); + } catch (Exception e) { + logger.log(POILogger.WARN, "Formatting failed for format " + formatStr + ", falling back", e); + } + } + + // Excel's # with value 0 will output empty where Java will output 0. This hack removes the # from the format. + if (emulateCSV && cellValue == 0.0 && formatStr.contains("#") && !formatStr.contains("0")) { + formatStr = formatStr.replaceAll("#", ""); + } + + // See if we already have it cached + Format format = formats.get(formatStr); + if (format != null) { + return format; + } + + // Is it one of the special built in types, General or @? + if ("General".equalsIgnoreCase(formatStr) || "@".equals(formatStr)) { + return generalNumberFormat; + } + + // Build a formatter, and cache it + format = createFormat(cellValue, formatIndex, formatStr); + formats.put(formatStr, format); + return format; + } + + /** + * Create and return a Format based on the format string from a cell's style. If the pattern cannot be parsed, + * return a default pattern. + * + * @param cell + * The Excel cell + * @return A Format representing the excel format. May return null. + */ + public Format createFormat(Cell cell) { + + int formatIndex = cell.getCellStyle().getDataFormat(); + String formatStr = cell.getCellStyle().getDataFormatString(); + return createFormat(cell.getNumericCellValue(), formatIndex, formatStr); + } + + private Format createFormat(double cellValue, int formatIndex, String sFormat) { + localeChangedObservable.checkForLocaleChange(); + + String formatStr = sFormat; + + // Remove colour formatting if present + Matcher colourM = colorPattern.matcher(formatStr); + while (colourM.find()) { + String colour = colourM.group(); + + // Paranoid replacement... + int at = formatStr.indexOf(colour); + if (at == -1) + break; + String nFormatStr = formatStr.substring(0, at) + formatStr.substring(at + colour.length()); + if (nFormatStr.equals(formatStr)) + break; + + // Try again in case there's multiple + formatStr = nFormatStr; + colourM = colorPattern.matcher(formatStr); + } + + // Strip off the locale information, we use an instance-wide locale for everything + Matcher m = localePatternGroup.matcher(formatStr); + while (m.find()) { + String match = m.group(); + String symbol = match.substring(match.indexOf('$') + 1, match.indexOf('-')); + if (symbol.indexOf('$') > -1) { + symbol = symbol.substring(0, symbol.indexOf('$')) + '\\' + + symbol.substring(symbol.indexOf('$'), symbol.length()); + } + formatStr = m.replaceAll(symbol); + m = localePatternGroup.matcher(formatStr); + } + + // Check for special cases + if (formatStr == null || formatStr.trim().length() == 0) { + return getDefaultFormat(cellValue); + } + + if ("General".equalsIgnoreCase(formatStr) || "@".equals(formatStr)) { + return generalNumberFormat; + } + + if ("".equals("")||(DateUtil.isADateFormat(formatIndex, formatStr) && DateUtil.isValidExcelDate(cellValue))) { + return createDateFormat(formatStr, cellValue); + } + // Excel supports fractions in format strings, which Java doesn't + if (formatStr.contains("#/") || formatStr.contains("?/")) { + String[] chunks = formatStr.split(";"); + for (String chunk1 : chunks) { + String chunk = chunk1.replaceAll("\\?", "#"); + Matcher matcher = fractionStripper.matcher(chunk); + chunk = matcher.replaceAll(" "); + chunk = chunk.replaceAll(" +", " "); + Matcher fractionMatcher = fractionPattern.matcher(chunk); + // take the first match + if (fractionMatcher.find()) { + String wholePart = (fractionMatcher.group(1) == null) ? "" : defaultFractionWholePartFormat; + return new FractionFormat(wholePart, fractionMatcher.group(3)); + } + } + + // Strip custom text in quotes and escaped characters for now as it can cause performance problems in + // fractions. + // String strippedFormatStr = formatStr.replaceAll("\\\\ ", " ").replaceAll("\\\\.", + // "").replaceAll("\"[^\"]*\"", " ").replaceAll("\\?", "#"); + // System.out.println("formatStr: "+strippedFormatStr); + return new FractionFormat(defaultFractionWholePartFormat, defaultFractionFractionPartFormat); + } + + if (numPattern.matcher(formatStr).find()) { + return createNumberFormat(formatStr, cellValue); + } + + if (emulateCSV) { + return new ConstantStringFormat(cleanFormatForNumber(formatStr)); + } + // TODO - when does this occur? + return null; + } + + private Format createDateFormat(String pFormatStr, double cellValue) { + String formatStr = pFormatStr; + formatStr = formatStr.replaceAll("\\\\-", "-"); + formatStr = formatStr.replaceAll("\\\\,", ","); + formatStr = formatStr.replaceAll("\\\\\\.", "."); // . is a special regexp char + formatStr = formatStr.replaceAll("\\\\ ", " "); + formatStr = formatStr.replaceAll("\\\\/", "/"); // weird: m\\/d\\/yyyy + formatStr = formatStr.replaceAll(";@", ""); + formatStr = formatStr.replaceAll("\"/\"", "/"); // "/" is escaped for no reason in: mm"/"dd"/"yyyy + formatStr = formatStr.replace("\"\"", "'"); // replace Excel quoting with Java style quoting + formatStr = formatStr.replaceAll("\\\\T", "'T'"); // Quote the T is iso8601 style dates + + boolean hasAmPm = false; + Matcher amPmMatcher = amPmPattern.matcher(formatStr); + while (amPmMatcher.find()) { + formatStr = amPmMatcher.replaceAll("@"); + hasAmPm = true; + amPmMatcher = amPmPattern.matcher(formatStr); + } + formatStr = formatStr.replaceAll("@", "a"); + + Matcher dateMatcher = daysAsText.matcher(formatStr); + if (dateMatcher.find()) { + String match = dateMatcher.group(0).toUpperCase(Locale.ROOT).replaceAll("D", "E"); + formatStr = dateMatcher.replaceAll(match); + } + + // Convert excel date format to SimpleDateFormat. + // Excel uses lower and upper case 'm' for both minutes and months. + // From Excel help: + /* + The "m" or "mm" code must appear immediately after the "h" or"hh" + code or immediately before the "ss" code; otherwise, Microsoft + Excel displays the month instead of minutes." + */ + + StringBuilder sb = new StringBuilder(); + char[] chars = formatStr.toCharArray(); + boolean mIsMonth = true; + List ms = new ArrayList(); + boolean isElapsed = false; + for (int j = 0; j < chars.length; j++) { + char c = chars[j]; + if (c == '\'') { + sb.append(c); + j++; + + // skip until the next quote + while (j < chars.length) { + c = chars[j]; + sb.append(c); + if (c == '\'') { + break; + } + j++; + } + } else if (c == '[' && !isElapsed) { + isElapsed = true; + mIsMonth = false; + sb.append(c); + } else if (c == ']' && isElapsed) { + isElapsed = false; + sb.append(c); + } else if (isElapsed) { + if (c == 'h' || c == 'H') { + sb.append('H'); + } else if (c == 'm' || c == 'M') { + sb.append('m'); + } else if (c == 's' || c == 'S') { + sb.append('s'); + } else { + sb.append(c); + } + } else if (c == 'h' || c == 'H') { + mIsMonth = false; + if (hasAmPm) { + sb.append('h'); + } else { + sb.append('H'); + } + } else if (c == 'm' || c == 'M') { + if (mIsMonth) { + sb.append('M'); + ms.add(Integer.valueOf(sb.length() - 1)); + } else { + sb.append('m'); + } + } else if (c == 's' || c == 'S') { + sb.append('s'); + // if 'M' precedes 's' it should be minutes ('m') + for (int index : ms) { + if (sb.charAt(index) == 'M') { + sb.replace(index, index + 1, "m"); + } + } + mIsMonth = true; + ms.clear(); + } else if (Character.isLetter(c)) { + mIsMonth = true; + ms.clear(); + if (c == 'y' || c == 'Y') { + sb.append('y'); + } else if (c == 'd' || c == 'D') { + sb.append('d'); + } else { + sb.append(c); + } + } else { + if (Character.isWhitespace(c)) { + ms.clear(); + } + sb.append(c); + } + } + formatStr = sb.toString(); + + try { + return new ExcelStyleDateFormatter(formatStr, dateSymbols); + } catch (IllegalArgumentException iae) { + logger.log(POILogger.DEBUG, "Formatting failed for format " + formatStr + ", falling back", iae); + // the pattern could not be parsed correctly, + // so fall back to the default number format + return getDefaultFormat(cellValue); + } + + } + + private String cleanFormatForNumber(String formatStr) { + StringBuilder sb = new StringBuilder(formatStr); + + if (emulateCSV) { + // Requested spacers with "_" are replaced by a single space. + // Full-column-width padding "*" are removed. + // Not processing fractions at this time. Replace ? with space. + // This matches CSV output. + for (int i = 0; i < sb.length(); i++) { + char c = sb.charAt(i); + if (c == '_' || c == '*' || c == '?') { + if (i > 0 && sb.charAt((i - 1)) == '\\') { + // It's escaped, don't worry + continue; + } + if (c == '?') { + sb.setCharAt(i, ' '); + } else if (i < sb.length() - 1) { + // Remove the character we're supposed + // to match the space of / pad to the + // column width with + if (c == '_') { + sb.setCharAt(i + 1, ' '); + } else { + sb.deleteCharAt(i + 1); + } + // Remove the character too + sb.deleteCharAt(i); + i--; + } + } + } + } else { + // If they requested spacers, with "_", + // remove those as we don't do spacing + // If they requested full-column-width + // padding, with "*", remove those too + for (int i = 0; i < sb.length(); i++) { + char c = sb.charAt(i); + if (c == '_' || c == '*') { + if (i > 0 && sb.charAt((i - 1)) == '\\') { + // It's escaped, don't worry + continue; + } + if (i < sb.length() - 1) { + // Remove the character we're supposed + // to match the space of / pad to the + // column width with + sb.deleteCharAt(i + 1); + } + // Remove the _ too + sb.deleteCharAt(i); + i--; + } + } + } + + // Now, handle the other aspects like + // quoting and scientific notation + for (int i = 0; i < sb.length(); i++) { + char c = sb.charAt(i); + // remove quotes and back slashes + if (c == '\\' || c == '"') { + sb.deleteCharAt(i); + i--; + + // for scientific/engineering notation + } else if (c == '+' && i > 0 && sb.charAt(i - 1) == 'E') { + sb.deleteCharAt(i); + i--; + } + } + + return sb.toString(); + } + + private static class InternalDecimalFormatWithScale extends Format { + + private static final Pattern endsWithCommas = Pattern.compile("(,+)$"); + private BigDecimal divider; + private static final BigDecimal ONE_THOUSAND = new BigDecimal(1000); + private final DecimalFormat df; + + private static final String trimTrailingCommas(String s) { + return s.replaceAll(",+$", ""); + } + + public InternalDecimalFormatWithScale(String pattern, DecimalFormatSymbols symbols) { + df = new DecimalFormat(trimTrailingCommas(pattern), symbols); + setExcelStyleRoundingMode(df); + Matcher endsWithCommasMatcher = endsWithCommas.matcher(pattern); + if (endsWithCommasMatcher.find()) { + String commas = (endsWithCommasMatcher.group(1)); + BigDecimal temp = BigDecimal.ONE; + for (int i = 0; i < commas.length(); ++i) { + temp = temp.multiply(ONE_THOUSAND); + } + divider = temp; + } else { + divider = null; + } + } + + private Object scaleInput(Object obj) { + if (divider != null) { + if (obj instanceof BigDecimal) { + obj = ((BigDecimal)obj).divide(divider, RoundingMode.HALF_UP); + } else if (obj instanceof Double) { + obj = (Double)obj / divider.doubleValue(); + } else { + throw new UnsupportedOperationException(); + } + } + return obj; + } + + @Override + public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos) { + obj = scaleInput(obj); + return df.format(obj, toAppendTo, pos); + } + + @Override + public Object parseObject(String source, ParsePosition pos) { + throw new UnsupportedOperationException(); + } + } + + private Format createNumberFormat(String formatStr, double cellValue) { + String format = cleanFormatForNumber(formatStr); + DecimalFormatSymbols symbols = decimalSymbols; + + // Do we need to change the grouping character? + // eg for a format like #'##0 which wants 12'345 not 12,345 + Matcher agm = alternateGrouping.matcher(format); + if (agm.find()) { + char grouping = agm.group(2).charAt(0); + // Only replace the grouping character if it is not the default + // grouping character for the US locale (',') in order to enable + // correct grouping for non-US locales. + if (grouping != ',') { + symbols = DecimalFormatSymbols.getInstance(locale); + + symbols.setGroupingSeparator(grouping); + String oldPart = agm.group(1); + String newPart = oldPart.replace(grouping, ','); + format = format.replace(oldPart, newPart); + } + } + + try { + return new InternalDecimalFormatWithScale(format, symbols); + } catch (IllegalArgumentException iae) { + logger.log(POILogger.DEBUG, "Formatting failed for format " + formatStr + ", falling back", iae); + // the pattern could not be parsed correctly, + // so fall back to the default number format + return getDefaultFormat(cellValue); + } + } + + /** + * Returns a default format for a cell. + * + * @param cell + * The cell + * @return a default format + */ + public Format getDefaultFormat(Cell cell) { + return getDefaultFormat(cell.getNumericCellValue()); + } + + private Format getDefaultFormat(double cellValue) { + localeChangedObservable.checkForLocaleChange(); + + // for numeric cells try user supplied default + if (defaultNumFormat != null) { + return defaultNumFormat; + + // otherwise use general format + } + return generalNumberFormat; + } + + /** + * Performs Excel-style date formatting, using the supplied Date and format + */ + private String performDateFormatting(Date d, Format dateFormat) { + return (dateFormat != null ? dateFormat : defaultDateformat).format(d); + } + + /** + * Returns the formatted value of an Excel date as a String based on the cell's DataFormat. + * i.e. "Thursday, January 02, 2003" , "01/02/2003" , "02-Jan" , etc. + *

+ * If any conditional format rules apply, the highest priority with a number format is used. If no rules contain a + * number format, or no rules apply, the cell's style format is used. If the style does not have a format, the + * default date format is applied. + * + * @param cell + * to format + * @param cfEvaluator + * ConditionalFormattingEvaluator (if available) + * @return Formatted value + */ + private String getFormattedDateString(Cell cell, ConditionalFormattingEvaluator cfEvaluator) { + Format dateFormat = getFormat(cell, cfEvaluator); + if (dateFormat instanceof ExcelStyleDateFormatter) { + // Hint about the raw excel value + ((ExcelStyleDateFormatter)dateFormat).setDateToBeFormatted(cell.getNumericCellValue()); + } + Date d = cell.getDateCellValue(); + return performDateFormatting(d, dateFormat); + } + + /** + * Returns the formatted value of an Excel number as a String based on the cell's DataFormat. + * Supported formats include currency, percents, decimals, phone number, SSN, etc.: "61.54%", "$100.00", "(800) + * 555-1234". + *

+ * Format comes from either the highest priority conditional format rule with a specified format, or from the cell + * style. + * + * @param cell + * The cell + * @param cfEvaluator + * if available, or null + * @return a formatted number string + */ + private String getFormattedNumberString(Cell cell, ConditionalFormattingEvaluator cfEvaluator) { + + Format numberFormat = getFormat(cell, cfEvaluator); + double d = cell.getNumericCellValue(); + if (numberFormat == null) { + return String.valueOf(d); + } + String formatted = numberFormat.format(new Double(d)); + return formatted.replaceFirst("E(\\d)", "E+$1"); // to match Excel's E-notation + } + + /** + * Formats the given raw cell value, based on the supplied format index and string, according to excel style rules. + * + * @see #formatCellValue(Cell) + */ + public String formatRawCellContents(double value, int formatIndex, String formatString) { + return formatRawCellContents(value, formatIndex, formatString, false); + } + + /** + * Formats the given raw cell value, based on the supplied format index and string, according to excel style rules. + * + * @see #formatCellValue(Cell) + */ + public String formatRawCellContents(double value, int formatIndex, String formatString, boolean use1904Windowing) { + localeChangedObservable.checkForLocaleChange(); + + // Is it a date? + if (DateUtil.isADateFormat(formatIndex, formatString)) { + if (DateUtil.isValidExcelDate(value)) { + Format dateFormat = getFormat(value, formatIndex, formatString); + if (dateFormat instanceof ExcelStyleDateFormatter) { + // Hint about the raw excel value + ((ExcelStyleDateFormatter)dateFormat).setDateToBeFormatted(value); + } + Date d = DateUtil.getJavaDate(value, use1904Windowing); + return performDateFormatting(d, dateFormat); + } + // RK: Invalid dates are 255 #s. + if (emulateCSV) { + return invalidDateTimeString; + } + } + + // else Number + Format numberFormat = getFormat(value, formatIndex, formatString); + if (numberFormat == null) { + return String.valueOf(value); + } + + // When formatting 'value', double to text to BigDecimal produces more + // accurate results than double to Double in JDK8 (as compared to + // previous versions). However, if the value contains E notation, this + // would expand the values, which we do not want, so revert to + // original method. + String result; + final String textValue = NumberToTextConverter.toText(value); + if (textValue.indexOf('E') > -1) { + result = numberFormat.format(new Double(value)); + } else { + result = numberFormat.format(new BigDecimal(textValue)); + } + // Complete scientific notation by adding the missing +. + if (result.indexOf('E') > -1 && !result.contains("E-")) { + result = result.replaceFirst("E", "E+"); + } + return result; + } + + /** + *

+ * Returns the formatted value of a cell as a String regardless of the cell type. If the Excel format + * pattern cannot be parsed then the cell value will be formatted using a default format. + *

+ *

+ * When passed a null or blank cell, this method will return an empty String (""). Formulas in formula type cells + * will not be evaluated. + *

+ * + * @param cell + * The cell + * @return the formatted cell value as a String + */ + public String formatCellValue(Cell cell) { + return formatCellValue(cell, null); + } + + /** + *

+ * Returns the formatted value of a cell as a String regardless of the cell type. If the Excel number + * format pattern cannot be parsed then the cell value will be formatted using a default format. + *

+ *

+ * When passed a null or blank cell, this method will return an empty String (""). Formula cells will be evaluated + * using the given {@link FormulaEvaluator} if the evaluator is non-null. If the evaluator is null, then the formula + * String will be returned. The caller is responsible for setting the currentRow on the evaluator + *

+ * + * @param cell + * The cell (can be null) + * @param evaluator + * The FormulaEvaluator (can be null) + * @return a string value of the cell + */ + public String formatCellValue(Cell cell, FormulaEvaluator evaluator) { + return formatCellValue(cell, evaluator, null); + } + + /** + *

+ * Returns the formatted value of a cell as a String regardless of the cell type. If the Excel number + * format pattern cannot be parsed then the cell value will be formatted using a default format. + *

+ *

+ * When passed a null or blank cell, this method will return an empty String (""). Formula cells will be evaluated + * using the given {@link FormulaEvaluator} if the evaluator is non-null. If the evaluator is null, then the formula + * String will be returned. The caller is responsible for setting the currentRow on the evaluator + *

+ *

+ * When a ConditionalFormattingEvaluator is present, it is checked first to see if there is a number format to + * apply. If multiple rules apply, the last one is used. If no ConditionalFormattingEvaluator is present, no rules + * apply, or the applied rules do not define a format, the cell's style format is used. + *

+ *

+ * The two evaluators should be from the same context, to avoid inconsistencies in cached values. + *

+ * + * @param cell + * The cell (can be null) + * @param evaluator + * The FormulaEvaluator (can be null) + * @param cfEvaluator + * ConditionalFormattingEvaluator (can be null) + * @return a string value of the cell + */ + public String formatCellValue(Cell cell, FormulaEvaluator evaluator, ConditionalFormattingEvaluator cfEvaluator) { + localeChangedObservable.checkForLocaleChange(); + + if (cell == null) { + return ""; + } + + CellType cellType = cell.getCellTypeEnum(); + if (cellType == CellType.FORMULA) { + if (evaluator == null) { + return cell.getCellFormula(); + } + cellType = evaluator.evaluateFormulaCellEnum(cell); + } + switch (cellType) { + case NUMERIC: + +// if (DateUtil.isCellDateFormatted(cell, cfEvaluator)) { + return getFormattedDateString(cell, cfEvaluator); +// } +// return getFormattedNumberString(cell, cfEvaluator); + + case STRING: + return cell.getRichStringCellValue().getString(); + + case BOOLEAN: + return cell.getBooleanCellValue() ? "TRUE" : "FALSE"; + case BLANK: + return ""; + case ERROR: + return FormulaError.forInt(cell.getErrorCellValue()).getString(); + default: + throw new RuntimeException("Unexpected celltype (" + cellType + ")"); + } + } + + /** + *

+ * Sets a default number format to be used when the Excel format cannot be parsed successfully. Note: This is + * a fall back for when an error occurs while parsing an Excel number format pattern. This will not affect cells + * with the General format. + *

+ *

+ * The value that will be passed to the Format's format method (specified by java.text.Format#format) + * will be a double value from a numeric cell. Therefore the code in the format method should expect a + * Number value. + *

+ * + * @param format + * A Format instance to be used as a default + * @see java.text.Format#format + */ + public void setDefaultNumberFormat(Format format) { + for (Map.Entry entry : formats.entrySet()) { + if (entry.getValue() == generalNumberFormat) { + entry.setValue(format); + } + } + defaultNumFormat = format; + } + + /** + * Adds a new format to the available formats. + *

+ * The value that will be passed to the Format's format method (specified by java.text.Format#format) + * will be a double value from a numeric cell. Therefore the code in the format method should expect a + * Number value. + *

+ * + * @param excelFormatStr + * The data format string + * @param format + * A Format instance + */ + public void addFormat(String excelFormatStr, Format format) { + formats.put(excelFormatStr, format); + } + + // Some custom formats + + /** + * @return a DecimalFormat with parseIntegerOnly set true + */ + private static DecimalFormat createIntegerOnlyFormat(String fmt) { + DecimalFormatSymbols dsf = DecimalFormatSymbols.getInstance(Locale.ROOT); + DecimalFormat result = new DecimalFormat(fmt, dsf); + result.setParseIntegerOnly(true); + return result; + } + + /** + * Enables excel style rounding mode (round half up) on the Decimal Format given. + */ + public static void setExcelStyleRoundingMode(DecimalFormat format) { + setExcelStyleRoundingMode(format, RoundingMode.HALF_UP); + } + + /** + * Enables custom rounding mode on the given Decimal Format. + * + * @param format + * DecimalFormat + * @param roundingMode + * RoundingMode + */ + public static void setExcelStyleRoundingMode(DecimalFormat format, RoundingMode roundingMode) { + format.setRoundingMode(roundingMode); + } + + /** + * If the Locale has been changed via {@link LocaleUtil#setUserLocale(Locale)} the stored formats need to be + * refreshed. All formats which aren't originated from DataFormatter itself, i.e. all Formats added via + * {@link DataFormatter#addFormat(String, Format)} and {@link DataFormatter#setDefaultNumberFormat(Format)}, need to + * be added again. To notify callers, the returned {@link Observable} should be used. The Object in + * {@link Observer#update(Observable, Object)} is the new Locale. + * + * @return the listener object, where callers can register themselves + */ + public Observable getLocaleChangedObservable() { + return localeChangedObservable; + } + + /** + * Update formats when locale has been changed + * + * @param observable + * usually this is our own Observable instance + * @param localeObj + * only reacts on Locale objects + */ + public void update(Observable observable, Object localeObj) { + if (!(localeObj instanceof Locale)) + return; + Locale newLocale = (Locale)localeObj; + if (!localeIsAdapting || newLocale.equals(locale)) + return; + + locale = newLocale; + + dateSymbols = DateFormatSymbols.getInstance(locale); + decimalSymbols = DecimalFormatSymbols.getInstance(locale); + generalNumberFormat = new ExcelGeneralNumberFormat(locale); + + // taken from Date.toString() + defaultDateformat = new SimpleDateFormat("EEE MMM dd HH:mm:ss zzz yyyy", dateSymbols); + defaultDateformat.setTimeZone(LocaleUtil.getUserTimeZone()); + + // init built-in formats + + formats.clear(); + Format zipFormat = ZipPlusFourFormat.instance; + addFormat("00000\\-0000", zipFormat); + addFormat("00000-0000", zipFormat); + + Format phoneFormat = PhoneFormat.instance; + // allow for format string variations + addFormat("[<=9999999]###\\-####;\\(###\\)\\ ###\\-####", phoneFormat); + addFormat("[<=9999999]###-####;(###) ###-####", phoneFormat); + addFormat("###\\-####;\\(###\\)\\ ###\\-####", phoneFormat); + addFormat("###-####;(###) ###-####", phoneFormat); + + Format ssnFormat = SSNFormat.instance; + addFormat("000\\-00\\-0000", ssnFormat); + addFormat("000-00-0000", ssnFormat); + } + + /** + * Format class for Excel's SSN format. This class mimics Excel's built-in SSN formatting. + * + * @author James May + */ + @SuppressWarnings("serial") + private static final class SSNFormat extends Format { + public static final Format instance = new SSNFormat(); + private static final DecimalFormat df = createIntegerOnlyFormat("000000000"); + + private SSNFormat() { + // enforce singleton + } + + /** Format a number as an SSN */ + public static String format(Number num) { + String result = df.format(num); + return result.substring(0, 3) + '-' + result.substring(3, 5) + '-' + result.substring(5, 9); + } + + @Override + public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos) { + return toAppendTo.append(format((Number)obj)); + } + + @Override + public Object parseObject(String source, ParsePosition pos) { + return df.parseObject(source, pos); + } + } + + /** + * Format class for Excel Zip + 4 format. This class mimics Excel's built-in formatting for Zip + 4. + * + * @author James May + */ + @SuppressWarnings("serial") + private static final class ZipPlusFourFormat extends Format { + public static final Format instance = new ZipPlusFourFormat(); + private static final DecimalFormat df = createIntegerOnlyFormat("000000000"); + + private ZipPlusFourFormat() { + // enforce singleton + } + + /** Format a number as Zip + 4 */ + public static String format(Number num) { + String result = df.format(num); + return result.substring(0, 5) + '-' + result.substring(5, 9); + } + + @Override + public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos) { + return toAppendTo.append(format((Number)obj)); + } + + @Override + public Object parseObject(String source, ParsePosition pos) { + return df.parseObject(source, pos); + } + } + + /** + * Format class for Excel phone number format. This class mimics Excel's built-in phone number formatting. + * + * @author James May + */ + @SuppressWarnings("serial") + private static final class PhoneFormat extends Format { + public static final Format instance = new PhoneFormat(); + private static final DecimalFormat df = createIntegerOnlyFormat("##########"); + + private PhoneFormat() { + // enforce singleton + } + + /** Format a number as a phone number */ + public static String format(Number num) { + String result = df.format(num); + StringBuilder sb = new StringBuilder(); + String seg1, seg2, seg3; + int len = result.length(); + if (len <= 4) { + return result; + } + + seg3 = result.substring(len - 4, len); + seg2 = result.substring(Math.max(0, len - 7), len - 4); + seg1 = result.substring(Math.max(0, len - 10), Math.max(0, len - 7)); + + if (seg1.trim().length() > 0) { + sb.append('(').append(seg1).append(") "); + } + if (seg2.trim().length() > 0) { + sb.append(seg2).append('-'); + } + sb.append(seg3); + return sb.toString(); + } + + @Override + public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos) { + return toAppendTo.append(format((Number)obj)); + } + + @Override + public Object parseObject(String source, ParsePosition pos) { + return df.parseObject(source, pos); + } + } + + /** + * Format class that does nothing and always returns a constant string. + * + * This format is used to simulate Excel's handling of a format string of all # when the value is 0. Excel will + * output "", Java will output "0". + * + */ + @SuppressWarnings("serial") + private static final class ConstantStringFormat extends Format { + private static final DecimalFormat df = createIntegerOnlyFormat("##########"); + private final String str; + + public ConstantStringFormat(String s) { + str = s; + } + + @Override + public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos) { + return toAppendTo.append(str); + } + + @Override + public Object parseObject(String source, ParsePosition pos) { + return df.parseObject(source, pos); + } + } + + /** + * Workaround until we merge {@link DataFormatter} with {@link CellFormat}. Constant, non-cachable wrapper around a + * {@link CellFormatResult} + */ + @SuppressWarnings("serial") + private final class CellFormatResultWrapper extends Format { + private final CellFormatResult result; + + private CellFormatResultWrapper(CellFormatResult result) { + this.result = result; + } + + public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos) { + if (emulateCSV) { + return toAppendTo.append(result.text); + } else { + return toAppendTo.append(result.text.trim()); + } + } + + public Object parseObject(String source, ParsePosition pos) { + return null; // Not supported + } + } +} diff --git a/src/test/java/com/alibaba/easyexcel/test/temp/simple/Wirte.java b/src/test/java/com/alibaba/easyexcel/test/temp/simple/Wirte.java index b19ac81a..44bc943f 100644 --- a/src/test/java/com/alibaba/easyexcel/test/temp/simple/Wirte.java +++ b/src/test/java/com/alibaba/easyexcel/test/temp/simple/Wirte.java @@ -64,7 +64,7 @@ public class Wirte { List list = new ArrayList(); for (int i = 0; i < 10; i++) { DemoData data = new DemoData(); - data.setString("字符串" + i); + data.setString("640121807369666560" + i); data.setDate(new Date()); data.setDoubleData(null); list.add(data);