瀏覽代碼

bug 57840: add javadocs (warn about stale XSSFTable cache), remove rowIndex argument from calls to FormulaParser.parse(String, FormulaParsingWorkbook, FormulaType, int sheetIndex) when rowIndex is irrelevant, clear xmlColumnPr and commonXPath during updateHeaders so that users have a mechanism to clear invalidated cached values

git-svn-id: https://svn.apache.org/repos/asf/poi/branches/xssf_structured_references@1747627 13f79535-47bb-0310-9956-ffa450edef68
tags/REL_3_15_BETA2
Javen O'Neal 8 年之前
父節點
當前提交
1a941fcd69

+ 0
- 1
src/java/org/apache/poi/ss/formula/EvaluationWorkbook.java 查看文件

@@ -68,7 +68,6 @@ public interface EvaluationWorkbook {
*/
ExternalName getExternalName(String nameName, String sheetName, int externalWorkbookNumber);
EvaluationName getName(NamePtg namePtg);
EvaluationName getName(String name, int sheetIndex);
String resolveNameXText(NameXPtg ptg);

+ 29
- 12
src/java/org/apache/poi/ss/formula/FormulaParser.java 查看文件

@@ -145,17 +145,20 @@ public final class FormulaParser {
}

/**
* 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
@@ -165,7 +168,22 @@ public final class FormulaParser {
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);
}
@@ -180,7 +198,8 @@ public final class FormulaParser {
* @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");
}
@@ -588,14 +607,14 @@ public final class FormulaParser {
* @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();
@@ -845,11 +864,9 @@ public final class FormulaParser {
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")));
}
@@ -875,7 +892,7 @@ public final class FormulaParser {

// 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);

+ 2
- 11
src/java/org/apache/poi/ss/formula/WorkbookEvaluator.java 查看文件

@@ -299,15 +299,6 @@ public final class WorkbookEvaluator {
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 {
@@ -319,9 +310,9 @@ public final class WorkbookEvaluator {
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:

+ 8
- 9
src/java/org/apache/poi/ss/formula/functions/Indirect.java 查看文件

@@ -96,9 +96,6 @@ public final class Indirect implements FreeRefFunction {
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('!');

@@ -119,17 +116,19 @@ public final class Indirect implements FreeRefFunction {
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();

+ 6
- 2
src/ooxml/java/org/apache/poi/xssf/usermodel/BaseXSSFEvaluationWorkbook.java 查看文件

@@ -315,6 +315,10 @@ public abstract class BaseXSSFEvaluationWorkbook implements FormulaRenderingWork
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
@@ -336,7 +340,7 @@ public abstract class BaseXSSFEvaluationWorkbook implements FormulaRenderingWork

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);
}
}
@@ -356,7 +360,7 @@ public abstract class BaseXSSFEvaluationWorkbook implements FormulaRenderingWork
@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);
}

+ 1
- 1
src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFCell.java 查看文件

@@ -550,7 +550,7 @@ public final class XSSFCell implements Cell {

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);

+ 55
- 14
src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFTable.java 查看文件

@@ -26,6 +26,7 @@ import java.util.ArrayList;
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;
@@ -33,6 +34,7 @@ import org.apache.poi.openxml4j.opc.PackageRelationship;
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;
@@ -121,20 +123,22 @@ public class XSSFTable extends POIXMLDocumentPart implements Table {
* @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();
@@ -146,6 +150,7 @@ public class XSSFTable extends POIXMLDocumentPart implements Table {
*
* 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
*/
@@ -176,10 +181,8 @@ public class XSSFTable extends POIXMLDocumentPart implements Table {
}
}

commonXPath = "";
for (int i = 1 ; i< commonTokens.length;i++) {
commonXPath +="/"+commonTokens[i];
}
commonTokens[0] = "";
commonXPath = StringUtil.join(commonTokens, "/");
}
return commonXPath;
@@ -188,6 +191,7 @@ public class XSSFTable extends POIXMLDocumentPart implements Table {
/**
* 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() {
@@ -297,7 +301,7 @@ public class XSSFTable extends POIXMLDocumentPart implements Table {
* 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;
@@ -317,43 +321,80 @@ public class XSSFTable extends POIXMLDocumentPart implements Table {
}
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();
}

+ 2
- 1
src/ooxml/java/org/apache/poi/xssf/usermodel/helpers/XSSFFormulaUtils.java 查看文件

@@ -113,7 +113,8 @@ public final class XSSFFormulaUtils {
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);
}

+ 6
- 3
src/ooxml/java/org/apache/poi/xssf/usermodel/helpers/XSSFRowShifter.java 查看文件

@@ -134,8 +134,9 @@ public final class XSSFRowShifter {
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);
@@ -218,10 +219,11 @@ public final class XSSFRowShifter {
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);
@@ -238,6 +240,7 @@ public final class XSSFRowShifter {
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();
@@ -283,7 +286,7 @@ public final class XSSFRowShifter {
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);

+ 1
- 1
src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFFormulaParser.java 查看文件

@@ -42,7 +42,7 @@ import java.util.Arrays;

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

+ 3
- 3
src/testcases/org/apache/poi/hssf/model/TestFormulaParser.java 查看文件

@@ -1197,7 +1197,7 @@ public final class TestFormulaParser {
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
@@ -1486,8 +1486,8 @@ public final class TestFormulaParser {

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.");

+ 1
- 1
src/testcases/org/apache/poi/hssf/record/TestArrayRecord.java 查看文件

@@ -51,7 +51,7 @@ public final class TestArrayRecord extends TestCase {
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

+ 2
- 2
src/testcases/org/apache/poi/hssf/record/TestSharedFormulaRecord.java 查看文件

@@ -107,7 +107,7 @@ public final class TestSharedFormulaRecord extends TestCase {

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
@@ -123,7 +123,7 @@ public final class TestSharedFormulaRecord extends TestCase {
//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));

Loading…
取消
儲存