Looly 5 年 前
コミット
3081e25a4d

+ 2 - 1
CHANGELOG.md

@@ -3,7 +3,7 @@
 
 -------------------------------------------------------------------------------------------------------------
 
-# 5.5.0 (2020-11-08)
+# 5.5.0 (2020-11-09)
 
 ### 新特性
 * 【core   】     NumberUtil.parseInt等支持123,2.00这类数字(issue#I23ORQ@Gitee)
@@ -20,6 +20,7 @@
 * 【core   】     修复ClassUtil.getTypeArgument方法在判断泛型时导致的问题(issue#1207@Github)
 * 【core   】     修复Ipv4Util分隔符问题(issue#I24A9I@Gitee)
 * 【core   】     修复Ipv4Util.longToIp的问题
+* 【poi    】     修复Excel07SaxReader读取公式的错误的问题(issue#I23VFL@Gitee)
 
 -------------------------------------------------------------------------------------------------------------
 

+ 3 - 3
hutool-core/src/main/java/cn/hutool/core/text/StrBuilder.java

@@ -33,7 +33,7 @@ public class StrBuilder implements CharSequence, Appendable, Serializable {
 	/**
 	 * 创建字符串构建器
 	 *
-	 * @return {@link StrBuilder}
+	 * @return this
 	 */
 	public static StrBuilder create() {
 		return new StrBuilder();
@@ -43,7 +43,7 @@ public class StrBuilder implements CharSequence, Appendable, Serializable {
 	 * 创建字符串构建器
 	 *
 	 * @param initialCapacity 初始容量
-	 * @return {@link StrBuilder}
+	 * @return this
 	 */
 	public static StrBuilder create(int initialCapacity) {
 		return new StrBuilder(initialCapacity);
@@ -53,7 +53,7 @@ public class StrBuilder implements CharSequence, Appendable, Serializable {
 	 * 创建字符串构建器
 	 *
 	 * @param strs 初始字符串
-	 * @return {@link StrBuilder}
+	 * @return this
 	 * @since 4.0.1
 	 */
 	public static StrBuilder create(CharSequence... strs) {

+ 36 - 2
hutool-poi/src/main/java/cn/hutool/poi/excel/cell/FormulaCellValue.java

@@ -2,17 +2,39 @@ package cn.hutool.poi.excel.cell;
 
 /**
  * 公式类型的值
- * 
+ *
  * @author looly
  * @since 4.0.11
  */
 public class FormulaCellValue implements CellValue<String> {
 
-	/** 公式 */
+	/**
+	 * 公式
+	 */
 	String formula;
+	/**
+	 * 结果,使用ExcelWriter时可以不用
+	 */
+	Object result;
 
+	/**
+	 * 构造
+	 *
+	 * @param formula 公式
+	 */
 	public FormulaCellValue(String formula) {
+		this(formula, null);
+	}
+
+	/**
+	 * 构造
+	 *
+	 * @param formula 公式
+	 * @param result  结果
+	 */
+	public FormulaCellValue(String formula, Object result) {
 		this.formula = formula;
+		this.result = result;
 	}
 
 	@Override
@@ -20,4 +42,16 @@ public class FormulaCellValue implements CellValue<String> {
 		return this.formula;
 	}
 
+	/**
+	 * 获取结果
+	 * @return 结果
+	 */
+	public Object getResult() {
+		return this.result;
+	}
+
+	@Override
+	public String toString() {
+		return getResult().toString();
+	}
 }

+ 3 - 3
hutool-poi/src/main/java/cn/hutool/poi/excel/sax/CellDataType.java

@@ -11,15 +11,15 @@ public enum CellDataType {
 	BOOL("b"),
 	/** 类型错误 */
 	ERROR("e"),
-	/** 计算结果类型 */
-	FORMULA("str"),
+	/** 计算结果类型,此类型使用f标签辅助判断,而非属性 */
+	FORMULA("formula"),
 	/** 富文本类型 */
 	INLINESTR("inlineStr"),
 	/** 共享字符串索引类型 */
 	SSTINDEX("s"),
 	/** 数字类型 */
 	NUMBER(""),
-	/** 日期类型 */
+	/** 日期类型,此类型使用值判断,而非属性 */
 	DATE("m/d/yy"),
 	/** 空类型 */
 	NULL("");

+ 23 - 2
hutool-poi/src/main/java/cn/hutool/poi/excel/sax/ElementName.java

@@ -12,9 +12,17 @@ public enum ElementName {
 	 */
 	row,
 	/**
-	 * 单元格标签名,表示一个单元格
+	 * Cell单元格标签名,表示一个单元格
 	 */
-	c;
+	c,
+	/**
+	 * Value单元格值的标签,表示单元格内的值
+	 */
+	v,
+	/**
+	 * Formula公式,表示一个存放公式的单元格
+	 */
+	f;
 
 	/**
 	 * 给定标签名是否匹配当前标签
@@ -25,4 +33,17 @@ public enum ElementName {
 	public boolean match(String elementName){
 		return this.name().equals(elementName);
 	}
+
+	/**
+	 * 解析支持的节点名枚举
+	 * @param elementName 节点名
+	 * @return 节点名枚举
+	 */
+	public static ElementName of(String elementName){
+		try {
+			return valueOf(elementName);
+		} catch (Exception ignore){
+		}
+		return null;
+	}
 }

+ 3 - 15
hutool-poi/src/main/java/cn/hutool/poi/excel/sax/Excel03SaxReader.java

@@ -275,10 +275,10 @@ public class Excel03SaxReader implements HSSFListener, ExcelSaxReader<Excel03Sax
 						// This is stored in the next record
 						isOutputNextStringRecord = true;
 					} else {
-						value = formatListener.formatNumberDateCell(formulaRec);
+						value = ExcelSaxUtil.getNumberOrDateValue(formulaRec, formulaRec.getValue(), this.formatListener);
 					}
 				} else {
-					value = StrUtil.wrap(HSSFFormulaParser.toFormulaString(stubWorkbook, formulaRec.getParsedExpression()), "\"");
+					value = HSSFFormulaParser.toFormulaString(stubWorkbook, formulaRec.getParsedExpression());
 				}
 				addToRowCellList(formulaRec, value);
 				break;
@@ -305,19 +305,7 @@ public class Excel03SaxReader implements HSSFListener, ExcelSaxReader<Excel03Sax
 				break;
 			case NumberRecord.sid: // 数字类型
 				final NumberRecord numrec = (NumberRecord) record;
-				if(ExcelSaxUtil.isDateFormat(numrec, formatListener)){
-					// 可能为日期格式
-					value = ExcelSaxUtil.getDateValue(numrec.getValue());
-				} else {
-					final double doubleValue = numrec.getValue();
-					final long longPart = (long) doubleValue;
-					// 对于无小数部分的数字类型,转为Long,否则保留原数字
-					if (((double) longPart) == doubleValue) {
-						value = longPart;
-					} else {
-						value = doubleValue;
-					}
-				}
+				value = ExcelSaxUtil.getNumberOrDateValue(numrec, numrec.getValue(), this.formatListener);
 				// 向容器加入列值
 				addToRowCellList(numrec, value);
 				break;

+ 37 - 8
hutool-poi/src/main/java/cn/hutool/poi/excel/sax/Excel07SaxReader.java

@@ -5,6 +5,7 @@ import cn.hutool.core.io.IoUtil;
 import cn.hutool.core.text.StrBuilder;
 import cn.hutool.core.util.NumberUtil;
 import cn.hutool.core.util.StrUtil;
+import cn.hutool.poi.excel.cell.FormulaCellValue;
 import cn.hutool.poi.excel.sax.handler.RowHandler;
 import cn.hutool.poi.exceptions.POIException;
 import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
@@ -54,6 +55,8 @@ public class Excel07SaxReader extends DefaultHandler implements ExcelSaxReader<E
 	private long rowNumber;
 	// 当前列坐标, 如A1,B5
 	private String curCoordinate;
+	// 当前节点名称
+	private ElementName curElementName;
 	// 前一个列的坐标
 	private String preCoordinate;
 	// 行的最大列坐标
@@ -65,6 +68,8 @@ public class Excel07SaxReader extends DefaultHandler implements ExcelSaxReader<E
 
 	// 上一次的内容
 	private final StrBuilder lastContent = StrUtil.strBuilder();
+	// 上一次的内容
+	private final StrBuilder lastFormula = StrUtil.strBuilder();
 	// 存储每行的列元素
 	private List<Object> rowCellList = new ArrayList<>();
 
@@ -189,10 +194,20 @@ public class Excel07SaxReader extends DefaultHandler implements ExcelSaxReader<E
 	 */
 	@Override
 	public void startElement(String uri, String localName, String qName, Attributes attributes) {
-		if (ElementName.row.match(localName)) {// 行开始
-			startRow(attributes);
-		} else if (ElementName.c.match(localName)) {// 单元格元素
-			startCell(attributes);
+		final ElementName name = ElementName.of(localName);
+		this.curElementName = name;
+
+		if(null != name){
+			switch (name){
+				case row:
+					// 行开始
+					startRow(attributes);
+					break;
+				case c:
+					// 单元格元素
+					startCell(attributes);
+					break;
+			}
 		}
 	}
 
@@ -201,6 +216,7 @@ public class Excel07SaxReader extends DefaultHandler implements ExcelSaxReader<E
 	 */
 	@Override
 	public void endElement(String uri, String localName, String qName) {
+		this.curElementName = null;
 		if (ElementName.c.match(localName)) { // 单元格结束
 			endCell();
 		} else if (ElementName.row.match(localName)) {// 行结束
@@ -213,8 +229,16 @@ public class Excel07SaxReader extends DefaultHandler implements ExcelSaxReader<E
 	 */
 	@Override
 	public void characters(char[] ch, int start, int length) {
-		// 得到单元格内容的值
-		lastContent.append(ch, start, length);
+		switch (this.curElementName){
+			case v:
+				// 得到单元格内容的值
+				lastContent.append(ch, start, length);
+				break;
+			case f:
+				// 得到单元格内容的值
+				lastFormula.append(ch, start, length);
+				break;
+		}
 	}
 
 	// --------------------------------------------------------------------------------------- Private method start
@@ -299,16 +323,21 @@ public class Excel07SaxReader extends DefaultHandler implements ExcelSaxReader<E
 
 		// 清空之前的数据
 		lastContent.reset();
+		lastFormula.reset();
 	}
 
 	/**
 	 * 一个单元格结尾
 	 */
 	private void endCell() {
-		final String contentStr = StrUtil.trim(lastContent);
-		final Object value = ExcelSaxUtil.getDataValue(this.cellDataType, contentStr, this.sharedStringsTable, this.numFmtString);
 		// 补全单元格之间的空格
 		fillBlankCell(preCoordinate, curCoordinate, false);
+
+		final String contentStr = StrUtil.trim(lastContent);
+		Object value = ExcelSaxUtil.getDataValue(this.cellDataType, contentStr, this.sharedStringsTable, this.numFmtString);
+		if(false == this.lastFormula.isEmpty()){
+			value = new FormulaCellValue(StrUtil.trim(lastFormula), value);
+		}
 		addCellValue(curCell++, value);
 	}
 

+ 30 - 5
hutool-poi/src/main/java/cn/hutool/poi/excel/sax/ExcelSaxUtil.java

@@ -8,7 +8,7 @@ import cn.hutool.core.util.StrUtil;
 import cn.hutool.poi.excel.sax.handler.RowHandler;
 import cn.hutool.poi.exceptions.POIException;
 import org.apache.poi.hssf.eventusermodel.FormatTrackingHSSFListener;
-import org.apache.poi.hssf.record.NumberRecord;
+import org.apache.poi.hssf.record.CellValueRecordInterface;
 import org.apache.poi.ooxml.util.SAXHelper;
 import org.apache.poi.ss.usermodel.DataFormatter;
 import org.apache.poi.xssf.model.SharedStringsTable;
@@ -175,14 +175,14 @@ public class ExcelSaxUtil {
 
 	/**
 	 * 判断数字Record中是否为日期格式
-	 * @param numrec 单元格记录
+	 * @param cell 单元格记录
 	 * @param formatListener {@link FormatTrackingHSSFListener}
 	 * @return 是否为日期格式
 	 * @since 5.4.8
 	 */
-	public static boolean isDateFormat(NumberRecord numrec, FormatTrackingHSSFListener formatListener){
-		final int formatIndex = formatListener.getFormatIndex(numrec);
-		final String formatString = formatListener.getFormatString(numrec);
+	public static boolean isDateFormat(CellValueRecordInterface cell, FormatTrackingHSSFListener formatListener){
+		final int formatIndex = formatListener.getFormatIndex(cell);
+		final String formatString = formatListener.getFormatString(cell);
 		return org.apache.poi.ss.usermodel.DateUtil.isADateFormat(formatIndex, formatString);
 	}
 
@@ -223,6 +223,31 @@ public class ExcelSaxUtil {
 	}
 
 	/**
+	 * 在Excel03 sax读取中获取日期或数字类型的结果值
+	 * @param cell 记录单元格
+	 * @param value 值
+	 * @param formatListener {@link FormatTrackingHSSFListener}
+	 * @return 值,可能为Date或Double或Long
+	 * @since 5.5.0
+	 */
+	public static Object getNumberOrDateValue(CellValueRecordInterface cell, double value, FormatTrackingHSSFListener formatListener){
+		Object result;
+		if(ExcelSaxUtil.isDateFormat(cell, formatListener)){
+			// 可能为日期格式
+			result = ExcelSaxUtil.getDateValue(value);
+		} else {
+			final long longPart = (long) value;
+			// 对于无小数部分的数字类型,转为Long,否则保留原数字
+			if (((double) longPart) == value) {
+				result = longPart;
+			} else {
+				result = value;
+			}
+		}
+		return result;
+	}
+
+	/**
 	 * 获取数字类型值
 	 *
 	 * @param value        值

+ 35 - 3
hutool-poi/src/test/java/cn/hutool/poi/excel/test/ExcelSaxReadTest.java

@@ -2,9 +2,11 @@ package cn.hutool.poi.excel.test;
 
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.convert.Convert;
+import cn.hutool.core.io.FileUtil;
 import cn.hutool.core.lang.Console;
 import cn.hutool.core.util.StrUtil;
 import cn.hutool.poi.excel.ExcelUtil;
+import cn.hutool.poi.excel.cell.FormulaCellValue;
 import cn.hutool.poi.excel.sax.Excel03SaxReader;
 import cn.hutool.poi.excel.sax.handler.RowHandler;
 import org.apache.poi.ss.usermodel.CellStyle;
@@ -13,6 +15,7 @@ import org.junit.Ignore;
 import org.junit.Test;
 
 import java.io.File;
+import java.util.ArrayList;
 import java.util.List;
 
 /**
@@ -104,10 +107,39 @@ public class ExcelSaxReadTest {
 	}
 
 	@Test
-	@Ignore
+	public void formulaRead03Test() {
+		Console.log(FileUtil.file("data_for_sax_test.xls"));
+		List<Object> rows = new ArrayList<>();
+		ExcelUtil.readBySax("data_for_sax_test.xls", -1, (i, i1, list) -> {
+			if(list.size() > 1){
+				rows.add(list.get(1));
+			} else{
+				rows.add("");
+			}
+		});
+		Assert.assertEquals(50L, rows.get(3));
+	}
+
+	@Test
+	public void formulaRead07Test() {
+		List<Object> rows = new ArrayList<>();
+		ExcelUtil.readBySax("data_for_sax_test.xlsx", 0, (i, i1, list) ->
+				rows.add(list.get(1)));
+
+		final FormulaCellValue value = (FormulaCellValue) rows.get(3);
+		Assert.assertEquals(50L, value.getResult());
+	}
+
+	@Test
 	public void dateReadTest() {
-		ExcelUtil.readBySax("d:/test/date_test.xls", 0, (i, i1, list) ->
-				Console.log(StrUtil.join(", ", list)));
+		List<String> rows = new ArrayList<>();
+		ExcelUtil.readBySax("data_for_sax_test.xls", 0, (i, i1, list) ->
+				rows.add(StrUtil.toString(list.get(0))));
+
+		Assert.assertEquals("2020-10-09 00:00:00", rows.get(1));
+		// 非日期格式不做转换
+		Assert.assertEquals("112233", rows.get(2));
+		Assert.assertEquals("1000", rows.get(3));
 	}
 
 	@Test

BIN
hutool-poi/src/test/resources/data_for_sax_test.xls


BIN
hutool-poi/src/test/resources/data_for_sax_test.xlsx