mirror of
https://github.com/apache/poi.git
synced 2026-02-27 20:40:08 +08:00
New api for reading xlsb (#920)
* Add new API * Add testBasicXSSFBSheetContentsHandler * Add testCommentsXSSFBSheetContentsHandler * Add testDateXSSFBSheetContentsHandler * Fix comment * Code Review feedback * Code review feedback * Fix backwards compatibility * rename helper method * Organise imports * Add @since POI 5.5.0
This commit is contained in:
parent
ea74954fcf
commit
63c0bf9368
@ -23,6 +23,8 @@ import java.util.Queue;
|
|||||||
|
|
||||||
import org.apache.poi.ss.usermodel.BuiltinFormats;
|
import org.apache.poi.ss.usermodel.BuiltinFormats;
|
||||||
import org.apache.poi.ss.usermodel.DataFormatter;
|
import org.apache.poi.ss.usermodel.DataFormatter;
|
||||||
|
import org.apache.poi.ss.usermodel.ExcelNumberFormat;
|
||||||
|
import org.apache.poi.ss.usermodel.FormulaError;
|
||||||
import org.apache.poi.ss.usermodel.RichTextString;
|
import org.apache.poi.ss.usermodel.RichTextString;
|
||||||
import org.apache.poi.ss.util.CellAddress;
|
import org.apache.poi.ss.util.CellAddress;
|
||||||
import org.apache.poi.util.Internal;
|
import org.apache.poi.util.Internal;
|
||||||
@ -41,10 +43,9 @@ public class XSSFBSheetHandler extends XSSFBParser {
|
|||||||
private static final int CHECK_ALL_ROWS = -1;
|
private static final int CHECK_ALL_ROWS = -1;
|
||||||
|
|
||||||
private final SharedStrings stringsTable;
|
private final SharedStrings stringsTable;
|
||||||
private final XSSFSheetXMLHandler.SheetContentsHandler handler;
|
private final XSSFBSheetContentsHandler handler;
|
||||||
private final XSSFBStylesTable styles;
|
private final XSSFBStylesTable styles;
|
||||||
private final XSSFBCommentsTable comments;
|
private final XSSFBCommentsTable comments;
|
||||||
private final DataFormatter dataFormatter;
|
|
||||||
private final boolean formulasNotResults;//TODO: implement this
|
private final boolean formulasNotResults;//TODO: implement this
|
||||||
|
|
||||||
private int lastEndedRow = -1;
|
private int lastEndedRow = -1;
|
||||||
@ -55,6 +56,51 @@ public class XSSFBSheetHandler extends XSSFBParser {
|
|||||||
private StringBuilder xlWideStringBuffer = new StringBuilder();
|
private StringBuilder xlWideStringBuffer = new StringBuilder();
|
||||||
|
|
||||||
private final XSSFBCellHeader cellBuffer = new XSSFBCellHeader();
|
private final XSSFBCellHeader cellBuffer = new XSSFBCellHeader();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a handler that forwards native POI cell types to the supplied {@link
|
||||||
|
* XSSFBSheetContentsHandler}.
|
||||||
|
*
|
||||||
|
* <p>Select this overload when the consumer expects the raw cell representation rather than
|
||||||
|
* formatted strings.
|
||||||
|
*
|
||||||
|
* @param is XLSB worksheet stream to parse
|
||||||
|
* @param styles table providing cell style and number format metadata
|
||||||
|
* @param comments optional comments table, may be {@code null}
|
||||||
|
* @param strings shared strings table used by the sheet
|
||||||
|
* @param sheetContentsHandler callback receiving native cell events
|
||||||
|
* @since POI 5.5.0
|
||||||
|
*/
|
||||||
|
public XSSFBSheetHandler(InputStream is,
|
||||||
|
XSSFBStylesTable styles,
|
||||||
|
XSSFBCommentsTable comments,
|
||||||
|
SharedStrings strings,
|
||||||
|
XSSFBSheetContentsHandler sheetContentsHandler,
|
||||||
|
boolean formulasNotResults) {
|
||||||
|
super(is);
|
||||||
|
this.styles = styles;
|
||||||
|
this.comments = comments;
|
||||||
|
this.stringsTable = strings;
|
||||||
|
this.handler = sheetContentsHandler;
|
||||||
|
this.formulasNotResults = formulasNotResults;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a handler that converts numeric and date cells to formatted strings via {@link
|
||||||
|
* DataFormatter}.
|
||||||
|
*
|
||||||
|
* <p>Select this overload when the consumer expects formatted string values rather than raw
|
||||||
|
* cell representations.
|
||||||
|
*
|
||||||
|
* @param is XLSB worksheet stream to parse
|
||||||
|
* @param styles table providing cell style and number format metadata
|
||||||
|
* @param comments optional comments table, may be {@code null}
|
||||||
|
* @param strings shared strings table used by the sheet
|
||||||
|
* @param sheetContentsHandler callback receiving formatted string values
|
||||||
|
* @param dataFormatter formatter applied to numeric and date cells
|
||||||
|
* @see #XSSFBSheetHandler(InputStream, XSSFBStylesTable, XSSFBCommentsTable, SharedStrings,
|
||||||
|
* XSSFBSheetContentsHandler, boolean)
|
||||||
|
*/
|
||||||
public XSSFBSheetHandler(InputStream is,
|
public XSSFBSheetHandler(InputStream is,
|
||||||
XSSFBStylesTable styles,
|
XSSFBStylesTable styles,
|
||||||
XSSFBCommentsTable comments,
|
XSSFBCommentsTable comments,
|
||||||
@ -66,11 +112,18 @@ public class XSSFBSheetHandler extends XSSFBParser {
|
|||||||
this.styles = styles;
|
this.styles = styles;
|
||||||
this.comments = comments;
|
this.comments = comments;
|
||||||
this.stringsTable = strings;
|
this.stringsTable = strings;
|
||||||
this.handler = sheetContentsHandler;
|
this.handler = new XSSFBSheetContentsHandlerWrapper(sheetContentsHandler, dataFormatter);
|
||||||
this.dataFormatter = dataFormatter;
|
|
||||||
this.formulasNotResults = formulasNotResults;
|
this.formulasNotResults = formulasNotResults;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dispatches a parsed XLSB record to the appropriate specialised handler.
|
||||||
|
*
|
||||||
|
* @param id numeric record identifier supplied by {@link XSSFBParser}
|
||||||
|
* @param data raw record payload
|
||||||
|
* @throws XSSFBParseException if the record cannot be processed according to the XLSB spec
|
||||||
|
* @see XSSFBRecordType
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void handleRecord(int id, byte[] data) throws XSSFBParseException {
|
public void handleRecord(int id, byte[] data) throws XSSFBParseException {
|
||||||
XSSFBRecordType type = XSSFBRecordType.lookup(id);
|
XSSFBRecordType type = XSSFBRecordType.lookup(id);
|
||||||
@ -133,86 +186,117 @@ public class XSSFBSheetHandler extends XSSFBParser {
|
|||||||
checkMissedComments(currentRow, cellBuffer.getColNum());
|
checkMissedComments(currentRow, cellBuffer.getColNum());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleCellValue(String formattedValue) {
|
private void handleStringCellValue(String val) {
|
||||||
CellAddress cellAddress = new CellAddress(currentRow, cellBuffer.getColNum());
|
CellAddress cellAddress = getCellAddress();
|
||||||
|
XSSFBComment comment = getCellComment(cellAddress);
|
||||||
|
handler.stringCell(cellAddress.formatAsString(), val, comment);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleDoubleCellValue(double val) {
|
||||||
|
CellAddress cellAddress = getCellAddress();
|
||||||
|
XSSFBComment comment = getCellComment(cellAddress);
|
||||||
|
ExcelNumberFormat nf = getExcelNumberFormat();
|
||||||
|
handler.doubleCell(cellAddress.formatAsString(), val, comment, nf);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleErrorCellValue(int val) {
|
||||||
|
FormulaError fe;
|
||||||
|
try {
|
||||||
|
fe = FormulaError.forInt(val);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
fe = null;
|
||||||
|
}
|
||||||
|
CellAddress cellAddress = getCellAddress();
|
||||||
|
XSSFBComment comment = getCellComment(cellAddress);
|
||||||
|
handler.errorCell(cellAddress.formatAsString(), fe, comment);
|
||||||
|
}
|
||||||
|
|
||||||
|
private CellAddress getCellAddress() {
|
||||||
|
return new CellAddress(currentRow, cellBuffer.getColNum());
|
||||||
|
}
|
||||||
|
|
||||||
|
private XSSFBComment getCellComment(CellAddress cellAddress) {
|
||||||
XSSFBComment comment = null;
|
XSSFBComment comment = null;
|
||||||
if (comments != null) {
|
if (comments != null) {
|
||||||
comment = comments.get(cellAddress);
|
comment = comments.get(cellAddress);
|
||||||
}
|
}
|
||||||
handler.cell(cellAddress.formatAsString(), formattedValue, comment);
|
return comment;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ExcelNumberFormat getExcelNumberFormat() {
|
||||||
|
int styleIdx = cellBuffer.getStyleIdx();
|
||||||
|
String formatString = styles.getNumberFormatString(styleIdx);
|
||||||
|
short styleIndex = styles.getNumberFormatIndex(styleIdx);
|
||||||
|
// for now, if formatString is null, silently punt
|
||||||
|
// and use "General". Not the best behavior,
|
||||||
|
// but we're doing it now in the streaming and non-streaming
|
||||||
|
// extractors for xlsx. See BUG-61053
|
||||||
|
if (formatString == null) {
|
||||||
|
formatString = BuiltinFormats.getBuiltinFormat(0);
|
||||||
|
styleIndex = 0;
|
||||||
|
}
|
||||||
|
return new ExcelNumberFormat(styleIndex, formatString);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleFmlaNum(byte[] data) {
|
private void handleFmlaNum(byte[] data) {
|
||||||
beforeCellValue(data);
|
beforeCellValue(data);
|
||||||
//xNum
|
//xNum
|
||||||
double val = LittleEndian.getDouble(data, XSSFBCellHeader.length);
|
double val = LittleEndian.getDouble(data, XSSFBCellHeader.length);
|
||||||
handleCellValue(formatVal(val, cellBuffer.getStyleIdx()));
|
handleDoubleCellValue(val);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleCellSt(byte[] data) {
|
private void handleCellSt(byte[] data) {
|
||||||
beforeCellValue(data);
|
beforeCellValue(data);
|
||||||
xlWideStringBuffer.setLength(0);
|
xlWideStringBuffer.setLength(0);
|
||||||
XSSFBUtils.readXLWideString(data, XSSFBCellHeader.length, xlWideStringBuffer);
|
XSSFBUtils.readXLWideString(data, XSSFBCellHeader.length, xlWideStringBuffer);
|
||||||
handleCellValue(xlWideStringBuffer.toString());
|
handleStringCellValue(xlWideStringBuffer.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleFmlaString(byte[] data) {
|
private void handleFmlaString(byte[] data) {
|
||||||
beforeCellValue(data);
|
beforeCellValue(data);
|
||||||
xlWideStringBuffer.setLength(0);
|
xlWideStringBuffer.setLength(0);
|
||||||
XSSFBUtils.readXLWideString(data, XSSFBCellHeader.length, xlWideStringBuffer);
|
XSSFBUtils.readXLWideString(data, XSSFBCellHeader.length, xlWideStringBuffer);
|
||||||
handleCellValue(xlWideStringBuffer.toString());
|
handleStringCellValue(xlWideStringBuffer.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleCellError(byte[] data) {
|
private void handleCellError(byte[] data) {
|
||||||
beforeCellValue(data);
|
beforeCellValue(data);
|
||||||
//TODO, read byte to figure out the type of error
|
int val = data[XSSFBCellHeader.length] & 0xFF;
|
||||||
handleCellValue("ERROR");
|
handleErrorCellValue(val);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleFmlaError(byte[] data) {
|
private void handleFmlaError(byte[] data) {
|
||||||
beforeCellValue(data);
|
beforeCellValue(data);
|
||||||
//TODO, read byte to figure out the type of error
|
int val = data[XSSFBCellHeader.length] & 0xFF;
|
||||||
handleCellValue("ERROR");
|
handleErrorCellValue(val);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleBoolean(byte[] data) {
|
private void handleBoolean(byte[] data) {
|
||||||
beforeCellValue(data);
|
beforeCellValue(data);
|
||||||
String formattedVal = (data[XSSFBCellHeader.length] == 1) ? "TRUE" : "FALSE";
|
boolean val = data[XSSFBCellHeader.length] == 1;
|
||||||
handleCellValue(formattedVal);
|
CellAddress cellAddress = getCellAddress();
|
||||||
|
XSSFBComment comment = getCellComment(cellAddress);
|
||||||
|
handler.booleanCell(cellAddress.formatAsString(), val, comment);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleCellReal(byte[] data) {
|
private void handleCellReal(byte[] data) {
|
||||||
beforeCellValue(data);
|
beforeCellValue(data);
|
||||||
//xNum
|
//xNum
|
||||||
double val = LittleEndian.getDouble(data, XSSFBCellHeader.length);
|
double val = LittleEndian.getDouble(data, XSSFBCellHeader.length);
|
||||||
handleCellValue(formatVal(val, cellBuffer.getStyleIdx()));
|
handleDoubleCellValue(val);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleCellRk(byte[] data) {
|
private void handleCellRk(byte[] data) {
|
||||||
beforeCellValue(data);
|
beforeCellValue(data);
|
||||||
double val = rkNumber(data, XSSFBCellHeader.length);
|
double val = rkNumber(data, XSSFBCellHeader.length);
|
||||||
handleCellValue(formatVal(val, cellBuffer.getStyleIdx()));
|
handleDoubleCellValue(val);
|
||||||
}
|
|
||||||
|
|
||||||
private String formatVal(double val, int styleIdx) {
|
|
||||||
String formatString = styles.getNumberFormatString(styleIdx);
|
|
||||||
short styleIndex = styles.getNumberFormatIndex(styleIdx);
|
|
||||||
//for now, if formatString is null, silently punt
|
|
||||||
//and use "General". Not the best behavior,
|
|
||||||
//but we're doing it now in the streaming and non-streaming
|
|
||||||
//extractors for xlsx. See BUG-61053
|
|
||||||
if (formatString == null) {
|
|
||||||
formatString = BuiltinFormats.getBuiltinFormat(0);
|
|
||||||
styleIndex = 0;
|
|
||||||
}
|
|
||||||
return dataFormatter.formatRawCellContents(val, styleIndex, formatString);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleBrtCellIsst(byte[] data) {
|
private void handleBrtCellIsst(byte[] data) {
|
||||||
beforeCellValue(data);
|
beforeCellValue(data);
|
||||||
int idx = XSSFBUtils.castToInt(LittleEndian.getUInt(data, XSSFBCellHeader.length));
|
int idx = XSSFBUtils.castToInt(LittleEndian.getUInt(data, XSSFBCellHeader.length));
|
||||||
RichTextString rtss = stringsTable.getItemAt(idx);
|
RichTextString rtss = stringsTable.getItemAt(idx);
|
||||||
handleCellValue(rtss.getString());
|
handleStringCellValue(rtss.getString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -300,7 +384,7 @@ public class XSSFBSheetHandler extends XSSFBParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void dumpEmptyCellComment(CellAddress cellAddress, XSSFBComment comment) {
|
private void dumpEmptyCellComment(CellAddress cellAddress, XSSFBComment comment) {
|
||||||
handler.cell(cellAddress.formatAsString(), null, comment);
|
handler.stringCell(cellAddress.formatAsString(), null, comment);
|
||||||
}
|
}
|
||||||
|
|
||||||
private double rkNumber(byte[] data, int offset) {
|
private double rkNumber(byte[] data, int offset) {
|
||||||
@ -326,6 +410,174 @@ public class XSSFBSheetHandler extends XSSFBParser {
|
|||||||
return d;
|
return d;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Receives streaming callbacks while {@link XSSFBSheetHandler} parses an XLSB sheet.
|
||||||
|
*
|
||||||
|
* @see XSSFBSheetHandler
|
||||||
|
* @since POI 5.5.0
|
||||||
|
*/
|
||||||
|
public interface XSSFBSheetContentsHandler {
|
||||||
|
/**
|
||||||
|
* Signals that a row has started before any of its cells are delivered.
|
||||||
|
*
|
||||||
|
* @param rowNum zero-based row index
|
||||||
|
* @see #endRow(int)
|
||||||
|
*/
|
||||||
|
void startRow(int rowNum);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signals that a row has ended after all of its cells and comments were processed.
|
||||||
|
*
|
||||||
|
* @param rowNum zero-based row index
|
||||||
|
* @see #startRow(int)
|
||||||
|
*/
|
||||||
|
void endRow(int rowNum);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles a cell that resolves to a string value, possibly representing a comment-only cell.
|
||||||
|
*
|
||||||
|
* @param cellReference A1-style cell address
|
||||||
|
* @param value string contents, or {@code null} if only a comment is present
|
||||||
|
* @param comment associated comment, or {@code null} if absent
|
||||||
|
* <p>Sheets that have missing or empty cells may result in sparse calls to <code>cell
|
||||||
|
* </code>. See the code in <code>
|
||||||
|
* poi-examples/src/main/java/org/apache/poi/xssf/eventusermodel/XLSX2CSV.java</code> for an
|
||||||
|
* example of how to handle this scenario.
|
||||||
|
* @see #doubleCell(String, double, XSSFComment, ExcelNumberFormat)
|
||||||
|
*/
|
||||||
|
void stringCell(String cellReference, String value, XSSFComment comment);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles a numeric cell while providing the corresponding {@link ExcelNumberFormat}.
|
||||||
|
*
|
||||||
|
* @param cellReference A1-style cell address
|
||||||
|
* @param value numeric value extracted from the sheet
|
||||||
|
* @param comment associated comment, or {@code null} if absent
|
||||||
|
* @param nf number format describing how the value should be rendered
|
||||||
|
* <p>Sheets that have missing or empty cells may result in sparse calls to <code>cell
|
||||||
|
* </code>. See the code in <code>
|
||||||
|
* poi-examples/src/main/java/org/apache/poi/xssf/eventusermodel/XLSX2CSV.java</code> for an
|
||||||
|
* example of how to handle this scenario.
|
||||||
|
* @see #stringCell(String, String, XSSFComment)
|
||||||
|
*/
|
||||||
|
void doubleCell(String cellReference, double value, XSSFComment comment, ExcelNumberFormat nf);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles a boolean cell.
|
||||||
|
*
|
||||||
|
* @param cellReference A1-style cell address
|
||||||
|
* @param value boolean value stored in the cell
|
||||||
|
* @param comment associated comment, or {@code null} if absent
|
||||||
|
* <p>Sheets that have missing or empty cells may result in sparse calls to <code>cell
|
||||||
|
* </code>. See the code in <code>
|
||||||
|
* poi-examples/src/main/java/org/apache/poi/xssf/eventusermodel/XLSX2CSV.java</code> for an
|
||||||
|
* example of how to handle this scenario.
|
||||||
|
* @see #stringCell(String, String, XSSFComment)
|
||||||
|
*/
|
||||||
|
void booleanCell(String cellReference, boolean value, XSSFComment comment);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles a cell that evaluates to an error.
|
||||||
|
*
|
||||||
|
* @param cellReference A1-style cell address
|
||||||
|
* @param fe mapped {@link FormulaError}, or {@code null} when the error code is unknown
|
||||||
|
* @param comment associated comment, or {@code null} if absent
|
||||||
|
* <p>Sheets that have missing or empty cells may result in sparse calls to <code>cell
|
||||||
|
* </code>. See the code in <code>
|
||||||
|
* poi-examples/src/main/java/org/apache/poi/xssf/eventusermodel/XLSX2CSV.java</code> for an
|
||||||
|
* example of how to handle this scenario.
|
||||||
|
* @see FormulaError
|
||||||
|
*/
|
||||||
|
void errorCell(String cellReference, FormulaError fe, XSSFComment comment);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Receives header or footer text encountered in the sheet.
|
||||||
|
*
|
||||||
|
* @param text resolved header or footer text
|
||||||
|
* @param isHeader {@code true} when the text belongs to a header, otherwise {@code false}
|
||||||
|
* @param tagName POI-internal tag representing the header or footer section
|
||||||
|
* @see #endSheet()
|
||||||
|
*/
|
||||||
|
void headerFooter(String text, boolean isHeader, String tagName);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signals that the sheet has been completely processed.
|
||||||
|
*
|
||||||
|
* @see #startRow(int)
|
||||||
|
*/
|
||||||
|
void endSheet();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bridges a {@link XSSFSheetXMLHandler.SheetContentsHandler} to the {@link
|
||||||
|
* XSSFBSheetContentsHandler} contract.
|
||||||
|
*
|
||||||
|
* @see XSSFSheetXMLHandler
|
||||||
|
*/
|
||||||
|
private final class XSSFBSheetContentsHandlerWrapper implements XSSFBSheetContentsHandler {
|
||||||
|
private final XSSFSheetXMLHandler.SheetContentsHandler delegate;
|
||||||
|
private final DataFormatter dataFormatter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a wrapper that forwards events to the XML sheet handler while formatting numeric
|
||||||
|
* cells.
|
||||||
|
*
|
||||||
|
* @param delegate target handler compatible with the XML streaming API
|
||||||
|
* @param dataFormatter formatter used for numeric and date cell rendering
|
||||||
|
*/
|
||||||
|
XSSFBSheetContentsHandlerWrapper(
|
||||||
|
XSSFSheetXMLHandler.SheetContentsHandler delegate, DataFormatter dataFormatter) {
|
||||||
|
this.delegate = delegate;
|
||||||
|
this.dataFormatter = dataFormatter;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void startRow(int rowNum) {
|
||||||
|
delegate.startRow(rowNum);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void endRow(int rowNum) {
|
||||||
|
delegate.endRow(rowNum);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void stringCell(String cellReference, String value, XSSFComment comment) {
|
||||||
|
delegate.cell(cellReference, value, comment);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void doubleCell(
|
||||||
|
String cellReference, double value, XSSFComment comment, ExcelNumberFormat nf) {
|
||||||
|
String formattedValue =
|
||||||
|
dataFormatter.formatRawCellContents(value, nf.getIdx(), nf.getFormat());
|
||||||
|
delegate.cell(cellReference, formattedValue, comment);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void booleanCell(String cellReference, boolean value, XSSFComment comment) {
|
||||||
|
delegate.cell(cellReference, Boolean.toString(value), comment);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void errorCell(String cellReference, FormulaError fe, XSSFComment comment) {
|
||||||
|
// For backward compatibility, we pass "ERROR" as the cell value.
|
||||||
|
// If you need the actual error code, you should implement
|
||||||
|
// XSSFBSheetContentsHandler directly
|
||||||
|
delegate.cell(cellReference, "ERROR", comment);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void headerFooter(String text, boolean isHeader, String tagName) {
|
||||||
|
delegate.headerFooter(text, isHeader, tagName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void endSheet() {
|
||||||
|
delegate.endSheet();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* You need to implement this to handle the results
|
* You need to implement this to handle the results
|
||||||
* of the sheet parsing.
|
* of the sheet parsing.
|
||||||
|
|||||||
@ -17,10 +17,6 @@
|
|||||||
|
|
||||||
package org.apache.poi.xssf.eventusermodel;
|
package org.apache.poi.xssf.eventusermodel;
|
||||||
|
|
||||||
import static org.apache.poi.POITestCase.assertContains;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
|
||||||
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -28,11 +24,29 @@ import java.util.List;
|
|||||||
import org.apache.poi.POIDataSamples;
|
import org.apache.poi.POIDataSamples;
|
||||||
import org.apache.poi.openxml4j.opc.OPCPackage;
|
import org.apache.poi.openxml4j.opc.OPCPackage;
|
||||||
import org.apache.poi.ss.usermodel.DataFormatter;
|
import org.apache.poi.ss.usermodel.DataFormatter;
|
||||||
|
import org.apache.poi.ss.usermodel.ExcelNumberFormat;
|
||||||
|
import org.apache.poi.ss.usermodel.FormulaError;
|
||||||
import org.apache.poi.xssf.binary.XSSFBSharedStringsTable;
|
import org.apache.poi.xssf.binary.XSSFBSharedStringsTable;
|
||||||
import org.apache.poi.xssf.binary.XSSFBSheetHandler;
|
import org.apache.poi.xssf.binary.XSSFBSheetHandler;
|
||||||
import org.apache.poi.xssf.binary.XSSFBStylesTable;
|
import org.apache.poi.xssf.binary.XSSFBStylesTable;
|
||||||
import org.apache.poi.xssf.usermodel.XSSFComment;
|
import org.apache.poi.xssf.usermodel.XSSFComment;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.mockito.ArgumentCaptor;
|
||||||
|
import org.mockito.ArgumentMatcher;
|
||||||
|
import org.mockito.InOrder;
|
||||||
|
import org.mockito.quality.Strictness;
|
||||||
|
|
||||||
|
import static org.apache.poi.POITestCase.assertContains;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.argThat;
|
||||||
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
|
import static org.mockito.ArgumentMatchers.isNull;
|
||||||
|
import static org.mockito.ArgumentMatchers.notNull;
|
||||||
|
import static org.mockito.Mockito.inOrder;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.withSettings;
|
||||||
|
|
||||||
class TestXSSFBReader {
|
class TestXSSFBReader {
|
||||||
|
|
||||||
@ -216,4 +230,234 @@ class TestXSSFBReader {
|
|||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static XSSFBSheetHandler.XSSFBSheetContentsHandler mockSheetContentsHandler() {
|
||||||
|
return mock(
|
||||||
|
XSSFBSheetHandler.XSSFBSheetContentsHandler.class,
|
||||||
|
withSettings().strictness(Strictness.STRICT_STUBS));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ArgumentMatcher<XSSFComment> commentWith(String author, String text) {
|
||||||
|
return comment -> comment != null
|
||||||
|
&& author.equals(comment.getAuthor())
|
||||||
|
&& comment.getString() != null
|
||||||
|
&& text.equals(comment.getString().toString().trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void readAllSheetsFromWorkbook(String fileName,
|
||||||
|
XSSFBSheetHandler.XSSFBSheetContentsHandler handler) throws Exception {
|
||||||
|
try (OPCPackage pkg = OPCPackage.open(_ssTests.openResourceAsStream(fileName))) {
|
||||||
|
XSSFBReader r = new XSSFBReader(pkg);
|
||||||
|
assertNotNull(r.getXSSFBStylesTable());
|
||||||
|
XSSFBSharedStringsTable sst = new XSSFBSharedStringsTable(pkg);
|
||||||
|
XSSFBStylesTable xssfbStylesTable = r.getXSSFBStylesTable();
|
||||||
|
XSSFBReader.SheetIterator it = (XSSFBReader.SheetIterator) r.getSheetsData();
|
||||||
|
|
||||||
|
while (it.hasNext()) {
|
||||||
|
InputStream is = it.next();
|
||||||
|
XSSFBSheetHandler sheetHandler = new XSSFBSheetHandler(is,
|
||||||
|
xssfbStylesTable,
|
||||||
|
it.getXSSFBSheetComments(),
|
||||||
|
sst,
|
||||||
|
handler,
|
||||||
|
false);
|
||||||
|
sheetHandler.parse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testBasicXSSFBSheetContentsHandler() throws Exception {
|
||||||
|
XSSFBSheetHandler.XSSFBSheetContentsHandler handler = mockSheetContentsHandler();
|
||||||
|
readAllSheetsFromWorkbook("testVarious.xlsb", handler);
|
||||||
|
|
||||||
|
InOrder ordered = inOrder(handler);
|
||||||
|
ordered.verify(handler).startRow(0);
|
||||||
|
ordered.verify(handler).stringCell(eq("A1"), eq("String"), isNull());
|
||||||
|
ordered.verify(handler).stringCell(eq("B1"), eq("This is a string"), isNull());
|
||||||
|
ordered.verify(handler).endRow(0);
|
||||||
|
|
||||||
|
ordered.verify(handler).startRow(1);
|
||||||
|
ordered.verify(handler).stringCell(eq("A2"), eq("integer"), isNull());
|
||||||
|
ordered.verify(handler).doubleCell(eq("B2"), eq(13.0d), isNull(), any(ExcelNumberFormat.class));
|
||||||
|
ordered.verify(handler).endRow(1);
|
||||||
|
|
||||||
|
ordered.verify(handler).startRow(2);
|
||||||
|
ordered.verify(handler).stringCell(eq("A3"), eq("float"), isNull());
|
||||||
|
ordered.verify(handler).doubleCell(eq("B3"), eq(13.1211231321d), isNull(), any(ExcelNumberFormat.class));
|
||||||
|
ordered.verify(handler).endRow(2);
|
||||||
|
|
||||||
|
ordered.verify(handler).startRow(3);
|
||||||
|
ordered.verify(handler).stringCell(eq("A4"), eq("currency"), isNull());
|
||||||
|
ordered.verify(handler).doubleCell(eq("B4"), eq(3.03d), isNull(), any(ExcelNumberFormat.class));
|
||||||
|
ordered.verify(handler).endRow(3);
|
||||||
|
|
||||||
|
ordered.verify(handler).startRow(4);
|
||||||
|
ordered.verify(handler).stringCell(eq("A5"), eq("percent"), isNull());
|
||||||
|
ordered.verify(handler).doubleCell(eq("B5"), eq(0.2d), isNull(), any(ExcelNumberFormat.class));
|
||||||
|
ordered.verify(handler).endRow(4);
|
||||||
|
|
||||||
|
ordered.verify(handler).startRow(5);
|
||||||
|
ordered.verify(handler).stringCell(eq("A6"), eq("float 2"), isNull());
|
||||||
|
ordered.verify(handler).doubleCell(eq("B6"), eq(13.12131231d), isNull(), any(ExcelNumberFormat.class));
|
||||||
|
ordered.verify(handler).endRow(5);
|
||||||
|
|
||||||
|
ordered.verify(handler).startRow(6);
|
||||||
|
ordered.verify(handler).stringCell(eq("A7"), eq("long int"), isNull());
|
||||||
|
ordered.verify(handler).doubleCell(eq("B7"), eq(1.23456789012345E14d), isNull(), any(ExcelNumberFormat.class));
|
||||||
|
ordered.verify(handler).endRow(6);
|
||||||
|
|
||||||
|
ordered.verify(handler).startRow(7);
|
||||||
|
ordered.verify(handler).stringCell(eq("A8"), eq("longer int"), isNull());
|
||||||
|
ordered.verify(handler).doubleCell(eq("B8"), eq(1.23456789012345E15d), isNull(), any(ExcelNumberFormat.class));
|
||||||
|
ordered.verify(handler).stringCell(eq("C8"), isNull(), notNull(XSSFComment.class));
|
||||||
|
ordered.verify(handler).endRow(7);
|
||||||
|
|
||||||
|
ordered.verify(handler).startRow(8);
|
||||||
|
ordered.verify(handler).stringCell(eq("A9"), eq("fraction"), isNull());
|
||||||
|
ordered.verify(handler).doubleCell(eq("B9"), eq(0.25d), isNull(), any(ExcelNumberFormat.class));
|
||||||
|
ordered.verify(handler).endRow(8);
|
||||||
|
|
||||||
|
ordered.verify(handler).startRow(9);
|
||||||
|
ordered.verify(handler).stringCell(eq("A10"), eq("date"), isNull());
|
||||||
|
ordered.verify(handler).doubleCell(eq("B10"), eq(42803.0d), isNull(), any(ExcelNumberFormat.class));
|
||||||
|
ordered.verify(handler).endRow(9);
|
||||||
|
|
||||||
|
ordered.verify(handler).startRow(10);
|
||||||
|
ordered.verify(handler).stringCell(eq("A11"), eq("comment"), isNull());
|
||||||
|
ordered.verify(handler).stringCell(eq("B11"), eq("contents"), notNull(XSSFComment.class));
|
||||||
|
ordered.verify(handler).endRow(10);
|
||||||
|
|
||||||
|
ordered.verify(handler).startRow(11);
|
||||||
|
ordered.verify(handler).stringCell(eq("A12"), eq("hyperlink"), isNull());
|
||||||
|
ordered.verify(handler).stringCell(eq("B12"), eq("tika_link"), isNull());
|
||||||
|
ordered.verify(handler).endRow(11);
|
||||||
|
|
||||||
|
ordered.verify(handler).startRow(12);
|
||||||
|
ordered.verify(handler).stringCell(eq("A13"), eq("formula"), isNull());
|
||||||
|
ordered.verify(handler).doubleCell(eq("B13"), eq(4.0d), isNull(), any(ExcelNumberFormat.class));
|
||||||
|
ordered.verify(handler).doubleCell(eq("C13"), eq(2.0d), isNull(), any(ExcelNumberFormat.class));
|
||||||
|
ordered.verify(handler).endRow(12);
|
||||||
|
|
||||||
|
ordered.verify(handler).startRow(13);
|
||||||
|
ordered.verify(handler).stringCell(eq("A14"), eq("formulaErr"), isNull());
|
||||||
|
ordered.verify(handler).errorCell(eq("B14"), eq(FormulaError.NAME), isNull());
|
||||||
|
ordered.verify(handler).endRow(13);
|
||||||
|
|
||||||
|
ordered.verify(handler).startRow(14);
|
||||||
|
ordered.verify(handler).stringCell(eq("A15"), eq("formulaFloat"), isNull());
|
||||||
|
ordered.verify(handler).doubleCell(eq("B15"), eq(0.5d), isNull(), any(ExcelNumberFormat.class));
|
||||||
|
ordered.verify(handler).stringCell(eq("D15"), eq("March"), isNull());
|
||||||
|
ordered.verify(handler).stringCell(eq("E15"), eq("April"), isNull());
|
||||||
|
ordered.verify(handler).endRow(14);
|
||||||
|
|
||||||
|
ordered.verify(handler).startRow(15);
|
||||||
|
ordered.verify(handler).stringCell(eq("A16"), eq("customFormat1"), isNull());
|
||||||
|
ordered.verify(handler).stringCell(eq("B16"), eq(" 46/1963"), isNull());
|
||||||
|
ordered.verify(handler).stringCell(eq("C16"), eq("merchant1"), isNull());
|
||||||
|
ordered.verify(handler).doubleCell(eq("D16"), eq(1.0d), isNull(), any(ExcelNumberFormat.class));
|
||||||
|
ordered.verify(handler).doubleCell(eq("E16"), eq(3.0d), isNull(), any(ExcelNumberFormat.class));
|
||||||
|
ordered.verify(handler).endRow(15);
|
||||||
|
|
||||||
|
ordered.verify(handler).startRow(16);
|
||||||
|
ordered.verify(handler).stringCell(eq("A17"), eq("customFormat2"), isNull());
|
||||||
|
ordered.verify(handler).stringCell(eq("B17"), eq(" 3/128"), isNull());
|
||||||
|
ordered.verify(handler).stringCell(eq("C17"), eq("merchant2"), isNull());
|
||||||
|
ordered.verify(handler).doubleCell(eq("D17"), eq(2.0d), isNull(), any(ExcelNumberFormat.class));
|
||||||
|
ordered.verify(handler).doubleCell(eq("E17"), eq(4.0d), isNull(), any(ExcelNumberFormat.class));
|
||||||
|
ordered.verify(handler).endRow(16);
|
||||||
|
|
||||||
|
ordered.verify(handler).startRow(20);
|
||||||
|
ordered.verify(handler).stringCell(eq("C21"), eq("text test"), isNull());
|
||||||
|
ordered.verify(handler).endRow(20);
|
||||||
|
|
||||||
|
ordered.verify(handler).startRow(22);
|
||||||
|
ordered.verify(handler).stringCell(eq("A23"), isNull(), notNull(XSSFComment.class));
|
||||||
|
ordered.verify(handler).endRow(22);
|
||||||
|
|
||||||
|
ordered.verify(handler).startRow(23);
|
||||||
|
ordered.verify(handler).stringCell(eq("C24"), isNull(), notNull(XSSFComment.class));
|
||||||
|
ordered.verify(handler).endRow(23);
|
||||||
|
|
||||||
|
ordered.verify(handler).startRow(27);
|
||||||
|
ordered.verify(handler).stringCell(eq("B28"), isNull(), notNull(XSSFComment.class));
|
||||||
|
ordered.verify(handler).endRow(27);
|
||||||
|
|
||||||
|
ordered.verify(handler).startRow(29);
|
||||||
|
ordered.verify(handler).stringCell(eq("B30"), eq("the"), isNull());
|
||||||
|
ordered.verify(handler).stringCell(eq("C30"), isNull(), notNull(XSSFComment.class));
|
||||||
|
ordered.verify(handler).endRow(29);
|
||||||
|
|
||||||
|
ordered.verify(handler).startRow(32);
|
||||||
|
ordered.verify(handler).stringCell(eq("B33"), eq("the"), isNull());
|
||||||
|
ordered.verify(handler).stringCell(eq("C33"), isNull(), notNull(XSSFComment.class));
|
||||||
|
ordered.verify(handler).stringCell(eq("D33"), eq("quick"), isNull());
|
||||||
|
ordered.verify(handler).endRow(32);
|
||||||
|
|
||||||
|
ordered.verify(handler).startRow(34);
|
||||||
|
ordered.verify(handler).stringCell(eq("B35"), eq("comment6"), notNull(XSSFComment.class));
|
||||||
|
ordered.verify(handler).endRow(34);
|
||||||
|
|
||||||
|
ordered.verify(handler).startRow(64);
|
||||||
|
ordered.verify(handler).stringCell(eq("I65"), isNull(), notNull(XSSFComment.class));
|
||||||
|
ordered.verify(handler).endRow(64);
|
||||||
|
|
||||||
|
ordered.verify(handler).startRow(65);
|
||||||
|
ordered.verify(handler).stringCell(eq("I66"), isNull(), notNull(XSSFComment.class));
|
||||||
|
ordered.verify(handler).endRow(65);
|
||||||
|
|
||||||
|
ordered.verify(handler).headerFooter(eq("OddLeftHeader OddCenterHeader OddRightHeader"), eq(true), eq("header"));
|
||||||
|
ordered.verify(handler).headerFooter(eq("OddLeftFooter OddCenterFooter OddRightFooter"), eq(false), eq("footer"));
|
||||||
|
ordered.verify(handler).headerFooter(eq("EvenLeftHeader EvenCenterHeader EvenRightHeader\n"), eq(true), eq("evenHeader"));
|
||||||
|
ordered.verify(handler).headerFooter(eq("EvenLeftFooter EvenCenterFooter EvenRightFooter"), eq(false), eq("evenFooter"));
|
||||||
|
ordered.verify(handler).headerFooter(eq("FirstPageLeftHeader FirstPageCenterHeader FirstPageRightHeader"), eq(true), eq("firstHeader"));
|
||||||
|
ordered.verify(handler).headerFooter(eq("FirstPageLeftFooter FirstPageCenterFooter FirstPageRightFooter"), eq(false), eq("firstFooter"));
|
||||||
|
ordered.verifyNoMoreInteractions();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testCommentsXSSFBSheetContentsHandler() throws Exception {
|
||||||
|
XSSFBSheetHandler.XSSFBSheetContentsHandler handler = mockSheetContentsHandler();
|
||||||
|
readAllSheetsFromWorkbook("comments.xlsb", handler);
|
||||||
|
|
||||||
|
InOrder ordered = inOrder(handler);
|
||||||
|
ordered.verify(handler).startRow(0);
|
||||||
|
ordered.verify(handler).stringCell(eq("A1"), isNull(),
|
||||||
|
argThat(commentWith("Sven Nissel", "comment top row1 (index0)")));
|
||||||
|
ordered.verify(handler).stringCell(eq("B1"), eq("row1"), isNull());
|
||||||
|
ordered.verify(handler).endRow(0);
|
||||||
|
ordered.verify(handler).startRow(1);
|
||||||
|
ordered.verify(handler).stringCell(eq("A2"), isNull(),
|
||||||
|
argThat(commentWith("Allison, Timothy B.", "Allison, Timothy B.:\ncomment row2 (index1)")));
|
||||||
|
ordered.verify(handler).endRow(1);
|
||||||
|
ordered.verify(handler).startRow(2);
|
||||||
|
ordered.verify(handler).stringCell(eq("A3"), eq("row3"),
|
||||||
|
argThat(commentWith("Sven Nissel", "comment top row3 (index2)")));
|
||||||
|
ordered.verify(handler).stringCell(eq("B3"), eq("row3"), isNull());
|
||||||
|
ordered.verify(handler).endRow(2);
|
||||||
|
ordered.verify(handler).startRow(3);
|
||||||
|
ordered.verify(handler).stringCell(eq("A4"), isNull(),
|
||||||
|
argThat(commentWith("Sven Nissel", "comment top row4 (index3)")));
|
||||||
|
ordered.verify(handler).stringCell(eq("B4"), eq("row4"), isNull());
|
||||||
|
ordered.verify(handler).endRow(3);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testDateXSSFBSheetContentsHandler() throws Exception {
|
||||||
|
XSSFBSheetHandler.XSSFBSheetContentsHandler handler = mockSheetContentsHandler();
|
||||||
|
readAllSheetsFromWorkbook("date.xlsb", handler);
|
||||||
|
|
||||||
|
InOrder ordered = inOrder(handler);
|
||||||
|
ArgumentCaptor<ExcelNumberFormat> numberFormat = ArgumentCaptor.forClass(ExcelNumberFormat.class);
|
||||||
|
ordered.verify(handler).startRow(0);
|
||||||
|
ordered.verify(handler).doubleCell(eq("A1"), eq(41286.0d), isNull(), numberFormat.capture());
|
||||||
|
ordered.verify(handler).endRow(0);
|
||||||
|
ExcelNumberFormat format = numberFormat.getValue();
|
||||||
|
assertNotNull(format);
|
||||||
|
assertEquals(14, format.getIdx());
|
||||||
|
assertEquals("m/d/yy", format.getFormat());
|
||||||
|
ordered.verifyNoMoreInteractions();
|
||||||
|
DataFormatter df = new DataFormatter();
|
||||||
|
assertEquals("1/12/13", df.formatRawCellContents(41286.0d, format.getIdx(), format.getFormat()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user