*/
ExternalName getExternalName(String nameName, String sheetName, int externalWorkbookNumber);
-
EvaluationName getName(NamePtg namePtg);
EvaluationName getName(String name, int sheetIndex);
String resolveNameXText(NameXPtg ptg);
}
/**
- * Parse a formula into a array of tokens
- * Side effect: creates name (Workbook.createName) if formula contains unrecognized names (names are likely UDFs)
+ * Parse a formula into an array of tokens
+ * Side effect: creates name ({@link org.apache.poi.ss.usermodel.Workbook#createName})
+ * if formula contains unrecognized names (names are likely UDFs)
*
* @param formula the formula to parse
* @param workbook the parent workbook
* @param formulaType the type of the formula, see {@link FormulaType}
* @param sheetIndex the 0-based index of the sheet this formula belongs to.
+ * The sheet index is required to resolve sheet-level names. <code>-1</code> means that
+ * the scope of the name will be ignored and the parser will match names only by name
* @param rowIndex - the related cell's row index in 0-based form (-1 if the formula is not cell related)
* used to handle structured references that have the "#This Row" quantifier.
- * The sheet index is required to resolve sheet-level names. <code>-1</code> means that
- * the scope of the name will be ignored and the parser will match names only by name
+ * Use rowIndex=-1 or {@link #parseStructuredReference(String, FormulaParsingWorkbook, int, int) if formula
+ * does not contain structured references.
*
* @return array of parsed tokens
* @throws FormulaParseException if the formula has incorrect syntax or is otherwise invalid
fp.parse();
return fp.getRPNPtg(formulaType);
}
-
+
+ /**
+ * Parse a formula into an array of tokens
+ * Side effect: creates name ({@link org.apache.poi.ss.usermodel.Workbook#createName})
+ * if formula contains unrecognized names (names are likely UDFs)
+ *
+ * @param formula the formula to parse
+ * @param workbook the parent workbook
+ * @param formulaType the type of the formula, see {@link FormulaType}
+ * @param sheetIndex the 0-based index of the sheet this formula belongs to.
+ * The sheet index is required to resolve sheet-level names. <code>-1</code> means that
+ * the scope of the name will be ignored and the parser will match names only by name
+ *
+ * @return array of parsed tokens
+ * @throws FormulaParseException if the formula has incorrect syntax or is otherwise invalid
+ */
public static Ptg[] parse(String formula, FormulaParsingWorkbook workbook, int formulaType, int sheetIndex) {
return parse(formula, workbook, formulaType, sheetIndex, -1);
}
* @return the area that being represented by the structured reference.
*/
public static Area3DPxg parseStructuredReference(String tableText, FormulaParsingWorkbook workbook, int rowIndex) {
- Ptg[] arr = FormulaParser.parse(tableText, workbook, 0, 0, rowIndex);
+ final int sheetIndex = -1; //don't care?
+ Ptg[] arr = FormulaParser.parse(tableText, workbook, FormulaType.CELL, sheetIndex, rowIndex);
if (arr.length != 1 || !(arr[0] instanceof Area3DPxg) ) {
throw new IllegalStateException("Illegal structured reference");
}
* @param tableName
* @return
*/
- private ParseNode parseStructuredReference(String tableName){
+ private ParseNode parseStructuredReference(String tableName) {
if ( ! (_ssVersion.equals(SpreadsheetVersion.EXCEL2007)) ) {
- throw new FormulaParseException("Strctured references work only on XSSF (Excel 2007)!");
+ throw new FormulaParseException("Structured references work only on XSSF (Excel 2007+)!");
}
Table tbl = _book.getTable(tableName);
if (tbl == null) {
- throw new FormulaParseException("Illegal table name!");
+ throw new FormulaParseException("Illegal table name!");
}
String sheetName = tbl.getSheetName();
if (look == '(') {
return function(name);
}
- //TODO Livshen's code
if(look == '['){
return parseStructuredReference(name);
}
- //TODO End of Livshen's code
if (name.equalsIgnoreCase("TRUE") || name.equalsIgnoreCase("FALSE")) {
return new ParseNode(BoolPtg.valueOf(name.equalsIgnoreCase("TRUE")));
}
// defined names may begin with a letter or underscore or backslash
if (!Character.isLetter(look) && look != '_' && look != '\\') {
- throw expected("number, string, defined name, or table");
+ throw expected("number, string, defined name, or data table");
}
while (isValidDefinedNameChar(look)) {
sb.append(look);
try {
Ptg[] ptgs = _workbook.getFormulaTokens(srcCell);
-// System.out.println("=====");
-// XSSFCell c = ((XSSFEvaluationCell)srcCell).getXSSFCell();
-// System.out.println("Formula is "+ c);
-// System.out.println("The cell is " + c.getSheet().getSheetName()+"!"+c.getReference());
-// System.out.println("Evaluation tokens : "); // TODO Dlivshen remove
-// for (Ptg ptg : ptgs) { // TODO Dlivshen remove
-// System.out.println(ptg); // TODO Dlivshen remove
-// } // TODO Dlivshen remove
-// System.out.println("======"); // TODO Dlivshen remove
if (evalListener == null) {
result = evaluateFormula(ec, ptgs);
} else {
tracker.updateCacheResult(result);
}
catch (NotImplementedException e) {
- throw addExceptionInfo(e, sheetIndex, rowIndex, columnIndex);
+ throw addExceptionInfo(e, sheetIndex, rowIndex, columnIndex);
} catch (RuntimeException re) {
- if (re.getCause() instanceof WorkbookNotFoundException && _ignoreMissingWorkbooks) {
+ if (re.getCause() instanceof WorkbookNotFoundException && _ignoreMissingWorkbooks) {
logInfo(re.getCause().getMessage() + " - Continuing with cached value!");
switch(srcCell.getCachedFormulaResultType()) {
case Cell.CELL_TYPE_NUMERIC:
private static ValueEval evaluateIndirect(final OperationEvaluationContext ec, String text,
boolean isA1style) {
- ec.getRowIndex();
- ec.getColumnIndex();
-
// Search backwards for '!' because sheet names can contain '!'
int plingPos = text.lastIndexOf('!');
refText = text.substring(plingPos + 1);
}
- String refStrPart1;
- String refStrPart2;
- if (Table.isStructuredReference.matcher(refText).matches()) { // The argument is structured reference
+ if (Table.isStructuredReference.matcher(refText).matches()) {
+ // The argument is structured reference
Area3DPxg areaPtg = null;
- try{
+ try {
areaPtg = FormulaParser.parseStructuredReference(refText, (FormulaParsingWorkbook) ec.getWorkbook(), ec.getRowIndex());
- } catch(FormulaParseException e) {
+ } catch (FormulaParseException e) {
return ErrorEval.REF_INVALID;
}
return ec.getArea3DEval(areaPtg);
- } else { // The argumnet is regular reference
+ } else {
+ // The argument is regular reference
+ String refStrPart1;
+ String refStrPart2;
int colonPos = refText.indexOf(':');
if (colonPos < 0) {
refStrPart1 = refText.trim();
return _uBook.createName();
}
+ private static String caseInsensitive(String s) {
+ return s.toUpperCase(Locale.ROOT);
+ }
+
/*
* TODO: data tables are stored at the workbook level in XSSF, but are bound to a single sheet.
* The current code structure has them hanging off XSSFSheet, but formulas reference them
for (Sheet sheet : _uBook) {
for (XSSFTable tbl : ((XSSFSheet)sheet).getTables()) {
- String lname = tbl.getName().toLowerCase(Locale.ROOT);
+ String lname = caseInsensitive(tbl.getName());
_tableCache.put(lname, tbl);
}
}
@Override
public XSSFTable getTable(String name) {
if (name == null) return null;
- String lname = name.toLowerCase(Locale.ROOT);
+ String lname = caseInsensitive(name);
return getTableCache().get(lname);
}
XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(wb);
//validate through the FormulaParser
- FormulaParser.parse(formula, fpb, formulaType, wb.getSheetIndex(getSheet()), -1);
+ FormulaParser.parse(formula, fpb, formulaType, wb.getSheetIndex(getSheet()), getRowIndex());
CTCellFormula f = CTCellFormula.Factory.newInstance();
f.setStringValue(formula);
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
+import java.util.Locale;
import org.apache.poi.POIXMLDocumentPart;
import org.apache.poi.openxml4j.opc.PackagePart;
import org.apache.poi.ss.usermodel.Table;
import org.apache.poi.ss.util.CellReference;
import org.apache.poi.xssf.usermodel.helpers.XSSFXmlColumnPr;
+import org.apache.poi.util.StringUtil;
import org.apache.xmlbeans.XmlException;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTTable;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTTableColumn;
* @return true if the Table element contain mappings
*/
public boolean mapsTo(long id){
- boolean maps =false;
-
List<XSSFXmlColumnPr> pointers = getXmlColumnPrs();
for (XSSFXmlColumnPr pointer: pointers) {
if (pointer.getMapId()==id) {
- maps=true;
- break;
+ return true;
}
}
- return maps;
+ return false;
}
+ /**
+ * caches table columns for performance.
+ * Updated via updateHeaders
+ * @since 3.15 beta 2
+ */
private CTTableColumn[] getTableColumns() {
if (ctColumns == null) {
ctColumns = ctTable.getTableColumns().getTableColumnArray();
*
* Calculates the xpath of the root element for the table. This will be the common part
* of all the mapping's xpaths
+ * Note: this function caches the result for performance. To flush the cache {@link #updateHeaders()} must be called.
*
* @return the xpath of the table's root element
*/
}
}
- commonXPath = "";
- for (int i = 1 ; i< commonTokens.length;i++) {
- commonXPath +="/"+commonTokens[i];
- }
+ commonTokens[0] = "";
+ commonXPath = StringUtil.join(commonTokens, "/");
}
return commonXPath;
/**
* Note this list is static - once read, it does not notice later changes to the underlying column structures
+ * To clear the cache, call {@link #updateHeaders}
* @return List of XSSFXmlColumnPr
*/
public List<XSSFXmlColumnPr> getXmlColumnPrs() {
* Headers <em>must</em> be in sync, otherwise Excel will display a
* "Found unreadable content" message on startup.
*/
- public void updateHeaders(){
+ public void updateHeaders() {
XSSFSheet sheet = (XSSFSheet)getParent();
CellReference ref = getStartCellReference();
if(ref == null) return;
}
ctColumns = null;
columnMap = null;
+ xmlColumnPr = null;
+ commonXPath = null;
}
}
+ private static String caseInsensitive(String s) {
+ return s.toUpperCase(Locale.ROOT);
+ }
+
+ /**
+ * Gets the relative column index of a column in this table having the header name <code>column</code>.
+ * The column index is relative to the left-most column in the table, 0-indexed.
+ * Returns <code>-1</code> if <code>column</code> is not a header name in table.
+ *
+ * Note: this function caches column names for performance. To flush the cache (because columns
+ * have been moved or column headers have been changed), {@link #updateHeaders()} must be called.
+ *
+ * @since 3.15 beta 2
+ */
public int findColumnIndex(String column) {
if (columnMap == null) {
- columnMap = new HashMap<String, Integer>(getTableColumns().length);
+ // FIXME: replace with org.apache.commons.collections.map.CaseInsensitiveMap
+ int count = getTableColumns().length;
+ columnMap = new HashMap<String, Integer>(count);
- for (int i=0; i < getTableColumns().length; i++) {
- columnMap.put(getTableColumns()[i].getName().toUpperCase(), Integer.valueOf(i));
+ for (int i=0; i < count; i++) {
+ String columnName = getTableColumns()[i].getName();
+ columnMap.put(caseInsensitive(columnName), i);
}
}
// Table column names with special characters need a single quote escape
// but the escape is not present in the column definition
- Integer idx = columnMap.get(column.replace("'", "").toUpperCase());
+ Integer idx = columnMap.get(caseInsensitive(column.replace("'", "")));
return idx == null ? -1 : idx.intValue();
}
+ /**
+ * @since 3.15 beta 2
+ */
public String getSheetName() {
return getXSSFSheet().getSheetName();
}
+ /**
+ * @since 3.15 beta 2
+ */
public boolean isHasTotalsRow() {
return ctTable.getTotalsRowShown();
}
+ /**
+ * @since 3.15 beta 2
+ */
public int getStartColIndex() {
return getStartCellReference().getCol();
}
+ /**
+ * @since 3.15 beta 2
+ */
public int getStartRowIndex() {
return getStartCellReference().getRow();
}
+ /**
+ * @since 3.15 beta 2
+ */
public int getEndColIndex() {
return getEndCellReference().getCol();
}
+ /**
+ * @since 3.15 beta 2
+ */
public int getEndRowIndex() {
return getEndCellReference().getRow();
}
String formula = name.getRefersToFormula();
if (formula != null) {
int sheetIndex = name.getSheetIndex();
- Ptg[] ptgs = FormulaParser.parse(formula, _fpwb, FormulaType.NAMEDRANGE, sheetIndex, -1);
+ int rowIndex = -1; //don't care
+ Ptg[] ptgs = FormulaParser.parse(formula, _fpwb, FormulaType.NAMEDRANGE, sheetIndex, rowIndex);
for (Ptg ptg : ptgs) {
updatePtg(ptg, oldName, newName);
}
XSSFName name = wb.getNameAt(i);
String formula = name.getRefersToFormula();
int sheetIndex = name.getSheetIndex();
+ final int rowIndex = -1; //don't care, named ranges are not allowed to include structured references
- Ptg[] ptgs = FormulaParser.parse(formula, fpb, FormulaType.NAMEDRANGE, sheetIndex, -1);
+ Ptg[] ptgs = FormulaParser.parse(formula, fpb, FormulaType.NAMEDRANGE, sheetIndex, rowIndex);
if (shifter.adjustFormula(ptgs, sheetIndex)) {
String shiftedFmla = FormulaRenderer.toFormulaString(fpb, ptgs);
name.setRefersToFormula(shiftedFmla);
XSSFSheet sheet = row.getSheet();
XSSFWorkbook wb = sheet.getWorkbook();
int sheetIndex = wb.getSheetIndex(sheet);
+ final int rowIndex = row.getRowNum();
XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(wb);
try {
- Ptg[] ptgs = FormulaParser.parse(formula, fpb, FormulaType.CELL, sheetIndex, -1);
+ Ptg[] ptgs = FormulaParser.parse(formula, fpb, FormulaType.CELL, sheetIndex, rowIndex);
String shiftedFmla = null;
if (shifter.adjustFormula(ptgs, sheetIndex)) {
shiftedFmla = FormulaRenderer.toFormulaString(fpb, ptgs);
public void updateConditionalFormatting(FormulaShifter shifter) {
XSSFWorkbook wb = sheet.getWorkbook();
int sheetIndex = wb.getSheetIndex(sheet);
+ final int rowIndex = -1; //don't care, structured references not allowed in conditional formatting
XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(wb);
CTWorksheet ctWorksheet = sheet.getCTWorksheet();
String[] formulaArray = cfRule.getFormulaArray();
for (int i = 0; i < formulaArray.length; i++) {
String formula = formulaArray[i];
- Ptg[] ptgs = FormulaParser.parse(formula, fpb, FormulaType.CELL, sheetIndex, -1);
+ Ptg[] ptgs = FormulaParser.parse(formula, fpb, FormulaType.CELL, sheetIndex, rowIndex);
if (shifter.adjustFormula(ptgs, sheetIndex)) {
String shiftedFmla = FormulaRenderer.toFormulaString(fpb, ptgs);
cfRule.setFormulaArray(i, shiftedFmla);
public final class TestXSSFFormulaParser {
private static Ptg[] parse(FormulaParsingWorkbook fpb, String fmla) {
- return FormulaParser.parse(fmla, fpb, FormulaType.CELL, -1, -1);
+ return FormulaParser.parse(fmla, fpb, FormulaType.CELL, -1);
}
@Test
String formula = "Sheet1!$B$2:$C$3,OFFSET(Sheet1!$E$2:$E$4,1,Sheet1!$A$1),Sheet1!$D$6";
HSSFWorkbook wb = new HSSFWorkbook();
wb.createSheet("Sheet1");
- Ptg[] ptgs = FormulaParser.parse(formula, HSSFEvaluationWorkbook.create(wb), FormulaType.CELL, -1,-1);
+ Ptg[] ptgs = FormulaParser.parse(formula, HSSFEvaluationWorkbook.create(wb), FormulaType.CELL, -1);
confirmTokenClasses(ptgs,
// TODO - AttrPtg.class, // Excel prepends this
confirmParseError(wb, "A1:ROUND(B1,1)", "The RHS of the range operator ':' at position 3 is not a proper reference.");
- confirmParseError(wb, "Sheet1!!!", "Parse error near char 7 '!' in specified formula 'Sheet1!!!'. Expected number, string, defined name, or table");
- confirmParseError(wb, "Sheet1!.Name", "Parse error near char 7 '.' in specified formula 'Sheet1!.Name'. Expected number, string, defined name, or table");
+ confirmParseError(wb, "Sheet1!!!", "Parse error near char 7 '!' in specified formula 'Sheet1!!!'. Expected number, string, defined name, or data table");
+ confirmParseError(wb, "Sheet1!.Name", "Parse error near char 7 '.' in specified formula 'Sheet1!.Name'. Expected number, string, defined name, or data table");
confirmParseError(wb, "Sheet1!Sheet1", "Specified name 'Sheet1' for sheet Sheet1 not found");
confirmParseError(wb, "Sheet1!F:Sheet1!G", "'Sheet1!F' is not a proper reference.");
confirmParseError(wb, "Sheet1!F..foobar", "Complete area reference expected after sheet name at index 11.");
assertEquals("MAX(C1:C2-D1:D2)", FormulaRenderer.toFormulaString(null, ptg));
//construct a new ArrayRecord with the same contents as r1
- Ptg[] fmlaPtg = FormulaParser.parse("MAX(C1:C2-D1:D2)", null, FormulaType.ARRAY, 0, -1);
+ Ptg[] fmlaPtg = FormulaParser.parse("MAX(C1:C2-D1:D2)", null, FormulaType.ARRAY, 0);
ArrayRecord r2 = new ArrayRecord(Formula.create(fmlaPtg), new CellRangeAddress8Bit(1, 1, 1, 1));
byte[] ser = r2.serialize();
//serialize and check that the data is the same as in r1
SharedFormula sf = new SharedFormula(SpreadsheetVersion.EXCEL97);
- sharedFormula = FormulaParser.parse("A2", fpb, FormulaType.CELL, -1, -1);
+ sharedFormula = FormulaParser.parse("A2", fpb, FormulaType.CELL, -1);
convertedFormula = sf.convertSharedFormulas(sharedFormula, 0, 0);
confirmOperandClasses(sharedFormula, convertedFormula);
//conversion relative to [0,0] should return the original formula
//one row down and one cell right
assertEquals("B3", FormulaRenderer.toFormulaString(fpb, convertedFormula));
- sharedFormula = FormulaParser.parse("SUM(A1:C1)", fpb, FormulaType.CELL, -1, -1);
+ sharedFormula = FormulaParser.parse("SUM(A1:C1)", fpb, FormulaType.CELL, -1);
convertedFormula = sf.convertSharedFormulas(sharedFormula, 0, 0);
confirmOperandClasses(sharedFormula, convertedFormula);
assertEquals("SUM(A1:C1)", FormulaRenderer.toFormulaString(fpb, convertedFormula));