reformat file

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1913066 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
PJ Fanning 2023-10-17 15:09:26 +00:00
parent 5035a531c5
commit 0dd2b18b56

View File

@ -38,12 +38,12 @@ import org.xml.sax.helpers.DefaultHandler;
/** /**
* This class handles the streaming processing of a sheet#.xml * This class handles the streaming processing of a sheet#.xml
* sheet part of a XSSF .xlsx file, and generates * sheet part of a XSSF .xlsx file, and generates
* row and cell events for it. * row and cell events for it.
* * <p>
* This allows to build functionality which reads huge files * This allows to build functionality which reads huge files
* without needing large amounts of main memory. * without needing large amounts of main memory.
* * <p>
* See {@link SheetContentsHandler} for the interface that * See {@link SheetContentsHandler} for the interface that
* you need to implement for reading information from a file. * you need to implement for reading information from a file.
*/ */
@ -51,499 +51,505 @@ public class XSSFSheetXMLHandler extends DefaultHandler {
private static final Logger LOG = LogManager.getLogger(XSSFSheetXMLHandler.class); private static final Logger LOG = LogManager.getLogger(XSSFSheetXMLHandler.class);
/** /**
* These are the different kinds of cells we support. * These are the different kinds of cells we support.
* We keep track of the current one between * We keep track of the current one between
* the start and end. * the start and end.
*/ */
enum xssfDataType { enum xssfDataType {
BOOLEAN, BOOLEAN,
ERROR, ERROR,
FORMULA, FORMULA,
INLINE_STRING, INLINE_STRING,
SST_STRING, SST_STRING,
NUMBER, NUMBER,
} }
/** /**
* Table with the styles used for formatting * Table with the styles used for formatting
*/ */
private final Styles stylesTable; private final Styles stylesTable;
/** /**
* Table with cell comments * Table with cell comments
*/ */
private final Comments comments; private final Comments comments;
/** /**
* Read only access to the shared strings table, for looking * Read only access to the shared strings table, for looking
* up (most) string cell's contents * up (most) string cell's contents
*/ */
private final SharedStrings sharedStringsTable; private final SharedStrings sharedStringsTable;
/** /**
* Where our text is going * Where our text is going
*/ */
private final SheetContentsHandler output; private final SheetContentsHandler output;
// Set when V start element is seen // Set when V start element is seen
private boolean vIsOpen; private boolean vIsOpen;
// Set when F start element is seen // Set when F start element is seen
private boolean fIsOpen; private boolean fIsOpen;
// Set when an Inline String "is" is seen // Set when an Inline String "is" is seen
private boolean isIsOpen; private boolean isIsOpen;
// Set when a header/footer element is seen // Set when a header/footer element is seen
private boolean hfIsOpen; private boolean hfIsOpen;
// Set when cell start element is seen; // Set when cell start element is seen;
// used when cell close element is seen. // used when cell close element is seen.
private xssfDataType nextDataType; private xssfDataType nextDataType;
// Used to format numeric cell values. // Used to format numeric cell values.
private short formatIndex; private short formatIndex;
private String formatString; private String formatString;
private final DataFormatter formatter; private final DataFormatter formatter;
private int rowNum; private int rowNum;
private int nextRowNum; // some sheets do not have rowNums, Excel can read them so we should try to handle them correctly as well private int nextRowNum; // some sheets do not have rowNums, Excel can read them so we should try to handle them correctly as well
private String cellRef; private String cellRef;
private final boolean formulasNotResults; private final boolean formulasNotResults;
// Gathers characters as they are seen. // Gathers characters as they are seen.
private final StringBuilder value = new StringBuilder(64); private final StringBuilder value = new StringBuilder(64);
private final StringBuilder formula = new StringBuilder(64); private final StringBuilder formula = new StringBuilder(64);
private final StringBuilder headerFooter = new StringBuilder(64); private final StringBuilder headerFooter = new StringBuilder(64);
private Queue<CellAddress> commentCellRefs; private Queue<CellAddress> commentCellRefs;
/** /**
* Accepts objects needed while parsing. * Accepts objects needed while parsing.
* *
* @param styles Table of styles * @param styles Table of styles
* @param strings Table of shared strings * @param strings Table of shared strings
*/ */
public XSSFSheetXMLHandler( public XSSFSheetXMLHandler(
Styles styles, Styles styles,
Comments comments, Comments comments,
SharedStrings strings, SharedStrings strings,
SheetContentsHandler sheetContentsHandler, SheetContentsHandler sheetContentsHandler,
DataFormatter dataFormatter, DataFormatter dataFormatter,
boolean formulasNotResults) { boolean formulasNotResults) {
this.stylesTable = styles; this.stylesTable = styles;
this.comments = comments; this.comments = comments;
this.sharedStringsTable = strings; this.sharedStringsTable = strings;
this.output = sheetContentsHandler; this.output = sheetContentsHandler;
this.formulasNotResults = formulasNotResults; this.formulasNotResults = formulasNotResults;
this.nextDataType = xssfDataType.NUMBER; this.nextDataType = xssfDataType.NUMBER;
this.formatter = dataFormatter; this.formatter = dataFormatter;
init(comments); init(comments);
} }
/** /**
* Accepts objects needed while parsing. * Accepts objects needed while parsing.
* *
* @param styles Table of styles * @param styles Table of styles
* @param strings Table of shared strings * @param strings Table of shared strings
*/ */
public XSSFSheetXMLHandler( public XSSFSheetXMLHandler(
Styles styles, Styles styles,
SharedStrings strings, SharedStrings strings,
SheetContentsHandler sheetContentsHandler, SheetContentsHandler sheetContentsHandler,
DataFormatter dataFormatter, DataFormatter dataFormatter,
boolean formulasNotResults) { boolean formulasNotResults) {
this(styles, null, strings, sheetContentsHandler, dataFormatter, formulasNotResults); this(styles, null, strings, sheetContentsHandler, dataFormatter, formulasNotResults);
} }
/** /**
* Accepts objects needed while parsing. * Accepts objects needed while parsing.
* *
* @param styles Table of styles * @param styles Table of styles
* @param strings Table of shared strings * @param strings Table of shared strings
*/ */
public XSSFSheetXMLHandler( public XSSFSheetXMLHandler(
Styles styles, Styles styles,
SharedStrings strings, SharedStrings strings,
SheetContentsHandler sheetContentsHandler, SheetContentsHandler sheetContentsHandler,
boolean formulasNotResults) { boolean formulasNotResults) {
this(styles, strings, sheetContentsHandler, new DataFormatter(), formulasNotResults); this(styles, strings, sheetContentsHandler, new DataFormatter(), formulasNotResults);
} }
private void init(Comments commentsTable) { private void init(Comments commentsTable) {
if (commentsTable != null) { if (commentsTable != null) {
commentCellRefs = new LinkedList<>(); commentCellRefs = new LinkedList<>();
for (Iterator<CellAddress> iter = commentsTable.getCellAddresses(); iter.hasNext(); ) { for (Iterator<CellAddress> iter = commentsTable.getCellAddresses(); iter.hasNext(); ) {
commentCellRefs.add(iter.next()); commentCellRefs.add(iter.next());
} }
} }
} }
private boolean isTextTag(String name) { private boolean isTextTag(String name) {
if("v".equals(name)) { if ("v".equals(name)) {
// Easy, normal v text tag // Easy, normal v text tag
return true; return true;
} }
if("inlineStr".equals(name)) { if ("inlineStr".equals(name)) {
// Easy inline string // Easy inline string
return true; return true;
} }
if("t".equals(name) && isIsOpen) { if ("t".equals(name) && isIsOpen) {
// Inline string <is><t>...</t></is> pair // Inline string <is><t>...</t></is> pair
return true; return true;
} }
// It isn't a text tag // It isn't a text tag
return false; return false;
} }
@Override @Override
@SuppressWarnings("unused") @SuppressWarnings("unused")
public void startElement(String uri, String localName, String qName, public void startElement(String uri, String localName, String qName,
Attributes attributes) throws SAXException { Attributes attributes) throws SAXException {
if (uri != null && ! uri.equals(NS_SPREADSHEETML)) { if (uri != null && !uri.equals(NS_SPREADSHEETML)) {
return; return;
} }
if (isTextTag(localName)) { if (isTextTag(localName)) {
vIsOpen = true; vIsOpen = true;
// Clear contents cache // Clear contents cache
if (!isIsOpen) { if (!isIsOpen) {
value.setLength(0); value.setLength(0);
} }
} else if ("is".equals(localName)) { } else if ("is".equals(localName)) {
// Inline string outer tag // Inline string outer tag
isIsOpen = true; isIsOpen = true;
} else if ("f".equals(localName)) { } else if ("f".equals(localName)) {
// Clear contents cache // Clear contents cache
formula.setLength(0); formula.setLength(0);
// Mark us as being a formula if not already // Mark us as being a formula if not already
nextDataType = xssfDataType.FORMULA; nextDataType = xssfDataType.FORMULA;
// Decide where to get the formula string from // Decide where to get the formula string from
String type = attributes.getValue("t"); String type = attributes.getValue("t");
if(type != null && type.equals("shared")) { if (type != null && type.equals("shared")) {
// Is it the one that defines the shared, or uses it? // Is it the one that defines the shared, or uses it?
String ref = attributes.getValue("ref"); String ref = attributes.getValue("ref");
String si = attributes.getValue("si"); String si = attributes.getValue("si");
if(ref != null) { if (ref != null) {
// This one defines it // This one defines it
// TODO Save it somewhere // TODO Save it somewhere
fIsOpen = true; fIsOpen = true;
} else { } else {
// This one uses a shared formula // This one uses a shared formula
// TODO Retrieve the shared formula and tweak it to // TODO Retrieve the shared formula and tweak it to
// match the current cell // match the current cell
if(formulasNotResults) { if (formulasNotResults) {
LOG.atWarn().log("shared formulas not yet supported!"); LOG.atWarn().log("shared formulas not yet supported!");
} /*else { } /*else {
// It's a shared formula, so we can't get at the formula string yet // It's a shared formula, so we can't get at the formula string yet
// However, they don't care about the formula string, so that's ok! // However, they don't care about the formula string, so that's ok!
}*/ }*/
} }
} else { } else {
fIsOpen = true; fIsOpen = true;
} }
} } else if ("oddHeader".equals(localName) || "evenHeader".equals(localName) ||
else if("oddHeader".equals(localName) || "evenHeader".equals(localName) || "firstHeader".equals(localName) || "firstFooter".equals(localName) ||
"firstHeader".equals(localName) || "firstFooter".equals(localName) || "oddFooter".equals(localName) || "evenFooter".equals(localName)) {
"oddFooter".equals(localName) || "evenFooter".equals(localName)) { hfIsOpen = true;
hfIsOpen = true; // Clear contents cache
// Clear contents cache headerFooter.setLength(0);
headerFooter.setLength(0); } else if ("row".equals(localName)) {
} String rowNumStr = attributes.getValue("r");
else if("row".equals(localName)) { if (rowNumStr != null) {
String rowNumStr = attributes.getValue("r"); rowNum = Integer.parseInt(rowNumStr) - 1;
if(rowNumStr != null) { } else {
rowNum = Integer.parseInt(rowNumStr) - 1; rowNum = nextRowNum;
} else { }
rowNum = nextRowNum; output.startRow(rowNum);
} }
output.startRow(rowNum); // c => cell
} else if ("c".equals(localName)) {
// c => cell // Set up defaults.
else if ("c".equals(localName)) { this.nextDataType = xssfDataType.NUMBER;
// Set up defaults. this.formatIndex = -1;
this.nextDataType = xssfDataType.NUMBER; this.formatString = null;
this.formatIndex = -1; cellRef = attributes.getValue("r");
this.formatString = null; String cellType = attributes.getValue("t");
cellRef = attributes.getValue("r"); String cellStyleStr = attributes.getValue("s");
String cellType = attributes.getValue("t"); if ("b".equals(cellType))
String cellStyleStr = attributes.getValue("s"); nextDataType = xssfDataType.BOOLEAN;
if ("b".equals(cellType)) else if ("e".equals(cellType))
nextDataType = xssfDataType.BOOLEAN; nextDataType = xssfDataType.ERROR;
else if ("e".equals(cellType)) else if ("inlineStr".equals(cellType))
nextDataType = xssfDataType.ERROR; nextDataType = xssfDataType.INLINE_STRING;
else if ("inlineStr".equals(cellType)) else if ("s".equals(cellType))
nextDataType = xssfDataType.INLINE_STRING; nextDataType = xssfDataType.SST_STRING;
else if ("s".equals(cellType)) else if ("str".equals(cellType))
nextDataType = xssfDataType.SST_STRING; nextDataType = xssfDataType.FORMULA;
else if ("str".equals(cellType)) else {
nextDataType = xssfDataType.FORMULA; // Number, but almost certainly with a special style or format
else { XSSFCellStyle style = null;
// Number, but almost certainly with a special style or format if (stylesTable != null) {
XSSFCellStyle style = null; if (cellStyleStr != null) {
if (stylesTable != null) { int styleIndex = Integer.parseInt(cellStyleStr);
if (cellStyleStr != null) { style = stylesTable.getStyleAt(styleIndex);
int styleIndex = Integer.parseInt(cellStyleStr); } else if (stylesTable.getNumCellStyles() > 0) {
style = stylesTable.getStyleAt(styleIndex); style = stylesTable.getStyleAt(0);
} else if (stylesTable.getNumCellStyles() > 0) { }
style = stylesTable.getStyleAt(0); }
} if (style != null) {
} this.formatIndex = style.getDataFormat();
if (style != null) { this.formatString = style.getDataFormatString();
this.formatIndex = style.getDataFormat(); if (this.formatString == null)
this.formatString = style.getDataFormatString(); this.formatString = BuiltinFormats.getBuiltinFormat(this.formatIndex);
if (this.formatString == null) }
this.formatString = BuiltinFormats.getBuiltinFormat(this.formatIndex); }
} }
} }
}
}
@Override @Override
public void endElement(String uri, String localName, String qName) public void endElement(String uri, String localName, String qName)
throws SAXException { throws SAXException {
if (uri != null && ! uri.equals(NS_SPREADSHEETML)) { if (uri != null && !uri.equals(NS_SPREADSHEETML)) {
return; return;
} }
// v => contents of a cell // v => contents of a cell
if (isTextTag(localName)) { if (isTextTag(localName)) {
vIsOpen = false; vIsOpen = false;
if (!isIsOpen) { if (!isIsOpen) {
outputCell(); outputCell();
value.setLength(0); value.setLength(0);
} }
} else if ("f".equals(localName)) { } else if ("f".equals(localName)) {
fIsOpen = false; fIsOpen = false;
} else if ("is".equals(localName)) { } else if ("is".equals(localName)) {
isIsOpen = false; isIsOpen = false;
outputCell(); outputCell();
value.setLength(0); value.setLength(0);
} else if ("row".equals(localName)) { } else if ("row".equals(localName)) {
// Handle any "missing" cells which had comments attached // Handle any "missing" cells which had comments attached
checkForEmptyCellComments(EmptyCellCommentsCheckType.END_OF_ROW); checkForEmptyCellComments(EmptyCellCommentsCheckType.END_OF_ROW);
// Finish up the row // Finish up the row
output.endRow(rowNum); output.endRow(rowNum);
// some sheets do not have rowNum set in the XML, Excel can read them so we should try to read them as well // some sheets do not have rowNum set in the XML, Excel can read them so we should try to read them as well
nextRowNum = rowNum + 1; nextRowNum = rowNum + 1;
} else if ("sheetData".equals(localName)) { } else if ("sheetData".equals(localName)) {
// Handle any "missing" cells which had comments attached // Handle any "missing" cells which had comments attached
checkForEmptyCellComments(EmptyCellCommentsCheckType.END_OF_SHEET_DATA); checkForEmptyCellComments(EmptyCellCommentsCheckType.END_OF_SHEET_DATA);
// indicate that this sheet is now done // indicate that this sheet is now done
output.endSheet(); output.endSheet();
} } else if ("oddHeader".equals(localName) || "evenHeader".equals(localName) ||
else if("oddHeader".equals(localName) || "evenHeader".equals(localName) || "firstHeader".equals(localName)) {
"firstHeader".equals(localName)) { hfIsOpen = false;
hfIsOpen = false; output.headerFooter(headerFooter.toString(), true, localName);
output.headerFooter(headerFooter.toString(), true, localName); } else if ("oddFooter".equals(localName) || "evenFooter".equals(localName) ||
} "firstFooter".equals(localName)) {
else if("oddFooter".equals(localName) || "evenFooter".equals(localName) || hfIsOpen = false;
"firstFooter".equals(localName)) { output.headerFooter(headerFooter.toString(), false, localName);
hfIsOpen = false; }
output.headerFooter(headerFooter.toString(), false, localName); }
}
}
/** /**
* Captures characters only if a suitable element is open. * Captures characters only if a suitable element is open.
* Originally was just "v"; extended for inlineStr also. * Originally was just "v"; extended for inlineStr also.
*/ */
@Override @Override
public void characters(char[] ch, int start, int length) public void characters(char[] ch, int start, int length)
throws SAXException { throws SAXException {
if (vIsOpen) { if (vIsOpen) {
value.append(ch, start, length); value.append(ch, start, length);
} }
if (fIsOpen) { if (fIsOpen) {
formula.append(ch, start, length); formula.append(ch, start, length);
} }
if (hfIsOpen) { if (hfIsOpen) {
headerFooter.append(ch, start, length); headerFooter.append(ch, start, length);
} }
} }
private void outputCell() { private void outputCell() {
String thisStr = null; String thisStr = null;
// Process the value contents as required, now we have it all // Process the value contents as required, now we have it all
switch (nextDataType) { switch (nextDataType) {
case BOOLEAN: case BOOLEAN:
char first = value.charAt(0); char first = value.charAt(0);
thisStr = first == '0' ? "FALSE" : "TRUE"; thisStr = first == '0' ? "FALSE" : "TRUE";
break; break;
case ERROR: case ERROR:
thisStr = "ERROR:" + value; thisStr = "ERROR:" + value;
break; break;
case FORMULA: case FORMULA:
if(formulasNotResults) { if (formulasNotResults) {
thisStr = formula.toString(); thisStr = formula.toString();
} else { } else {
String fv = value.toString(); String fv = value.toString();
if (this.formatString != null) { if (this.formatString != null) {
try { try {
// Try to use the value as a formattable number // Try to use the value as a formattable number
double d = Double.parseDouble(fv); double d = Double.parseDouble(fv);
thisStr = formatter.formatRawCellContents(d, this.formatIndex, this.formatString); thisStr = formatter.formatRawCellContents(d, this.formatIndex, this.formatString);
} catch(NumberFormatException e) { } catch (NumberFormatException e) {
// Formula is a String result not a Numeric one // Formula is a String result not a Numeric one
thisStr = fv; thisStr = fv;
} }
} else { } else {
// No formatting applied, just do raw value in all cases // No formatting applied, just do raw value in all cases
thisStr = fv; thisStr = fv;
} }
} }
break; break;
case INLINE_STRING: case INLINE_STRING:
// TODO: Can these ever have formatting on them? // TODO: Can these ever have formatting on them?
XSSFRichTextString rtsi = new XSSFRichTextString(value.toString()); XSSFRichTextString rtsi = new XSSFRichTextString(value.toString());
thisStr = rtsi.toString(); thisStr = rtsi.toString();
break; break;
case SST_STRING: case SST_STRING:
String sstIndex = value.toString(); String sstIndex = value.toString();
if (sstIndex.length() > 0) { if (sstIndex.length() > 0) {
try { try {
int idx = Integer.parseInt(sstIndex); int idx = Integer.parseInt(sstIndex);
RichTextString rtss = sharedStringsTable.getItemAt(idx); RichTextString rtss = sharedStringsTable.getItemAt(idx);
thisStr = rtss.toString(); thisStr = rtss.toString();
} catch (NumberFormatException ex) { } catch (NumberFormatException ex) {
LOG.atError().withThrowable(ex).log("Failed to parse SST index '{}'", sstIndex); LOG.atError().withThrowable(ex).log("Failed to parse SST index '{}'", sstIndex);
} }
} }
break; break;
case NUMBER: case NUMBER:
String n = value.toString(); String n = value.toString();
if (this.formatString != null && n.length() > 0) if (this.formatString != null && n.length() > 0)
thisStr = formatter.formatRawCellContents(Double.parseDouble(n), this.formatIndex, this.formatString); thisStr = formatter.formatRawCellContents(Double.parseDouble(n), this.formatIndex, this.formatString);
else else
thisStr = n; thisStr = n;
break; break;
default: default:
thisStr = "(TODO: Unexpected type: " + nextDataType + ")"; thisStr = "(TODO: Unexpected type: " + nextDataType + ")";
break; break;
} }
// Do we have a comment for this cell? // Do we have a comment for this cell?
checkForEmptyCellComments(EmptyCellCommentsCheckType.CELL); checkForEmptyCellComments(EmptyCellCommentsCheckType.CELL);
XSSFComment comment = comments != null ? comments.findCellComment(new CellAddress(cellRef)) : null; XSSFComment comment = comments != null ? comments.findCellComment(new CellAddress(cellRef)) : null;
// Output // Output
output.cell(cellRef, thisStr, comment); output.cell(cellRef, thisStr, comment);
} }
/** /**
* Do a check for, and output, comments in otherwise empty cells. * Do a check for, and output, comments in otherwise empty cells.
*/ */
private void checkForEmptyCellComments(EmptyCellCommentsCheckType type) { private void checkForEmptyCellComments(EmptyCellCommentsCheckType type) {
if (commentCellRefs != null && !commentCellRefs.isEmpty()) { if (commentCellRefs != null && !commentCellRefs.isEmpty()) {
// If we've reached the end of the sheet data, output any // If we've reached the end of the sheet data, output any
// comments we haven't yet already handled // comments we haven't yet already handled
if (type == EmptyCellCommentsCheckType.END_OF_SHEET_DATA) { if (type == EmptyCellCommentsCheckType.END_OF_SHEET_DATA) {
while (!commentCellRefs.isEmpty()) { while (!commentCellRefs.isEmpty()) {
outputEmptyCellComment(commentCellRefs.remove()); outputEmptyCellComment(commentCellRefs.remove());
} }
return; return;
} }
// At the end of a row, handle any comments for "missing" rows before us // At the end of a row, handle any comments for "missing" rows before us
if (this.cellRef == null) { if (this.cellRef == null) {
if (type == EmptyCellCommentsCheckType.END_OF_ROW) { if (type == EmptyCellCommentsCheckType.END_OF_ROW) {
while (!commentCellRefs.isEmpty()) { while (!commentCellRefs.isEmpty()) {
if (commentCellRefs.peek().getRow() == rowNum) { if (commentCellRefs.peek().getRow() == rowNum) {
outputEmptyCellComment(commentCellRefs.remove()); outputEmptyCellComment(commentCellRefs.remove());
} else { } else {
return; return;
} }
} }
return; return;
} else { } else {
throw new IllegalStateException("Cell ref should be null only if there are only empty cells in the row; rowNum: " + rowNum); throw new IllegalStateException("Cell ref should be null only if there are only empty cells in the row; rowNum: " + rowNum);
} }
} }
CellAddress nextCommentCellRef; CellAddress nextCommentCellRef;
do { do {
CellAddress cellRef = new CellAddress(this.cellRef); CellAddress cellRef = new CellAddress(this.cellRef);
CellAddress peekCellRef = commentCellRefs.peek(); CellAddress peekCellRef = commentCellRefs.peek();
if (type == EmptyCellCommentsCheckType.CELL && cellRef.equals(peekCellRef)) { if (type == EmptyCellCommentsCheckType.CELL && cellRef.equals(peekCellRef)) {
// remove the comment cell ref from the list if we're about to handle it alongside the cell content // remove the comment cell ref from the list if we're about to handle it alongside the cell content
commentCellRefs.remove(); commentCellRefs.remove();
return; return;
} else { } else {
// fill in any gaps if there are empty cells with comment mixed in with non-empty cells // fill in any gaps if there are empty cells with comment mixed in with non-empty cells
int comparison = peekCellRef.compareTo(cellRef); int comparison = peekCellRef.compareTo(cellRef);
if (comparison > 0 && type == EmptyCellCommentsCheckType.END_OF_ROW && peekCellRef.getRow() <= rowNum) { if (comparison > 0 && type == EmptyCellCommentsCheckType.END_OF_ROW && peekCellRef.getRow() <= rowNum) {
nextCommentCellRef = commentCellRefs.remove(); nextCommentCellRef = commentCellRefs.remove();
outputEmptyCellComment(nextCommentCellRef); outputEmptyCellComment(nextCommentCellRef);
} else if (comparison < 0 && type == EmptyCellCommentsCheckType.CELL && peekCellRef.getRow() <= rowNum) { } else if (comparison < 0 && type == EmptyCellCommentsCheckType.CELL && peekCellRef.getRow() <= rowNum) {
nextCommentCellRef = commentCellRefs.remove(); nextCommentCellRef = commentCellRefs.remove();
outputEmptyCellComment(nextCommentCellRef); outputEmptyCellComment(nextCommentCellRef);
} else { } else {
nextCommentCellRef = null; nextCommentCellRef = null;
} }
} }
} while (nextCommentCellRef != null && !commentCellRefs.isEmpty()); } while (nextCommentCellRef != null && !commentCellRefs.isEmpty());
} }
} }
/** /**
* Output an empty-cell comment. * Output an empty-cell comment.
*/ */
private void outputEmptyCellComment(CellAddress cellRef) { private void outputEmptyCellComment(CellAddress cellRef) {
XSSFComment comment = comments.findCellComment(cellRef); XSSFComment comment = comments.findCellComment(cellRef);
output.cell(cellRef.formatAsString(), null, comment); output.cell(cellRef.formatAsString(), null, comment);
} }
private enum EmptyCellCommentsCheckType { private enum EmptyCellCommentsCheckType {
CELL, CELL,
END_OF_ROW, END_OF_ROW,
END_OF_SHEET_DATA END_OF_SHEET_DATA
} }
/** /**
* This interface allows to provide callbacks when reading * This interface allows to provide callbacks when reading
* a sheet in streaming mode. * a sheet in streaming mode.
* * <p>
* The XSLX file is usually read via {@link XSSFReader}. * The XSLX file is usually read via {@link XSSFReader}.
* * <p>
* By implementing the methods, you can process arbitrarily * By implementing the methods, you can process arbitrarily
* large files without exhausting main memory. * large files without exhausting main memory.
*/ */
public interface SheetContentsHandler { public interface SheetContentsHandler {
/** A row with the (zero based) row number has started */ /**
void startRow(int rowNum); * A row with the (zero based) row number has started
*/
void startRow(int rowNum);
/** A row with the (zero based) row number has ended */ /**
void endRow(int rowNum); * A row with the (zero based) row number has ended
*/
void endRow(int rowNum);
/** /**
* A cell, with the given formatted value (may be null), * A cell, with the given formatted value (may be null),
* and possibly a comment (may be null), was encountered. * and possibly a comment (may be null), was encountered.
* * <p>
* Sheets that have missing or empty cells may result in * Sheets that have missing or empty cells may result in
* sparse calls to <code>cell</code>. See the code 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> * <code>poi-examples/src/main/java/org/apache/poi/xssf/eventusermodel/XLSX2CSV.java</code>
* for an example of how to handle this scenario. * for an example of how to handle this scenario.
*/ */
void cell(String cellReference, String formattedValue, XSSFComment comment); void cell(String cellReference, String formattedValue, XSSFComment comment);
/** A header or footer has been encountered */ /**
default void headerFooter(String text, boolean isHeader, String tagName) {} * A header or footer has been encountered
*/
default void headerFooter(String text, boolean isHeader, String tagName) {
}
/** Signal that the end of a sheet was been reached */ /**
default void endSheet() {} * Signal that the end of a sheet was been reached
} */
default void endSheet() {
}
}
} }