From 7a2c5634d03befa0e1817251cec6873c7e83f1e1 Mon Sep 17 00:00:00 2001 From: Jiaju Zhuang Date: Fri, 10 Sep 2021 20:47:20 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E6=88=90csv=E8=BD=AC=E6=8D=A2?= =?UTF-8?q?=E4=B9=A6=E5=86=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../excel/enums/NumericCellTypeEnum.java | 22 ++++ .../alibaba/excel/metadata/csv/CsvCell.java | 40 +++++-- .../excel/metadata/csv/CsvCellStyle.java | 8 +- .../alibaba/excel/metadata/csv/CsvSheet.java | 45 ++++--- .../excel/metadata/csv/CsvWorkbook.java | 20 +++- .../excel/metadata/data/DataFormatData.java | 4 - .../excel/metadata/format/DataFormatter.java | 112 ++++++++++-------- .../excel/util/NumberDataFormatterUtils.java | 25 +++- .../com/alibaba/excel/util/WorkBookUtil.java | 4 +- 9 files changed, 190 insertions(+), 90 deletions(-) create mode 100644 src/main/java/com/alibaba/excel/enums/NumericCellTypeEnum.java diff --git a/src/main/java/com/alibaba/excel/enums/NumericCellTypeEnum.java b/src/main/java/com/alibaba/excel/enums/NumericCellTypeEnum.java new file mode 100644 index 00000000..22001f13 --- /dev/null +++ b/src/main/java/com/alibaba/excel/enums/NumericCellTypeEnum.java @@ -0,0 +1,22 @@ +package com.alibaba.excel.enums; + +import org.apache.poi.ss.usermodel.CellType; + +/** + * Used to supplement {@link CellType}. + * + * Cannot distinguish between date and number in write case. + * + * @author Jiaju Zhuang + */ +public enum NumericCellTypeEnum { + /** + * number + */ + NUMBER, + /** + * date. Support only when writing. + */ + DATE, + ; +} diff --git a/src/main/java/com/alibaba/excel/metadata/csv/CsvCell.java b/src/main/java/com/alibaba/excel/metadata/csv/CsvCell.java index a434758e..f0e925e6 100644 --- a/src/main/java/com/alibaba/excel/metadata/csv/CsvCell.java +++ b/src/main/java/com/alibaba/excel/metadata/csv/CsvCell.java @@ -18,7 +18,6 @@ import org.apache.poi.ss.usermodel.CellBase; import org.apache.poi.ss.usermodel.CellStyle; import org.apache.poi.ss.usermodel.CellType; import org.apache.poi.ss.usermodel.Comment; -import org.apache.poi.ss.usermodel.DateUtil; import org.apache.poi.ss.usermodel.Hyperlink; import org.apache.poi.ss.usermodel.RichTextString; import org.apache.poi.ss.usermodel.Row; @@ -47,6 +46,11 @@ public class CsvCell extends CellBase { @Setter(value = AccessLevel.NONE) private CellType cellType; + /** + * numeric cell type + */ + private NumericCellTypeEnum numericCellType; + /** * workbook */ @@ -65,7 +69,7 @@ public class CsvCell extends CellBase { /** * {@link CellType#NUMERIC} */ - private Double numberValue; + private BigDecimal numberValue; /** * {@link CellType#STRING} and {@link CellType#ERROR} {@link CellType#FORMULA} */ @@ -75,6 +79,11 @@ public class CsvCell extends CellBase { */ private Boolean booleanValue; + /** + * {@link CellType#NUMERIC} + */ + private LocalDateTime dateValue; + /** * formula */ @@ -121,25 +130,33 @@ public class CsvCell extends CellBase { @Override protected void setCellValueImpl(double value) { - this.numberValue = value; + numberValue = BigDecimal.valueOf(value); this.cellType = CellType.NUMERIC; } @Override protected void setCellValueImpl(Date value) { - this.numberValue = DateUtil.getExcelDate(value, csvWorkbook.getUse1904windowing()); + if (value == null) { + return; + } + this.dateValue = LocalDateTime.ofInstant(value.toInstant(), ZoneId.systemDefault()); this.cellType = CellType.NUMERIC; + this.numericCellType = NumericCellTypeEnum.DATE; } @Override protected void setCellValueImpl(LocalDateTime value) { - this.numberValue = DateUtil.getExcelDate(value, csvWorkbook.getUse1904windowing()); + this.dateValue = value; this.cellType = CellType.NUMERIC; + this.numericCellType = NumericCellTypeEnum.DATE; } @Override protected void setCellValueImpl(Calendar value) { - this.numberValue = DateUtil.getExcelDate(value, csvWorkbook.getUse1904windowing()); + if (value == null) { + return; + } + this.dateValue = LocalDateTime.ofInstant(value.toInstant(), ZoneId.systemDefault()); this.cellType = CellType.NUMERIC; } @@ -231,18 +248,15 @@ public class CsvCell extends CellBase { @Override public Date getDateCellValue() { - if (numberValue == null) { + if (dateValue == null) { return null; } - return DateUtil.getJavaDate(numberValue, csvWorkbook.getUse1904windowing()); + return Date.from(dateValue.atZone(ZoneId.systemDefault()).toInstant()); } @Override public LocalDateTime getLocalDateTimeCellValue() { - if (numberValue == null) { - return null; - } - return DateUtil.getLocalDateTime(numberValue, csvWorkbook.getUse1904windowing()); + return dateValue; } @Override @@ -263,7 +277,7 @@ public class CsvCell extends CellBase { @Override public void setCellErrorValue(byte value) { - this.numberValue = value; + this.numberValue = BigDecimal.valueOf(value); this.cellType = CellType.ERROR; } diff --git a/src/main/java/com/alibaba/excel/metadata/csv/CsvCellStyle.java b/src/main/java/com/alibaba/excel/metadata/csv/CsvCellStyle.java index ca153e45..08b46e3a 100644 --- a/src/main/java/com/alibaba/excel/metadata/csv/CsvCellStyle.java +++ b/src/main/java/com/alibaba/excel/metadata/csv/CsvCellStyle.java @@ -52,13 +52,17 @@ public class CsvCellStyle implements CellStyle { @Override public short getDataFormat() { - initDataFormatData(); + if (dataFormatData == null) { + return 0; + } return dataFormatData.getIndex(); } @Override public String getDataFormatString() { - initDataFormatData(); + if (dataFormatData == null) { + return null; + } return dataFormatData.getFormat(); } diff --git a/src/main/java/com/alibaba/excel/metadata/csv/CsvSheet.java b/src/main/java/com/alibaba/excel/metadata/csv/CsvSheet.java index 0b203804..14c6a207 100644 --- a/src/main/java/com/alibaba/excel/metadata/csv/CsvSheet.java +++ b/src/main/java/com/alibaba/excel/metadata/csv/CsvSheet.java @@ -7,9 +7,9 @@ import java.util.Iterator; import java.util.List; import java.util.Map; +import com.alibaba.excel.constant.BuiltinFormats; import com.alibaba.excel.enums.NumericCellTypeEnum; import com.alibaba.excel.exception.ExcelGenerateException; -import com.alibaba.excel.metadata.data.WriteCellData; import com.alibaba.excel.util.DateUtils; import com.alibaba.excel.util.ListUtils; import com.alibaba.excel.util.NumberDataFormatterUtils; @@ -25,6 +25,7 @@ import org.apache.poi.ss.usermodel.CellStyle; import org.apache.poi.ss.usermodel.Comment; import org.apache.poi.ss.usermodel.DataValidation; import org.apache.poi.ss.usermodel.DataValidationHelper; +import org.apache.poi.ss.usermodel.DateUtil; import org.apache.poi.ss.usermodel.Drawing; import org.apache.poi.ss.usermodel.Footer; import org.apache.poi.ss.usermodel.Header; @@ -45,7 +46,6 @@ import org.apache.poi.ss.util.PaneInformation; */ @Data public class CsvSheet implements Sheet, Closeable { - /** * workbook */ @@ -747,21 +747,40 @@ public class CsvSheet implements Sheet, Closeable { case FORMULA: return csvCell.getStringCellValue(); case NUMERIC: - - NumberDataFormatterUtils.format() - + Short dataFormat = null; + String dataFormatString = null; + if (csvCell.getCellStyle() != null) { + dataFormat = csvCell.getCellStyle().getDataFormat(); + dataFormatString = csvCell.getCellStyle().getDataFormatString(); + } if (csvCell.getNumericCellType() == NumericCellTypeEnum.DATE) { + if (csvCell.getDateValue() == null) { + return null; + } // date - if (contentProperty == null || contentProperty.getDateTimeFormatProperty() == null) { - return new WriteCellData<>(DateUtils.format(value, null)); - } else { - return new WriteCellData<>( - DateUtils.format(value, contentProperty.getDateTimeFormatProperty().getFormat())); + if (dataFormat == null) { + dataFormatString = DateUtils.defaultDateFormat; + dataFormat = csvWorkbook.createDataFormat().getFormat(dataFormatString); } - + if (dataFormatString == null) { + dataFormatString = csvWorkbook.createDataFormat().getFormat(dataFormat); + } + return NumberDataFormatterUtils.format( + DateUtil.getExcelDate(csvCell.getDateValue(), csvWorkbook.getUse1904windowing()), + dataFormat, dataFormatString, csvWorkbook.getUse1904windowing(), csvWorkbook.getLocale(), + csvWorkbook.getUseScientificFormat()); } else { + if (csvCell.getNumberValue() == null) { + return null; + } //number - + if (dataFormat == null) { + dataFormat = BuiltinFormats.GENERAL; + dataFormatString = csvWorkbook.createDataFormat().getFormat(dataFormat); + } + return NumberDataFormatterUtils.format(csvCell.getNumericCellValue(), dataFormat, dataFormatString, + csvWorkbook.getUse1904windowing(), csvWorkbook.getLocale(), + csvWorkbook.getUseScientificFormat()); } case BOOLEAN: return csvCell.getBooleanValue().toString(); @@ -772,6 +791,4 @@ public class CsvSheet implements Sheet, Closeable { } } - private String - } diff --git a/src/main/java/com/alibaba/excel/metadata/csv/CsvWorkbook.java b/src/main/java/com/alibaba/excel/metadata/csv/CsvWorkbook.java index ba55e01f..e26a2b65 100644 --- a/src/main/java/com/alibaba/excel/metadata/csv/CsvWorkbook.java +++ b/src/main/java/com/alibaba/excel/metadata/csv/CsvWorkbook.java @@ -32,10 +32,7 @@ public class CsvWorkbook implements Workbook { * output */ private Appendable out; - /** - * locale - */ - private Locale locale; + /** * true if date uses 1904 windowing, or false if using 1900 date windowing. * @@ -45,6 +42,18 @@ public class CsvWorkbook implements Workbook { */ private Boolean use1904windowing; + /** + * locale + */ + private Locale locale; + + /** + * Whether to use scientific Format. + * + * default is false + */ + private Boolean useScientificFormat; + /** * data format */ @@ -58,10 +67,11 @@ public class CsvWorkbook implements Workbook { */ private List csvCellStyleList; - public CsvWorkbook(Appendable out, Locale locale, Boolean use1904windowing) { + public CsvWorkbook(Appendable out, Locale locale, Boolean use1904windowing, Boolean useScientificFormat) { this.out = out; this.locale = locale; this.use1904windowing = use1904windowing; + this.useScientificFormat = useScientificFormat; } @Override diff --git a/src/main/java/com/alibaba/excel/metadata/data/DataFormatData.java b/src/main/java/com/alibaba/excel/metadata/data/DataFormatData.java index 2cbcb42c..7f1a14c8 100644 --- a/src/main/java/com/alibaba/excel/metadata/data/DataFormatData.java +++ b/src/main/java/com/alibaba/excel/metadata/data/DataFormatData.java @@ -19,10 +19,6 @@ public class DataFormatData { */ private String format; - public DataFormatData() { - this.index = 0; - } - @Override public DataFormatData clone() { DataFormatData dataFormatData = new DataFormatData(); diff --git a/src/main/java/com/alibaba/excel/metadata/format/DataFormatter.java b/src/main/java/com/alibaba/excel/metadata/format/DataFormatter.java index e9266c71..aebbf4fc 100644 --- a/src/main/java/com/alibaba/excel/metadata/format/DataFormatter.java +++ b/src/main/java/com/alibaba/excel/metadata/format/DataFormatter.java @@ -34,6 +34,8 @@ import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; +import com.alibaba.excel.util.DateUtils; + import org.apache.poi.ss.format.CellFormat; import org.apache.poi.ss.format.CellFormatResult; import org.apache.poi.ss.usermodel.DateUtil; @@ -42,9 +44,6 @@ import org.apache.poi.ss.usermodel.FractionFormat; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.alibaba.excel.metadata.GlobalConfiguration; -import com.alibaba.excel.util.DateUtils; - /** * Written with reference to {@link org.apache.poi.ss.usermodel.DataFormatter}.Made some optimizations for date * conversion. @@ -54,21 +53,31 @@ import com.alibaba.excel.util.DateUtils; * @author Jiaju Zhuang */ public class DataFormatter { - /** For logging any problems we find */ + /** + * For logging any problems we find + */ private static final Logger LOGGER = LoggerFactory.getLogger(DataFormatter.class); private static final String defaultFractionWholePartFormat = "#"; private static final String defaultFractionFractionPartFormat = "#/##"; - /** Pattern to find a number format: "0" or "#" */ + /** + * 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...." */ + /** + * 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 */ + /** + * Pattern to find "AM/PM" marker + */ private static final Pattern amPmPattern = Pattern.compile("(([AP])[M/P]*)|(([上下])[午/下]*)", Pattern.CASE_INSENSITIVE); - /** Pattern to find formats with condition ranges e.g. [>=100] */ + /** + * Pattern to find formats with condition ranges e.g. [>=100] + */ private static final Pattern rangeConditionalPattern = Pattern.compile(".*\\[\\s*(>|>=|<|<=|=)\\s*[0-9]*\\.*[0-9].*"); @@ -107,10 +116,10 @@ public class DataFormatter { * ("#"). */ private static final String invalidDateTimeString; + static { StringBuilder buf = new StringBuilder(); - for (int i = 0; i < 255; i++) - buf.append('#'); + for (int i = 0; i < 255; i++) {buf.append('#');} invalidDateTimeString = buf.toString(); } @@ -123,14 +132,18 @@ public class DataFormatter { * The date symbols of the locale used for formatting values. */ private DateFormatSymbols dateSymbols; - /** A default format to use when a number pattern cannot be parsed. */ + /** + * 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(); - /** stores the locale valid it the last formatting call */ + /** + * stores the locale valid it the last formatting call + */ private Locale locale; /** * true if date uses 1904 windowing, or false if using 1900 date windowing. @@ -149,28 +162,31 @@ public class DataFormatter { /** * Creates a formatter using the given locale. - * */ - public DataFormatter(GlobalConfiguration globalConfiguration) { - if (globalConfiguration == null) { + public DataFormatter(Boolean use1904windowing, Locale locale, Boolean useScientificFormat) { + if (use1904windowing == null) { this.use1904windowing = Boolean.FALSE; + } else { + this.use1904windowing = use1904windowing; + } + + if (locale == null) { this.locale = Locale.getDefault(); + } else { + this.locale = locale; + } + + if (use1904windowing == null) { this.useScientificFormat = Boolean.FALSE; } else { - this.use1904windowing = - globalConfiguration.getUse1904windowing() != null ? globalConfiguration.getUse1904windowing() - : Boolean.FALSE; - this.locale = - globalConfiguration.getLocale() != null ? globalConfiguration.getLocale() : Locale.getDefault(); - this.useScientificFormat = - globalConfiguration.getUseScientificFormat() != null ? globalConfiguration.getUseScientificFormat() - : Boolean.FALSE; + this.useScientificFormat = useScientificFormat; } + this.dateSymbols = DateFormatSymbols.getInstance(this.locale); this.decimalSymbols = DecimalFormatSymbols.getInstance(this.locale); } - private Format getFormat(Double data,Short dataFormat, String dataFormatString) { + private Format getFormat(Double data, Short dataFormat, String dataFormatString) { // 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. @@ -188,7 +204,7 @@ public class DataFormatter { 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); @@ -200,9 +216,9 @@ public class DataFormatter { cellValueO = DateUtil.getJavaDate(data, use1904windowing); } // Wrap and return (non-cachable - CellFormat does that) - return new CellFormatResultWrapper( cfmt.apply(cellValueO) ); + return new CellFormatResultWrapper(cfmt.apply(cellValueO)); } catch (Exception e) { - LOGGER.warn("Formatting failed for format {}, falling back",formatStr, e); + LOGGER.warn("Formatting failed for format {}, falling back", formatStr, e); } } @@ -240,11 +256,9 @@ public class DataFormatter { // Paranoid replacement... int at = formatStr.indexOf(colour); - if (at == -1) - break; + if (at == -1) {break;} String nFormatStr = formatStr.substring(0, at) + formatStr.substring(at + colour.length()); - if (nFormatStr.equals(formatStr)) - break; + if (nFormatStr.equals(formatStr)) {break;} // Try again in case there's multiple formatStr = nFormatStr; @@ -601,8 +615,7 @@ public class DataFormatter { * 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 data - * to format + * @param data to format * @param dataFormat * @param dataFormatString * @return Formatted value @@ -624,8 +637,7 @@ public class DataFormatter { * Format comes from either the highest priority conditional format rule with a specified format, or from the cell * style. * - * @param data - * to format + * @param data to format * @param dataFormat * @param dataFormatString * @return a formatted number string @@ -663,8 +675,7 @@ public class DataFormatter { * Number value. *

* - * @param format - * A Format instance to be used as a default + * @param format A Format instance to be used as a default * @see Format#format */ public void setDefaultNumberFormat(Format format) { @@ -684,10 +695,8 @@ public class DataFormatter { * Number value. *

* - * @param excelFormatStr - * The data format string - * @param format - * A Format instance + * @param excelFormatStr The data format string + * @param format A Format instance */ public void addFormat(String excelFormatStr, Format format) { formats.put(excelFormatStr, format); @@ -715,10 +724,8 @@ public class DataFormatter { /** * Enables custom rounding mode on the given Decimal Format. * - * @param format - * DecimalFormat - * @param roundingMode - * RoundingMode + * @param format DecimalFormat + * @param roundingMode RoundingMode */ public static void setExcelStyleRoundingMode(DecimalFormat format, RoundingMode roundingMode) { format.setRoundingMode(roundingMode); @@ -737,7 +744,9 @@ public class DataFormatter { // enforce singleton } - /** Format a number as an SSN */ + /** + * 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); @@ -767,7 +776,9 @@ public class DataFormatter { // enforce singleton } - /** Format a number as Zip + 4 */ + /** + * 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); @@ -797,7 +808,9 @@ public class DataFormatter { // enforce singleton } - /** Format a number as a phone number */ + /** + * Format a number as a phone number + */ public static String format(Number num) { String result = df.format(num); StringBuilder sb = new StringBuilder(); @@ -833,7 +846,8 @@ public class DataFormatter { } /** - * Workaround until we merge {@link org.apache.poi.ss.usermodel.DataFormatter} with {@link CellFormat}. Constant, non-cachable wrapper around a + * Workaround until we merge {@link org.apache.poi.ss.usermodel.DataFormatter} with {@link CellFormat}. Constant, + * non-cachable wrapper around a * {@link CellFormatResult} */ private final class CellFormatResultWrapper extends Format { diff --git a/src/main/java/com/alibaba/excel/util/NumberDataFormatterUtils.java b/src/main/java/com/alibaba/excel/util/NumberDataFormatterUtils.java index de01ad54..f9a8ee23 100644 --- a/src/main/java/com/alibaba/excel/util/NumberDataFormatterUtils.java +++ b/src/main/java/com/alibaba/excel/util/NumberDataFormatterUtils.java @@ -1,5 +1,7 @@ package com.alibaba.excel.util; +import java.util.Locale; + import com.alibaba.excel.metadata.format.DataFormatter; import com.alibaba.excel.metadata.GlobalConfiguration; @@ -26,13 +28,32 @@ public class NumberDataFormatterUtils { */ public static String format(Double data, Short dataFormat, String dataFormatString, GlobalConfiguration globalConfiguration) { + if (globalConfiguration == null) { + return format(data, dataFormat, dataFormatString, null, null, null); + } + return format(data, dataFormat, dataFormatString, globalConfiguration.getUse1904windowing(), + globalConfiguration.getLocale(), globalConfiguration.getUseScientificFormat()); + } + + /** + * Format number data. + * + * @param data + * @param dataFormat Not null. + * @param dataFormatString + * @param use1904windowing + * @param locale + * @param useScientificFormat + * @return + */ + public static String format(Double data, Short dataFormat, String dataFormatString, Boolean use1904windowing, + Locale locale, Boolean useScientificFormat) { DataFormatter dataFormatter = DATA_FORMATTER_THREAD_LOCAL.get(); if (dataFormatter == null) { - dataFormatter = new DataFormatter(globalConfiguration); + dataFormatter = new DataFormatter(use1904windowing, locale, useScientificFormat); DATA_FORMATTER_THREAD_LOCAL.set(dataFormatter); } return dataFormatter.format(data, dataFormat, dataFormatString); - } public static void removeThreadLocalCache() { diff --git a/src/main/java/com/alibaba/excel/util/WorkBookUtil.java b/src/main/java/com/alibaba/excel/util/WorkBookUtil.java index a7a3d74a..3e9732a9 100644 --- a/src/main/java/com/alibaba/excel/util/WorkBookUtil.java +++ b/src/main/java/com/alibaba/excel/util/WorkBookUtil.java @@ -69,7 +69,9 @@ public class WorkBookUtil { case CSV: CsvWorkbook csvWorkbook = new CsvWorkbook( new OutputStreamWriter(writeWorkbookHolder.getOutputStream(), writeWorkbookHolder.getCharset()), - writeWorkbookHolder.getGlobalConfiguration().getLocale(), writeWorkbookHolder.getGlobalConfiguration().getUse1904windowing()); + writeWorkbookHolder.getGlobalConfiguration().getLocale(), + writeWorkbookHolder.getGlobalConfiguration().getUse1904windowing(), + writeWorkbookHolder.getGlobalConfiguration().getUseScientificFormat()); writeWorkbookHolder.setCachedWorkbook(csvWorkbook); writeWorkbookHolder.setWorkbook(csvWorkbook); return;