package org.apache.poi.xssf.binary;
import java.io.InputStream;
import java.util.Queue;
import org.apache.poi.ss.usermodel.BuiltinFormats;
import org.apache.poi.ss.usermodel.DataFormatter;
import org.apache.poi.ss.usermodel.RichTextString;
import org.apache.poi.ss.util.CellAddress;
import org.apache.poi.util.Internal;
import org.apache.poi.util.LittleEndian;
import org.apache.poi.xssf.eventusermodel.XSSFSheetXMLHandler;
import org.apache.poi.xssf.model.SharedStrings;
import org.apache.poi.xssf.usermodel.XSSFComment;
@Internal
public class XSSFBSheetHandler extends XSSFBParser {
private static final int CHECK_ALL_ROWS = -1;
private final SharedStrings stringsTable;
private final XSSFSheetXMLHandler.SheetContentsHandler handler;
private final XSSFBStylesTable styles;
private final XSSFBCommentsTable comments;
private final DataFormatter dataFormatter;
private final boolean formulasNotResults;
private int lastEndedRow = -1;
private int lastStartedRow = -1;
private int currentRow;
private byte[] rkBuffer = new byte[8];
private XSSFBCellRange hyperlinkCellRange;
private StringBuilder xlWideStringBuffer = new StringBuilder();
private final XSSFBCellHeader cellBuffer = new XSSFBCellHeader();
public XSSFBSheetHandler(InputStream is,
XSSFBStylesTable styles,
XSSFBCommentsTable comments,
SharedStrings strings,
XSSFSheetXMLHandler.SheetContentsHandler sheetContentsHandler,
DataFormatter dataFormatter,
boolean formulasNotResults) {
super(is);
this.styles = styles;
this.comments = comments;
this.stringsTable = strings;
this.handler = sheetContentsHandler;
this.dataFormatter = dataFormatter;
this.formulasNotResults = formulasNotResults;
}
@Override
public void handleRecord(int id, byte[] data) throws XSSFBParseException {
XSSFBRecordType type = XSSFBRecordType.lookup(id);
switch(type) {
case BrtRowHdr:
int rw = XSSFBUtils.castToInt(LittleEndian.getUInt(data, 0));
if (rw > 0x00100000) {
throw new XSSFBParseException("Row number beyond allowable range: "+rw);
}
currentRow = rw;
checkMissedComments(currentRow);
startRow(currentRow);
break;
case BrtCellIsst:
handleBrtCellIsst(data);
break;
case BrtCellSt:
handleCellSt(data);
break;
case BrtCellRk:
handleCellRk(data);
break;
case BrtCellReal:
handleCellReal(data);
break;
case BrtCellBool:
handleBoolean(data);
break;
case BrtCellError:
handleCellError(data);
break;
case BrtCellBlank:
beforeCellValue(data);
break;
case BrtFmlaString:
handleFmlaString(data);
break;
case BrtFmlaNum:
handleFmlaNum(data);
break;
case BrtFmlaError:
handleFmlaError(data);
break;
case BrtEndSheetData:
checkMissedComments(CHECK_ALL_ROWS);
endRow(lastStartedRow);
break;
case BrtBeginHeaderFooter:
handleHeaderFooter(data);
break;
}
}
private void beforeCellValue(byte[] data) {
XSSFBCellHeader.parse(data, 0, currentRow, cellBuffer);
checkMissedComments(currentRow, cellBuffer.getColNum());
}
private void handleCellValue(String formattedValue) {
CellAddress cellAddress = new CellAddress(currentRow, cellBuffer.getColNum());
XSSFBComment comment = null;
if (comments != null) {
comment = comments.get(cellAddress);
}
handler.cell(cellAddress.formatAsString(), formattedValue, comment);
}
private void handleFmlaNum(byte[] data) {
beforeCellValue(data);
double val = LittleEndian.getDouble(data, XSSFBCellHeader.length);
handleCellValue(formatVal(val, cellBuffer.getStyleIdx()));
}
private void handleCellSt(byte[] data) {
beforeCellValue(data);
xlWideStringBuffer.setLength(0);
XSSFBUtils.readXLWideString(data, XSSFBCellHeader.length, xlWideStringBuffer);
handleCellValue(xlWideStringBuffer.toString());
}
private void handleFmlaString(byte[] data) {
beforeCellValue(data);
xlWideStringBuffer.setLength(0);
XSSFBUtils.readXLWideString(data, XSSFBCellHeader.length, xlWideStringBuffer);
handleCellValue(xlWideStringBuffer.toString());
}
private void handleCellError(byte[] data) {
beforeCellValue(data);
handleCellValue("ERROR");
}
private void handleFmlaError(byte[] data) {
beforeCellValue(data);
handleCellValue("ERROR");
}
private void handleBoolean(byte[] data) {
beforeCellValue(data);
String formattedVal = (data[XSSFBCellHeader.length] == 1) ? "TRUE" : "FALSE";
handleCellValue(formattedVal);
}
private void handleCellReal(byte[] data) {
beforeCellValue(data);
double val = LittleEndian.getDouble(data, XSSFBCellHeader.length);
handleCellValue(formatVal(val, cellBuffer.getStyleIdx()));
}
private void handleCellRk(byte[] data) {
beforeCellValue(data);
double val = rkNumber(data, XSSFBCellHeader.length);
handleCellValue(formatVal(val, cellBuffer.getStyleIdx()));
}
private String formatVal(double val, int styleIdx) {
String formatString = styles.getNumberFormatString(styleIdx);
short styleIndex = styles.getNumberFormatIndex(styleIdx);
if (formatString == null) {
formatString = BuiltinFormats.getBuiltinFormat(0);
styleIndex = 0;
}
return dataFormatter.formatRawCellContents(val, styleIndex, formatString);
}
private void handleBrtCellIsst(byte[] data) {
beforeCellValue(data);
int idx = XSSFBUtils.castToInt(LittleEndian.getUInt(data, XSSFBCellHeader.length));
RichTextString rtss = stringsTable.getItemAt(idx);
handleCellValue(rtss.getString());
}
private void handleHeaderFooter(byte[] data) {
XSSFBHeaderFooters headerFooter = XSSFBHeaderFooters.parse(data);
outputHeaderFooter(headerFooter.getHeader());
outputHeaderFooter(headerFooter.getFooter());
outputHeaderFooter(headerFooter.getHeaderEven());
outputHeaderFooter(headerFooter.getFooterEven());
outputHeaderFooter(headerFooter.getHeaderFirst());
outputHeaderFooter(headerFooter.getFooterFirst());
}
private void outputHeaderFooter(XSSFBHeaderFooter headerFooter) {
String text = headerFooter.getString();
if (text != null && text.trim().length() > 0) {
handler.headerFooter(text, headerFooter.isHeader(), headerFooter.getHeaderFooterTypeLabel());
}
}
private void checkMissedComments(int currentRow, int colNum) {
if (comments == null) {
return;
}
Queue<CellAddress> queue = comments.getAddresses();
while (queue.size() > 0) {
CellAddress cellAddress = queue.peek();
if (cellAddress.getRow() == currentRow && cellAddress.getColumn() < colNum) {
cellAddress = queue.remove();
dumpEmptyCellComment(cellAddress, comments.get(cellAddress));
} else if (cellAddress.getRow() == currentRow && cellAddress.getColumn() == colNum) {
queue.remove();
return;
} else if (cellAddress.getRow() == currentRow && cellAddress.getColumn() > colNum) {
return;
} else if (cellAddress.getRow() > currentRow) {
return;
}
}
}
private void checkMissedComments(int currentRow) {
if (comments == null) {
return;
}
Queue<CellAddress> queue = comments.getAddresses();
int lastInterpolatedRow = -1;
while (queue.size() > 0) {
CellAddress cellAddress = queue.peek();
if (currentRow == CHECK_ALL_ROWS || cellAddress.getRow() < currentRow) {
cellAddress = queue.remove();
if (cellAddress.getRow() != lastInterpolatedRow) {
startRow(cellAddress.getRow());
}
dumpEmptyCellComment(cellAddress, comments.get(cellAddress));
lastInterpolatedRow = cellAddress.getRow();
} else {
break;
}
}
}
private void startRow(int row) {
if (row == lastStartedRow) {
return;
}
if (lastStartedRow != lastEndedRow) {
endRow(lastStartedRow);
}
handler.startRow(row);
lastStartedRow = row;
}
private void endRow(int row) {
if (lastEndedRow == row) {
return;
}
handler.endRow(row);
lastEndedRow = row;
}
private void dumpEmptyCellComment(CellAddress cellAddress, XSSFBComment comment) {
handler.cell(cellAddress.formatAsString(), null, comment);
}
private double rkNumber(byte[] data, int offset) {
byte b0 = data[offset];
boolean numDivBy100 = ((b0 & 1) == 1);
boolean floatingPoint = ((b0 >> 1 & 1) == 0);
b0 &= ~1;
b0 &= ~(1<<1);
rkBuffer[4] = b0;
for (int i = 1; i < 4; i++) {
rkBuffer[i+4] = data[offset+i];
}
double d = 0.0;
if (floatingPoint) {
d = LittleEndian.getDouble(rkBuffer);
} else {
int rawInt = LittleEndian.getInt(rkBuffer, 4);
d = rawInt >> 2;
}
d = (numDivBy100) ? d/100 : d;
return d;
}
public interface SheetContentsHandler extends XSSFSheetXMLHandler.SheetContentsHandler {
void hyperlinkCell(String cellReference, String formattedValue, String url, String toolTip, XSSFComment comment);
}
}