bug 57840: merge changes from ^/poi/branches/xssf_structured_references to ^/poi/trunk

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1747657 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Javen O'Neal 2016-06-10 07:41:09 +00:00
commit 12d7fb30a4
27 changed files with 5751 additions and 4867 deletions

View File

@ -31,53 +31,53 @@ import org.apache.poi.ss.formula.FormulaType;
*/ */
public final class HSSFFormulaParser { public final class HSSFFormulaParser {
private static FormulaParsingWorkbook createParsingWorkbook(HSSFWorkbook book) { private static FormulaParsingWorkbook createParsingWorkbook(HSSFWorkbook book) {
return HSSFEvaluationWorkbook.create(book); return HSSFEvaluationWorkbook.create(book);
} }
private HSSFFormulaParser() { private HSSFFormulaParser() {
// no instances of this class // no instances of this class
} }
/** /**
* Convenience method for parsing cell formulas. see {@link #parse(String, HSSFWorkbook, int, int)} * Convenience method for parsing cell formulas. see {@link #parse(String, HSSFWorkbook, int, int)}
*/ */
public static Ptg[] parse(String formula, HSSFWorkbook workbook) throws FormulaParseException { public static Ptg[] parse(String formula, HSSFWorkbook workbook) throws FormulaParseException {
return parse(formula, workbook, FormulaType.CELL); return parse(formula, workbook, FormulaType.CELL);
} }
/** /**
* @param formulaType a constant from {@link FormulaType} * @param formulaType a constant from {@link FormulaType}
* @return the parsed formula tokens * @return the parsed formula tokens
* @throws FormulaParseException if the formula has incorrect syntax or is otherwise invalid * @throws FormulaParseException if the formula has incorrect syntax or is otherwise invalid
*/ */
public static Ptg[] parse(String formula, HSSFWorkbook workbook, int formulaType) throws FormulaParseException { public static Ptg[] parse(String formula, HSSFWorkbook workbook, int formulaType) throws FormulaParseException {
return parse(formula, workbook, formulaType, -1); return parse(formula, workbook, formulaType, -1);
} }
/** /**
* @param formula the formula to parse * @param formula the formula to parse
* @param workbook the parent workbook * @param workbook the parent workbook
* @param formulaType a constant from {@link FormulaType} * @param formulaType a constant from {@link FormulaType}
* @param sheetIndex the 0-based index of the sheet this formula belongs to. * @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 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 named ranges only by name * the scope of the name will be ignored and the parser will match named ranges only by name
* *
* @return the parsed formula tokens * @return the parsed formula tokens
* @throws FormulaParseException if the formula has incorrect syntax or is otherwise invalid * @throws FormulaParseException if the formula has incorrect syntax or is otherwise invalid
*/ */
public static Ptg[] parse(String formula, HSSFWorkbook workbook, int formulaType, int sheetIndex) throws FormulaParseException { public static Ptg[] parse(String formula, HSSFWorkbook workbook, int formulaType, int sheetIndex) throws FormulaParseException {
return FormulaParser.parse(formula, createParsingWorkbook(workbook), formulaType, sheetIndex); return FormulaParser.parse(formula, createParsingWorkbook(workbook), formulaType, sheetIndex);
} }
/** /**
* Static method to convert an array of {@link Ptg}s in RPN order * Static method to convert an array of {@link Ptg}s in RPN order
* to a human readable string format in infix mode. * to a human readable string format in infix mode.
* @param book used for defined names and 3D references * @param book used for defined names and 3D references
* @param ptgs must not be <code>null</code> * @param ptgs must not be <code>null</code>
* @return a human readable String * @return a human readable String
*/ */
public static String toFormulaString(HSSFWorkbook book, Ptg[] ptgs) { public static String toFormulaString(HSSFWorkbook book, Ptg[] ptgs) {
return FormulaRenderer.toFormulaString(HSSFEvaluationWorkbook.create(book), ptgs); return FormulaRenderer.toFormulaString(HSSFEvaluationWorkbook.create(book), ptgs);
} }
} }

View File

@ -6,7 +6,7 @@
(the "License"); you may not use this file except in compliance with (the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0 http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, distributed under the License is distributed on an "AS IS" BASIS,
@ -38,6 +38,7 @@ import org.apache.poi.ss.formula.ptg.NameXPtg;
import org.apache.poi.ss.formula.ptg.Ptg; import org.apache.poi.ss.formula.ptg.Ptg;
import org.apache.poi.ss.formula.ptg.Ref3DPtg; import org.apache.poi.ss.formula.ptg.Ref3DPtg;
import org.apache.poi.ss.formula.udf.UDFFinder; import org.apache.poi.ss.formula.udf.UDFFinder;
import org.apache.poi.ss.usermodel.Table;
import org.apache.poi.ss.util.AreaReference; import org.apache.poi.ss.util.AreaReference;
import org.apache.poi.ss.util.CellReference; import org.apache.poi.ss.util.CellReference;
import org.apache.poi.util.POILogFactory; import org.apache.poi.util.POILogFactory;
@ -264,7 +265,16 @@ public final class HSSFEvaluationWorkbook implements FormulaRenderingWorkbook, E
return extIx; return extIx;
} }
@Override
public SpreadsheetVersion getSpreadsheetVersion(){ public SpreadsheetVersion getSpreadsheetVersion(){
return SpreadsheetVersion.EXCEL97; return SpreadsheetVersion.EXCEL97;
} }
/**
* @throws IllegalStateException: data tables are not supported in Excel 97-2003 format
*/
@Override
public Table getTable(String name) {
throw new IllegalStateException("XSSF-style tables are not supported for HSSF");
}
} }

File diff suppressed because it is too large Load Diff

View File

@ -20,6 +20,7 @@ package org.apache.poi.ss.formula;
import org.apache.poi.ss.SpreadsheetVersion; import org.apache.poi.ss.SpreadsheetVersion;
import org.apache.poi.ss.formula.ptg.Ptg; import org.apache.poi.ss.formula.ptg.Ptg;
import org.apache.poi.ss.usermodel.Name; import org.apache.poi.ss.usermodel.Name;
import org.apache.poi.ss.usermodel.Table;
import org.apache.poi.ss.util.AreaReference; import org.apache.poi.ss.util.AreaReference;
import org.apache.poi.ss.util.CellReference; import org.apache.poi.ss.util.CellReference;
@ -31,46 +32,51 @@ import org.apache.poi.ss.util.CellReference;
* @author Josh Micich * @author Josh Micich
*/ */
public interface FormulaParsingWorkbook { public interface FormulaParsingWorkbook {
/** /**
* named range name matching is case insensitive * named range name matching is case insensitive
*/ */
EvaluationName getName(String name, int sheetIndex); EvaluationName getName(String name, int sheetIndex);
/** /**
* Return the underlying workbook * Return the underlying workbook
*/ */
Name createName(); Name createName();
/** /**
* Return an external name (named range, function, user-defined function) Ptg * XSSF Only - gets a table that exists in the worksheet
*/ */
Ptg getNameXPtg(String name, SheetIdentifier sheet); Table getTable(String name);
/** /**
* Produce the appropriate Ptg for a 3d cell reference * Return an external name (named range, function, user-defined function) Ptg
*/ */
Ptg get3DReferencePtg(CellReference cell, SheetIdentifier sheet); Ptg getNameXPtg(String name, SheetIdentifier sheet);
/**
* Produce the appropriate Ptg for a 3d cell reference
*/
Ptg get3DReferencePtg(CellReference cell, SheetIdentifier sheet);
/** /**
* Produce the appropriate Ptg for a 3d area reference * Produce the appropriate Ptg for a 3d area reference
*/ */
Ptg get3DReferencePtg(AreaReference area, SheetIdentifier sheet); Ptg get3DReferencePtg(AreaReference area, SheetIdentifier sheet);
/** /**
* gets the externSheet index for a sheet from this workbook * gets the externSheet index for a sheet from this workbook
*/ */
int getExternalSheetIndex(String sheetName); int getExternalSheetIndex(String sheetName);
/** /**
* gets the externSheet index for a sheet from an external workbook * gets the externSheet index for a sheet from an external workbook
* @param workbookName e.g. "Budget.xls" * @param workbookName e.g. "Budget.xls"
* @param sheetName a name of a sheet in that workbook * @param sheetName a name of a sheet in that workbook
*/ */
int getExternalSheetIndex(String workbookName, String sheetName); int getExternalSheetIndex(String workbookName, String sheetName);
/** /**
* Returns an enum holding spreadhseet properties specific to an Excel version ( * Returns an enum holding spreadhseet properties specific to an Excel version (
* max column and row numbers, max arguments to a function, etc.) * max column and row numbers, max arguments to a function, etc.)
*/ */
SpreadsheetVersion getSpreadsheetVersion(); SpreadsheetVersion getSpreadsheetVersion();
} }

View File

@ -46,199 +46,199 @@ import org.apache.poi.ss.util.CellReference.NameType;
* For POI internal use only * For POI internal use only
*/ */
public final class OperationEvaluationContext { public final class OperationEvaluationContext {
public static final FreeRefFunction UDF = UserDefinedFunction.instance; public static final FreeRefFunction UDF = UserDefinedFunction.instance;
private final EvaluationWorkbook _workbook; private final EvaluationWorkbook _workbook;
private final int _sheetIndex; private final int _sheetIndex;
private final int _rowIndex; private final int _rowIndex;
private final int _columnIndex; private final int _columnIndex;
private final EvaluationTracker _tracker; private final EvaluationTracker _tracker;
private final WorkbookEvaluator _bookEvaluator; private final WorkbookEvaluator _bookEvaluator;
public OperationEvaluationContext(WorkbookEvaluator bookEvaluator, EvaluationWorkbook workbook, int sheetIndex, int srcRowNum, public OperationEvaluationContext(WorkbookEvaluator bookEvaluator, EvaluationWorkbook workbook, int sheetIndex, int srcRowNum,
int srcColNum, EvaluationTracker tracker) { int srcColNum, EvaluationTracker tracker) {
_bookEvaluator = bookEvaluator; _bookEvaluator = bookEvaluator;
_workbook = workbook; _workbook = workbook;
_sheetIndex = sheetIndex; _sheetIndex = sheetIndex;
_rowIndex = srcRowNum; _rowIndex = srcRowNum;
_columnIndex = srcColNum; _columnIndex = srcColNum;
_tracker = tracker; _tracker = tracker;
} }
public EvaluationWorkbook getWorkbook() { public EvaluationWorkbook getWorkbook() {
return _workbook; return _workbook;
} }
public int getRowIndex() { public int getRowIndex() {
return _rowIndex; return _rowIndex;
} }
public int getColumnIndex() { public int getColumnIndex() {
return _columnIndex; return _columnIndex;
} }
SheetRangeEvaluator createExternSheetRefEvaluator(ExternSheetReferenceToken ptg) { SheetRangeEvaluator createExternSheetRefEvaluator(ExternSheetReferenceToken ptg) {
return createExternSheetRefEvaluator(ptg.getExternSheetIndex()); return createExternSheetRefEvaluator(ptg.getExternSheetIndex());
} }
SheetRangeEvaluator createExternSheetRefEvaluator(String firstSheetName, String lastSheetName, int externalWorkbookNumber) { SheetRangeEvaluator createExternSheetRefEvaluator(String firstSheetName, String lastSheetName, int externalWorkbookNumber) {
ExternalSheet externalSheet = _workbook.getExternalSheet(firstSheetName, lastSheetName, externalWorkbookNumber); ExternalSheet externalSheet = _workbook.getExternalSheet(firstSheetName, lastSheetName, externalWorkbookNumber);
return createExternSheetRefEvaluator(externalSheet); return createExternSheetRefEvaluator(externalSheet);
} }
SheetRangeEvaluator createExternSheetRefEvaluator(int externSheetIndex) { SheetRangeEvaluator createExternSheetRefEvaluator(int externSheetIndex) {
ExternalSheet externalSheet = _workbook.getExternalSheet(externSheetIndex); ExternalSheet externalSheet = _workbook.getExternalSheet(externSheetIndex);
return createExternSheetRefEvaluator(externalSheet); return createExternSheetRefEvaluator(externalSheet);
} }
SheetRangeEvaluator createExternSheetRefEvaluator(ExternalSheet externalSheet) { SheetRangeEvaluator createExternSheetRefEvaluator(ExternalSheet externalSheet) {
WorkbookEvaluator targetEvaluator; WorkbookEvaluator targetEvaluator;
int otherFirstSheetIndex; int otherFirstSheetIndex;
int otherLastSheetIndex = -1; int otherLastSheetIndex = -1;
if (externalSheet == null || externalSheet.getWorkbookName() == null) { if (externalSheet == null || externalSheet.getWorkbookName() == null) {
// sheet is in same workbook // sheet is in same workbook
targetEvaluator = _bookEvaluator; targetEvaluator = _bookEvaluator;
if(externalSheet == null) { if(externalSheet == null) {
otherFirstSheetIndex = 0; otherFirstSheetIndex = 0;
} else { } else {
otherFirstSheetIndex = _workbook.getSheetIndex(externalSheet.getSheetName()); otherFirstSheetIndex = _workbook.getSheetIndex(externalSheet.getSheetName());
} }
if (externalSheet instanceof ExternalSheetRange) { if (externalSheet instanceof ExternalSheetRange) {
String lastSheetName = ((ExternalSheetRange)externalSheet).getLastSheetName(); String lastSheetName = ((ExternalSheetRange)externalSheet).getLastSheetName();
otherLastSheetIndex = _workbook.getSheetIndex(lastSheetName); otherLastSheetIndex = _workbook.getSheetIndex(lastSheetName);
} }
} else { } else {
// look up sheet by name from external workbook // look up sheet by name from external workbook
String workbookName = externalSheet.getWorkbookName(); String workbookName = externalSheet.getWorkbookName();
try { try {
targetEvaluator = _bookEvaluator.getOtherWorkbookEvaluator(workbookName); targetEvaluator = _bookEvaluator.getOtherWorkbookEvaluator(workbookName);
} catch (WorkbookNotFoundException e) { } catch (WorkbookNotFoundException e) {
throw new RuntimeException(e.getMessage(), e); throw new RuntimeException(e.getMessage(), e);
} }
otherFirstSheetIndex = targetEvaluator.getSheetIndex(externalSheet.getSheetName()); otherFirstSheetIndex = targetEvaluator.getSheetIndex(externalSheet.getSheetName());
if (externalSheet instanceof ExternalSheetRange) { if (externalSheet instanceof ExternalSheetRange) {
String lastSheetName = ((ExternalSheetRange)externalSheet).getLastSheetName(); String lastSheetName = ((ExternalSheetRange)externalSheet).getLastSheetName();
otherLastSheetIndex = targetEvaluator.getSheetIndex(lastSheetName); otherLastSheetIndex = targetEvaluator.getSheetIndex(lastSheetName);
} }
if (otherFirstSheetIndex < 0) { if (otherFirstSheetIndex < 0) {
throw new RuntimeException("Invalid sheet name '" + externalSheet.getSheetName() throw new RuntimeException("Invalid sheet name '" + externalSheet.getSheetName()
+ "' in bool '" + workbookName + "'."); + "' in bool '" + workbookName + "'.");
} }
} }
if (otherLastSheetIndex == -1) { if (otherLastSheetIndex == -1) {
// Reference to just one sheet // Reference to just one sheet
otherLastSheetIndex = otherFirstSheetIndex; otherLastSheetIndex = otherFirstSheetIndex;
} }
SheetRefEvaluator[] evals = new SheetRefEvaluator[otherLastSheetIndex-otherFirstSheetIndex+1]; SheetRefEvaluator[] evals = new SheetRefEvaluator[otherLastSheetIndex-otherFirstSheetIndex+1];
for (int i=0; i<evals.length; i++) { for (int i=0; i<evals.length; i++) {
int otherSheetIndex = i+otherFirstSheetIndex; int otherSheetIndex = i+otherFirstSheetIndex;
evals[i] = new SheetRefEvaluator(targetEvaluator, _tracker, otherSheetIndex); evals[i] = new SheetRefEvaluator(targetEvaluator, _tracker, otherSheetIndex);
} }
return new SheetRangeEvaluator(otherFirstSheetIndex, otherLastSheetIndex, evals); return new SheetRangeEvaluator(otherFirstSheetIndex, otherLastSheetIndex, evals);
} }
/** /**
* @return <code>null</code> if either workbook or sheet is not found * @return <code>null</code> if either workbook or sheet is not found
*/ */
private SheetRefEvaluator createExternSheetRefEvaluator(String workbookName, String sheetName) { private SheetRefEvaluator createExternSheetRefEvaluator(String workbookName, String sheetName) {
WorkbookEvaluator targetEvaluator; WorkbookEvaluator targetEvaluator;
if (workbookName == null) { if (workbookName == null) {
targetEvaluator = _bookEvaluator; targetEvaluator = _bookEvaluator;
} else { } else {
if (sheetName == null) { if (sheetName == null) {
throw new IllegalArgumentException("sheetName must not be null if workbookName is provided"); throw new IllegalArgumentException("sheetName must not be null if workbookName is provided");
} }
try { try {
targetEvaluator = _bookEvaluator.getOtherWorkbookEvaluator(workbookName); targetEvaluator = _bookEvaluator.getOtherWorkbookEvaluator(workbookName);
} catch (WorkbookNotFoundException e) { } catch (WorkbookNotFoundException e) {
return null; return null;
} }
} }
int otherSheetIndex = sheetName == null ? _sheetIndex : targetEvaluator.getSheetIndex(sheetName); int otherSheetIndex = sheetName == null ? _sheetIndex : targetEvaluator.getSheetIndex(sheetName);
if (otherSheetIndex < 0) { if (otherSheetIndex < 0) {
return null; return null;
} }
return new SheetRefEvaluator(targetEvaluator, _tracker, otherSheetIndex); return new SheetRefEvaluator(targetEvaluator, _tracker, otherSheetIndex);
} }
public SheetRangeEvaluator getRefEvaluatorForCurrentSheet() { public SheetRangeEvaluator getRefEvaluatorForCurrentSheet() {
SheetRefEvaluator sre = new SheetRefEvaluator(_bookEvaluator, _tracker, _sheetIndex); SheetRefEvaluator sre = new SheetRefEvaluator(_bookEvaluator, _tracker, _sheetIndex);
return new SheetRangeEvaluator(_sheetIndex, sre); return new SheetRangeEvaluator(_sheetIndex, sre);
} }
/** /**
* Resolves a cell or area reference dynamically. * Resolves a cell or area reference dynamically.
* @param workbookName the name of the workbook containing the reference. If <code>null</code> * @param workbookName the name of the workbook containing the reference. If <code>null</code>
* the current workbook is assumed. Note - to evaluate formulas which use multiple workbooks, * the current workbook is assumed. Note - to evaluate formulas which use multiple workbooks,
* a {@link CollaboratingWorkbooksEnvironment} must be set up. * a {@link CollaboratingWorkbooksEnvironment} must be set up.
* @param sheetName the name of the sheet containing the reference. May be <code>null</code> * @param sheetName the name of the sheet containing the reference. May be <code>null</code>
* (when <tt>workbookName</tt> is also null) in which case the current workbook and sheet is * (when <tt>workbookName</tt> is also null) in which case the current workbook and sheet is
* assumed. * assumed.
* @param refStrPart1 the single cell reference or first part of the area reference. Must not * @param refStrPart1 the single cell reference or first part of the area reference. Must not
* be <code>null</code>. * be <code>null</code>.
* @param refStrPart2 the second part of the area reference. For single cell references this * @param refStrPart2 the second part of the area reference. For single cell references this
* parameter must be <code>null</code> * parameter must be <code>null</code>
* @param isA1Style specifies the format for <tt>refStrPart1</tt> and <tt>refStrPart2</tt>. * @param isA1Style specifies the format for <tt>refStrPart1</tt> and <tt>refStrPart2</tt>.
* Pass <code>true</code> for 'A1' style and <code>false</code> for 'R1C1' style. * Pass <code>true</code> for 'A1' style and <code>false</code> for 'R1C1' style.
* TODO - currently POI only supports 'A1' reference style * TODO - currently POI only supports 'A1' reference style
* @return a {@link RefEval} or {@link AreaEval} * @return a {@link RefEval} or {@link AreaEval}
*/ */
public ValueEval getDynamicReference(String workbookName, String sheetName, String refStrPart1, public ValueEval getDynamicReference(String workbookName, String sheetName, String refStrPart1,
String refStrPart2, boolean isA1Style) { String refStrPart2, boolean isA1Style) {
if (!isA1Style) { if (!isA1Style) {
throw new RuntimeException("R1C1 style not supported yet"); throw new RuntimeException("R1C1 style not supported yet");
} }
SheetRefEvaluator se = createExternSheetRefEvaluator(workbookName, sheetName); SheetRefEvaluator se = createExternSheetRefEvaluator(workbookName, sheetName);
if (se == null) { if (se == null) {
return ErrorEval.REF_INVALID; return ErrorEval.REF_INVALID;
} }
SheetRangeEvaluator sre = new SheetRangeEvaluator(_sheetIndex, se); SheetRangeEvaluator sre = new SheetRangeEvaluator(_sheetIndex, se);
// ugly typecast - TODO - make spreadsheet version more easily accessible // ugly typecast - TODO - make spreadsheet version more easily accessible
SpreadsheetVersion ssVersion = ((FormulaParsingWorkbook)_workbook).getSpreadsheetVersion(); SpreadsheetVersion ssVersion = ((FormulaParsingWorkbook)_workbook).getSpreadsheetVersion();
NameType part1refType = classifyCellReference(refStrPart1, ssVersion); NameType part1refType = classifyCellReference(refStrPart1, ssVersion);
switch (part1refType) { switch (part1refType) {
case BAD_CELL_OR_NAMED_RANGE: case BAD_CELL_OR_NAMED_RANGE:
return ErrorEval.REF_INVALID; return ErrorEval.REF_INVALID;
case NAMED_RANGE: case NAMED_RANGE:
EvaluationName nm = ((FormulaParsingWorkbook)_workbook).getName(refStrPart1, _sheetIndex); EvaluationName nm = ((FormulaParsingWorkbook)_workbook).getName(refStrPart1, _sheetIndex);
if(!nm.isRange()){ if(!nm.isRange()){
throw new RuntimeException("Specified name '" + refStrPart1 + "' is not a range as expected."); throw new RuntimeException("Specified name '" + refStrPart1 + "' is not a range as expected.");
} }
return _bookEvaluator.evaluateNameFormula(nm.getNameDefinition(), this); return _bookEvaluator.evaluateNameFormula(nm.getNameDefinition(), this);
} }
if (refStrPart2 == null) { if (refStrPart2 == null) {
// no ':' // no ':'
switch (part1refType) { switch (part1refType) {
case COLUMN: case COLUMN:
case ROW: case ROW:
return ErrorEval.REF_INVALID; return ErrorEval.REF_INVALID;
case CELL: case CELL:
CellReference cr = new CellReference(refStrPart1); CellReference cr = new CellReference(refStrPart1);
return new LazyRefEval(cr.getRow(), cr.getCol(), sre); return new LazyRefEval(cr.getRow(), cr.getCol(), sre);
} }
throw new IllegalStateException("Unexpected reference classification of '" + refStrPart1 + "'."); throw new IllegalStateException("Unexpected reference classification of '" + refStrPart1 + "'.");
} }
NameType part2refType = classifyCellReference(refStrPart1, ssVersion); NameType part2refType = classifyCellReference(refStrPart1, ssVersion);
switch (part2refType) { switch (part2refType) {
case BAD_CELL_OR_NAMED_RANGE: case BAD_CELL_OR_NAMED_RANGE:
return ErrorEval.REF_INVALID; return ErrorEval.REF_INVALID;
case NAMED_RANGE: case NAMED_RANGE:
throw new RuntimeException("Cannot evaluate '" + refStrPart1 throw new RuntimeException("Cannot evaluate '" + refStrPart1
+ "'. Indirect evaluation of defined names not supported yet"); + "'. Indirect evaluation of defined names not supported yet");
} }
if (part2refType != part1refType) { if (part2refType != part1refType) {
// LHS and RHS of ':' must be compatible // LHS and RHS of ':' must be compatible
return ErrorEval.REF_INVALID; return ErrorEval.REF_INVALID;
} }
int firstRow, firstCol, lastRow, lastCol; int firstRow, firstCol, lastRow, lastCol;
switch (part1refType) { switch (part1refType) {
case COLUMN: case COLUMN:
firstRow =0; firstRow =0;
if (part2refType.equals(NameType.COLUMN)) if (part2refType.equals(NameType.COLUMN))
{ {
@ -252,7 +252,7 @@ public final class OperationEvaluationContext {
lastCol = parseColRef(refStrPart2); lastCol = parseColRef(refStrPart2);
} }
break; break;
case ROW: case ROW:
// support of cell range in the form of integer:integer // support of cell range in the form of integer:integer
firstCol = 0; firstCol = 0;
if (part2refType.equals(NameType.ROW)) if (part2refType.equals(NameType.ROW))
@ -265,61 +265,61 @@ public final class OperationEvaluationContext {
firstRow = parseRowRef(refStrPart1); firstRow = parseRowRef(refStrPart1);
lastRow = parseRowRef(refStrPart2); lastRow = parseRowRef(refStrPart2);
} }
break; break;
case CELL: case CELL:
CellReference cr; CellReference cr;
cr = new CellReference(refStrPart1); cr = new CellReference(refStrPart1);
firstRow = cr.getRow(); firstRow = cr.getRow();
firstCol = cr.getCol(); firstCol = cr.getCol();
cr = new CellReference(refStrPart2); cr = new CellReference(refStrPart2);
lastRow = cr.getRow(); lastRow = cr.getRow();
lastCol = cr.getCol(); lastCol = cr.getCol();
break; break;
default: default:
throw new IllegalStateException("Unexpected reference classification of '" + refStrPart1 + "'."); throw new IllegalStateException("Unexpected reference classification of '" + refStrPart1 + "'.");
} }
return new LazyAreaEval(firstRow, firstCol, lastRow, lastCol, sre); return new LazyAreaEval(firstRow, firstCol, lastRow, lastCol, sre);
} }
private static int parseRowRef(String refStrPart) { private static int parseRowRef(String refStrPart) {
return CellReference.convertColStringToIndex(refStrPart); return CellReference.convertColStringToIndex(refStrPart);
} }
private static int parseColRef(String refStrPart) { private static int parseColRef(String refStrPart) {
return Integer.parseInt(refStrPart) - 1; return Integer.parseInt(refStrPart) - 1;
} }
private static NameType classifyCellReference(String str, SpreadsheetVersion ssVersion) { private static NameType classifyCellReference(String str, SpreadsheetVersion ssVersion) {
int len = str.length(); int len = str.length();
if (len < 1) { if (len < 1) {
return CellReference.NameType.BAD_CELL_OR_NAMED_RANGE; return CellReference.NameType.BAD_CELL_OR_NAMED_RANGE;
} }
return CellReference.classifyCellReference(str, ssVersion); return CellReference.classifyCellReference(str, ssVersion);
} }
public FreeRefFunction findUserDefinedFunction(String functionName) { public FreeRefFunction findUserDefinedFunction(String functionName) {
return _bookEvaluator.findUserDefinedFunction(functionName); return _bookEvaluator.findUserDefinedFunction(functionName);
} }
public ValueEval getRefEval(int rowIndex, int columnIndex) { public ValueEval getRefEval(int rowIndex, int columnIndex) {
SheetRangeEvaluator sre = getRefEvaluatorForCurrentSheet(); SheetRangeEvaluator sre = getRefEvaluatorForCurrentSheet();
return new LazyRefEval(rowIndex, columnIndex, sre); return new LazyRefEval(rowIndex, columnIndex, sre);
} }
public ValueEval getRef3DEval(Ref3DPtg rptg) { public ValueEval getRef3DEval(Ref3DPtg rptg) {
SheetRangeEvaluator sre = createExternSheetRefEvaluator(rptg.getExternSheetIndex()); SheetRangeEvaluator sre = createExternSheetRefEvaluator(rptg.getExternSheetIndex());
return new LazyRefEval(rptg.getRow(), rptg.getColumn(), sre); return new LazyRefEval(rptg.getRow(), rptg.getColumn(), sre);
} }
public ValueEval getRef3DEval(Ref3DPxg rptg) { public ValueEval getRef3DEval(Ref3DPxg rptg) {
SheetRangeEvaluator sre = createExternSheetRefEvaluator( SheetRangeEvaluator sre = createExternSheetRefEvaluator(
rptg.getSheetName(), rptg.getLastSheetName(), rptg.getExternalWorkbookNumber()); rptg.getSheetName(), rptg.getLastSheetName(), rptg.getExternalWorkbookNumber());
return new LazyRefEval(rptg.getRow(), rptg.getColumn(), sre); return new LazyRefEval(rptg.getRow(), rptg.getColumn(), sre);
} }
public ValueEval getAreaEval(int firstRowIndex, int firstColumnIndex, public ValueEval getAreaEval(int firstRowIndex, int firstColumnIndex,
int lastRowIndex, int lastColumnIndex) { int lastRowIndex, int lastColumnIndex) {
SheetRangeEvaluator sre = getRefEvaluatorForCurrentSheet(); SheetRangeEvaluator sre = getRefEvaluatorForCurrentSheet();
return new LazyAreaEval(firstRowIndex, firstColumnIndex, lastRowIndex, lastColumnIndex, sre); return new LazyAreaEval(firstRowIndex, firstColumnIndex, lastRowIndex, lastColumnIndex, sre);
} }
public ValueEval getArea3DEval(Area3DPtg aptg) { public ValueEval getArea3DEval(Area3DPtg aptg) {
SheetRangeEvaluator sre = createExternSheetRefEvaluator(aptg.getExternSheetIndex()); SheetRangeEvaluator sre = createExternSheetRefEvaluator(aptg.getExternSheetIndex());
return new LazyAreaEval(aptg.getFirstRow(), aptg.getFirstColumn(), return new LazyAreaEval(aptg.getFirstRow(), aptg.getFirstColumn(),
@ -348,8 +348,8 @@ public final class OperationEvaluationContext {
); );
return getExternalNameXEval(externName, workbookName); return getExternalNameXEval(externName, workbookName);
} }
public ValueEval getNameXEval(NameXPxg nameXPxg) { public ValueEval getNameXEval(NameXPxg nameXPxg) {
ExternalSheet externSheet = _workbook.getExternalSheet(nameXPxg.getSheetName(), null, nameXPxg.getExternalWorkbookNumber()); ExternalSheet externSheet = _workbook.getExternalSheet(nameXPxg.getSheetName(), null, nameXPxg.getExternalWorkbookNumber());
if(externSheet == null || externSheet.getWorkbookName() == null) { if(externSheet == null || externSheet.getWorkbookName() == null) {
// External reference to our own workbook's name // External reference to our own workbook's name
return getLocalNameXEval(nameXPxg); return getLocalNameXEval(nameXPxg);
@ -363,8 +363,8 @@ public final class OperationEvaluationContext {
nameXPxg.getExternalWorkbookNumber() nameXPxg.getExternalWorkbookNumber()
); );
return getExternalNameXEval(externName, workbookName); return getExternalNameXEval(externName, workbookName);
} }
private ValueEval getLocalNameXEval(NameXPxg nameXPxg) { private ValueEval getLocalNameXEval(NameXPxg nameXPxg) {
// Look up the sheet, if present // Look up the sheet, if present
int sIdx = -1; int sIdx = -1;
@ -383,7 +383,7 @@ public final class OperationEvaluationContext {
return new FunctionNameEval(name); return new FunctionNameEval(name);
} }
} }
private ValueEval getLocalNameXEval(NameXPtg nameXPtg) { private ValueEval getLocalNameXEval(NameXPtg nameXPtg) {
String name = _workbook.resolveNameXText(nameXPtg); String name = _workbook.resolveNameXText(nameXPtg);
// Try to parse it as a name // Try to parse it as a name
@ -406,8 +406,11 @@ public final class OperationEvaluationContext {
// Must be an external function // Must be an external function
return new FunctionNameEval(name); return new FunctionNameEval(name);
} }
} }
public int getSheetIndex() {
return _sheetIndex;
}
private ValueEval getExternalNameXEval(ExternalName externName, String workbookName) { private ValueEval getExternalNameXEval(ExternalName externName, String workbookName) {
try { try {
// Fetch the workbook this refers to, and the name as defined with that // Fetch the workbook this refers to, and the name as defined with that

File diff suppressed because it is too large Load Diff

View File

@ -17,13 +17,18 @@
package org.apache.poi.ss.formula.functions; package org.apache.poi.ss.formula.functions;
import org.apache.poi.ss.formula.FormulaParseException;
import org.apache.poi.ss.formula.FormulaParser;
import org.apache.poi.ss.formula.FormulaParsingWorkbook;
import org.apache.poi.ss.formula.OperationEvaluationContext;
import org.apache.poi.ss.formula.eval.BlankEval; import org.apache.poi.ss.formula.eval.BlankEval;
import org.apache.poi.ss.formula.eval.ErrorEval; import org.apache.poi.ss.formula.eval.ErrorEval;
import org.apache.poi.ss.formula.eval.EvaluationException; import org.apache.poi.ss.formula.eval.EvaluationException;
import org.apache.poi.ss.formula.eval.MissingArgEval; import org.apache.poi.ss.formula.eval.MissingArgEval;
import org.apache.poi.ss.formula.eval.OperandResolver; import org.apache.poi.ss.formula.eval.OperandResolver;
import org.apache.poi.ss.formula.eval.ValueEval; import org.apache.poi.ss.formula.eval.ValueEval;
import org.apache.poi.ss.formula.OperationEvaluationContext; import org.apache.poi.ss.formula.ptg.Area3DPxg;
import org.apache.poi.ss.usermodel.Table;
/** /**
* Implementation for Excel function INDIRECT<p/> * Implementation for Excel function INDIRECT<p/>
@ -42,198 +47,210 @@ import org.apache.poi.ss.formula.OperationEvaluationContext;
*/ */
public final class Indirect implements FreeRefFunction { public final class Indirect implements FreeRefFunction {
public static final FreeRefFunction instance = new Indirect(); public static final FreeRefFunction instance = new Indirect();
private Indirect() { private Indirect() {
// enforce singleton // enforce singleton
} }
public ValueEval evaluate(ValueEval[] args, OperationEvaluationContext ec) { public ValueEval evaluate(ValueEval[] args, OperationEvaluationContext ec) {
if (args.length < 1) { if (args.length < 1) {
return ErrorEval.VALUE_INVALID; return ErrorEval.VALUE_INVALID;
} }
boolean isA1style; boolean isA1style;
String text; String text;
try { try {
ValueEval ve = OperandResolver.getSingleValue(args[0], ec.getRowIndex(), ec ValueEval ve = OperandResolver.getSingleValue(args[0], ec.getRowIndex(), ec
.getColumnIndex()); .getColumnIndex());
text = OperandResolver.coerceValueToString(ve); text = OperandResolver.coerceValueToString(ve);
switch (args.length) { switch (args.length) {
case 1: case 1:
isA1style = true; isA1style = true;
break; break;
case 2: case 2:
isA1style = evaluateBooleanArg(args[1], ec); isA1style = evaluateBooleanArg(args[1], ec);
break; break;
default: default:
return ErrorEval.VALUE_INVALID; return ErrorEval.VALUE_INVALID;
} }
} catch (EvaluationException e) { } catch (EvaluationException e) {
return e.getErrorEval(); return e.getErrorEval();
} }
return evaluateIndirect(ec, text, isA1style); return evaluateIndirect(ec, text, isA1style);
} }
private static boolean evaluateBooleanArg(ValueEval arg, OperationEvaluationContext ec) private static boolean evaluateBooleanArg(ValueEval arg, OperationEvaluationContext ec)
throws EvaluationException { throws EvaluationException {
ValueEval ve = OperandResolver.getSingleValue(arg, ec.getRowIndex(), ec.getColumnIndex()); ValueEval ve = OperandResolver.getSingleValue(arg, ec.getRowIndex(), ec.getColumnIndex());
if (ve == BlankEval.instance || ve == MissingArgEval.instance) { if (ve == BlankEval.instance || ve == MissingArgEval.instance) {
return false; return false;
} }
// numeric quantities follow standard boolean conversion rules // numeric quantities follow standard boolean conversion rules
// for strings, only "TRUE" and "FALSE" (case insensitive) are valid // for strings, only "TRUE" and "FALSE" (case insensitive) are valid
return OperandResolver.coerceValueToBoolean(ve, false).booleanValue(); return OperandResolver.coerceValueToBoolean(ve, false).booleanValue();
} }
private static ValueEval evaluateIndirect(OperationEvaluationContext ec, String text, private static ValueEval evaluateIndirect(final OperationEvaluationContext ec, String text,
boolean isA1style) { boolean isA1style) {
// Search backwards for '!' because sheet names can contain '!'
int plingPos = text.lastIndexOf('!'); // Search backwards for '!' because sheet names can contain '!'
int plingPos = text.lastIndexOf('!');
String workbookName; String workbookName;
String sheetName; String sheetName;
String refText; // whitespace around this gets trimmed OK String refText; // whitespace around this gets trimmed OK
if (plingPos < 0) { if (plingPos < 0) {
workbookName = null; workbookName = null;
sheetName = null; sheetName = null;
refText = text; refText = text;
} else { } else {
String[] parts = parseWorkbookAndSheetName(text.subSequence(0, plingPos)); String[] parts = parseWorkbookAndSheetName(text.subSequence(0, plingPos));
if (parts == null) { if (parts == null) {
return ErrorEval.REF_INVALID; return ErrorEval.REF_INVALID;
} }
workbookName = parts[0]; workbookName = parts[0];
sheetName = parts[1]; sheetName = parts[1];
refText = text.substring(plingPos + 1); refText = text.substring(plingPos + 1);
} }
String refStrPart1; if (Table.isStructuredReference.matcher(refText).matches()) {
String refStrPart2; // The argument is structured reference
Area3DPxg areaPtg = null;
try {
areaPtg = FormulaParser.parseStructuredReference(refText, (FormulaParsingWorkbook) ec.getWorkbook(), ec.getRowIndex());
} catch (FormulaParseException e) {
return ErrorEval.REF_INVALID;
}
return ec.getArea3DEval(areaPtg);
} else {
// The argument is regular reference
String refStrPart1;
String refStrPart2;
int colonPos = refText.indexOf(':');
if (colonPos < 0) {
refStrPart1 = refText.trim();
refStrPart2 = null;
} else {
refStrPart1 = refText.substring(0, colonPos).trim();
refStrPart2 = refText.substring(colonPos + 1).trim();
}
return ec.getDynamicReference(workbookName, sheetName, refStrPart1, refStrPart2, isA1style);
}
}
int colonPos = refText.indexOf(':'); /**
if (colonPos < 0) { * @return array of length 2: {workbookName, sheetName,}. Second element will always be
refStrPart1 = refText.trim(); * present. First element may be null if sheetName is unqualified.
refStrPart2 = null; * Returns <code>null</code> if text cannot be parsed.
} else { */
refStrPart1 = refText.substring(0, colonPos).trim(); private static String[] parseWorkbookAndSheetName(CharSequence text) {
refStrPart2 = refText.substring(colonPos + 1).trim(); int lastIx = text.length() - 1;
} if (lastIx < 0) {
return ec.getDynamicReference(workbookName, sheetName, refStrPart1, refStrPart2, isA1style); return null;
} }
if (canTrim(text)) {
return null;
}
char firstChar = text.charAt(0);
if (Character.isWhitespace(firstChar)) {
return null;
}
if (firstChar == '\'') {
// workbookName or sheetName needs quoting
// quotes go around both
if (text.charAt(lastIx) != '\'') {
return null;
}
firstChar = text.charAt(1);
if (Character.isWhitespace(firstChar)) {
return null;
}
String wbName;
int sheetStartPos;
if (firstChar == '[') {
int rbPos = text.toString().lastIndexOf(']');
if (rbPos < 0) {
return null;
}
wbName = unescapeString(text.subSequence(2, rbPos));
if (wbName == null || canTrim(wbName)) {
return null;
}
sheetStartPos = rbPos + 1;
} else {
wbName = null;
sheetStartPos = 1;
}
/** // else - just sheet name
* @return array of length 2: {workbookName, sheetName,}. Second element will always be String sheetName = unescapeString(text.subSequence(sheetStartPos, lastIx));
* present. First element may be null if sheetName is unqualified. if (sheetName == null) { // note - when quoted, sheetName can
* Returns <code>null</code> if text cannot be parsed. // start/end with whitespace
*/ return null;
private static String[] parseWorkbookAndSheetName(CharSequence text) { }
int lastIx = text.length() - 1; return new String[] { wbName, sheetName, };
if (lastIx < 0) { }
return null;
}
if (canTrim(text)) {
return null;
}
char firstChar = text.charAt(0);
if (Character.isWhitespace(firstChar)) {
return null;
}
if (firstChar == '\'') {
// workbookName or sheetName needs quoting
// quotes go around both
if (text.charAt(lastIx) != '\'') {
return null;
}
firstChar = text.charAt(1);
if (Character.isWhitespace(firstChar)) {
return null;
}
String wbName;
int sheetStartPos;
if (firstChar == '[') {
int rbPos = text.toString().lastIndexOf(']');
if (rbPos < 0) {
return null;
}
wbName = unescapeString(text.subSequence(2, rbPos));
if (wbName == null || canTrim(wbName)) {
return null;
}
sheetStartPos = rbPos + 1;
} else {
wbName = null;
sheetStartPos = 1;
}
// else - just sheet name if (firstChar == '[') {
String sheetName = unescapeString(text.subSequence(sheetStartPos, lastIx)); int rbPos = text.toString().lastIndexOf(']');
if (sheetName == null) { // note - when quoted, sheetName can if (rbPos < 0) {
// start/end with whitespace return null;
return null; }
} CharSequence wbName = text.subSequence(1, rbPos);
return new String[] { wbName, sheetName, }; if (canTrim(wbName)) {
} return null;
}
CharSequence sheetName = text.subSequence(rbPos + 1, text.length());
if (canTrim(sheetName)) {
return null;
}
return new String[] { wbName.toString(), sheetName.toString(), };
}
// else - just sheet name
return new String[] { null, text.toString(), };
}
if (firstChar == '[') { /**
int rbPos = text.toString().lastIndexOf(']'); * @return <code>null</code> if there is a syntax error in any escape sequence
if (rbPos < 0) { * (the typical syntax error is a single quote character not followed by another).
return null; */
} private static String unescapeString(CharSequence text) {
CharSequence wbName = text.subSequence(1, rbPos); int len = text.length();
if (canTrim(wbName)) { StringBuilder sb = new StringBuilder(len);
return null; int i = 0;
} while (i < len) {
CharSequence sheetName = text.subSequence(rbPos + 1, text.length()); char ch = text.charAt(i);
if (canTrim(sheetName)) { if (ch == '\'') {
return null; // every quote must be followed by another
} i++;
return new String[] { wbName.toString(), sheetName.toString(), }; if (i >= len) {
} return null;
// else - just sheet name }
return new String[] { null, text.toString(), }; ch = text.charAt(i);
} if (ch != '\'') {
return null;
}
}
sb.append(ch);
i++;
}
return sb.toString();
}
/** private static boolean canTrim(CharSequence text) {
* @return <code>null</code> if there is a syntax error in any escape sequence int lastIx = text.length() - 1;
* (the typical syntax error is a single quote character not followed by another). if (lastIx < 0) {
*/ return false;
private static String unescapeString(CharSequence text) { }
int len = text.length(); if (Character.isWhitespace(text.charAt(0))) {
StringBuilder sb = new StringBuilder(len); return true;
int i = 0; }
while (i < len) { if (Character.isWhitespace(text.charAt(lastIx))) {
char ch = text.charAt(i); return true;
if (ch == '\'') { }
// every quote must be followed by another return false;
i++; }
if (i >= len) {
return null;
}
ch = text.charAt(i);
if (ch != '\'') {
return null;
}
}
sb.append(ch);
i++;
}
return sb.toString();
}
private static boolean canTrim(CharSequence text) {
int lastIx = text.length() - 1;
if (lastIx < 0) {
return false;
}
if (Character.isWhitespace(text.charAt(0))) {
return true;
}
if (Character.isWhitespace(text.charAt(lastIx))) {
return true;
}
return false;
}
} }

View File

@ -36,9 +36,9 @@ public final class Area3DPxg extends AreaPtgBase implements Pxg3D {
private String firstSheetName; private String firstSheetName;
private String lastSheetName; private String lastSheetName;
public Area3DPxg(int externalWorkbookNumber, SheetIdentifier sheetName, String arearef) { public Area3DPxg(int externalWorkbookNumber, SheetIdentifier sheetName, String arearef) {
this(externalWorkbookNumber, sheetName, new AreaReference(arearef)); this(externalWorkbookNumber, sheetName, new AreaReference(arearef));
} }
public Area3DPxg(int externalWorkbookNumber, SheetIdentifier sheetName, AreaReference arearef) { public Area3DPxg(int externalWorkbookNumber, SheetIdentifier sheetName, AreaReference arearef) {
super(arearef); super(arearef);
this.externalWorkbookNumber = externalWorkbookNumber; this.externalWorkbookNumber = externalWorkbookNumber;
@ -57,8 +57,8 @@ public final class Area3DPxg extends AreaPtgBase implements Pxg3D {
this(-1, sheetName, arearef); this(-1, sheetName, arearef);
} }
@Override @Override
public String toString() { public String toString() {
StringBuffer sb = new StringBuffer(); StringBuffer sb = new StringBuffer();
sb.append(getClass().getName()); sb.append(getClass().getName());
sb.append(" ["); sb.append(" [");
@ -76,8 +76,8 @@ public final class Area3DPxg extends AreaPtgBase implements Pxg3D {
sb.append(formatReferenceAsString()); sb.append(formatReferenceAsString());
sb.append("]"); sb.append("]");
return sb.toString(); return sb.toString();
} }
public int getExternalWorkbookNumber() { public int getExternalWorkbookNumber() {
return externalWorkbookNumber; return externalWorkbookNumber;
} }

View File

@ -31,272 +31,272 @@ import org.apache.poi.util.LittleEndianOutput;
* @author Jason Height (jheight at chariot dot net dot au) * @author Jason Height (jheight at chariot dot net dot au)
*/ */
public abstract class AreaPtgBase extends OperandPtg implements AreaI { public abstract class AreaPtgBase extends OperandPtg implements AreaI {
/** /**
* TODO - (May-2008) fix subclasses of AreaPtg 'AreaN~' which are used in shared formulas. * TODO - (May-2008) fix subclasses of AreaPtg 'AreaN~' which are used in shared formulas.
* see similar comment in ReferencePtg * see similar comment in ReferencePtg
*/ */
protected final RuntimeException notImplemented() { protected final RuntimeException notImplemented() {
return new RuntimeException("Coding Error: This method should never be called. This ptg should be converted"); return new RuntimeException("Coding Error: This method should never be called. This ptg should be converted");
} }
/** zero based, unsigned 16 bit */ /** zero based, unsigned 16 bit */
private int field_1_first_row; private int field_1_first_row;
/** zero based, unsigned 16 bit */ /** zero based, unsigned 16 bit */
private int field_2_last_row; private int field_2_last_row;
/** zero based, unsigned 8 bit */ /** zero based, unsigned 8 bit */
private int field_3_first_column; //BitFields: (first row relative, first col relative, first column number) private int field_3_first_column; //BitFields: (first row relative, first col relative, first column number)
/** zero based, unsigned 8 bit */ /** zero based, unsigned 8 bit */
private int field_4_last_column; //BitFields: (last row relative, last col relative, last column number) private int field_4_last_column; //BitFields: (last row relative, last col relative, last column number)
private final static BitField rowRelative = BitFieldFactory.getInstance(0x8000); private final static BitField rowRelative = BitFieldFactory.getInstance(0x8000);
private final static BitField colRelative = BitFieldFactory.getInstance(0x4000); private final static BitField colRelative = BitFieldFactory.getInstance(0x4000);
private final static BitField columnMask = BitFieldFactory.getInstance(0x3FFF); private final static BitField columnMask = BitFieldFactory.getInstance(0x3FFF);
protected AreaPtgBase() { protected AreaPtgBase() {
// do nothing // do nothing
} }
protected AreaPtgBase(AreaReference ar) { protected AreaPtgBase(AreaReference ar) {
CellReference firstCell = ar.getFirstCell(); CellReference firstCell = ar.getFirstCell();
CellReference lastCell = ar.getLastCell(); CellReference lastCell = ar.getLastCell();
setFirstRow(firstCell.getRow()); setFirstRow(firstCell.getRow());
setFirstColumn(firstCell.getCol() == -1 ? 0 : firstCell.getCol()); setFirstColumn(firstCell.getCol() == -1 ? 0 : firstCell.getCol());
setLastRow(lastCell.getRow()); setLastRow(lastCell.getRow());
setLastColumn(lastCell.getCol() == -1 ? 0xFF : lastCell.getCol()); setLastColumn(lastCell.getCol() == -1 ? 0xFF : lastCell.getCol());
setFirstColRelative(!firstCell.isColAbsolute()); setFirstColRelative(!firstCell.isColAbsolute());
setLastColRelative(!lastCell.isColAbsolute()); setLastColRelative(!lastCell.isColAbsolute());
setFirstRowRelative(!firstCell.isRowAbsolute()); setFirstRowRelative(!firstCell.isRowAbsolute());
setLastRowRelative(!lastCell.isRowAbsolute()); setLastRowRelative(!lastCell.isRowAbsolute());
} }
protected AreaPtgBase(int firstRow, int lastRow, int firstColumn, int lastColumn, protected AreaPtgBase(int firstRow, int lastRow, int firstColumn, int lastColumn,
boolean firstRowRelative, boolean lastRowRelative, boolean firstColRelative, boolean lastColRelative) { boolean firstRowRelative, boolean lastRowRelative, boolean firstColRelative, boolean lastColRelative) {
if (lastRow >= firstRow) { if (lastRow >= firstRow) {
setFirstRow(firstRow); setFirstRow(firstRow);
setLastRow(lastRow); setLastRow(lastRow);
setFirstRowRelative(firstRowRelative); setFirstRowRelative(firstRowRelative);
setLastRowRelative(lastRowRelative); setLastRowRelative(lastRowRelative);
} else { } else {
setFirstRow(lastRow); setFirstRow(lastRow);
setLastRow(firstRow); setLastRow(firstRow);
setFirstRowRelative(lastRowRelative); setFirstRowRelative(lastRowRelative);
setLastRowRelative(firstRowRelative); setLastRowRelative(firstRowRelative);
} }
if (lastColumn >= firstColumn) { if (lastColumn >= firstColumn) {
setFirstColumn(firstColumn); setFirstColumn(firstColumn);
setLastColumn(lastColumn); setLastColumn(lastColumn);
setFirstColRelative(firstColRelative); setFirstColRelative(firstColRelative);
setLastColRelative(lastColRelative); setLastColRelative(lastColRelative);
} else { } else {
setFirstColumn(lastColumn); setFirstColumn(lastColumn);
setLastColumn(firstColumn); setLastColumn(firstColumn);
setFirstColRelative(lastColRelative); setFirstColRelative(lastColRelative);
setLastColRelative(firstColRelative); setLastColRelative(firstColRelative);
} }
} }
/** /**
* Sort the first and last row and columns in-place to the preferred (top left:bottom right) order * Sort the first and last row and columns in-place to the preferred (top left:bottom right) order
* Note: Sort only occurs when an instance is constructed or when this method is called. * Note: Sort only occurs when an instance is constructed or when this method is called.
* *
* <p>For example, <code>$E5:B$10</code> becomes <code>B5:$E$10</code></p> * <p>For example, <code>$E5:B$10</code> becomes <code>B5:$E$10</code></p>
*/ */
public void sortTopLeftToBottomRight() { public void sortTopLeftToBottomRight() {
if (getFirstRow() > getLastRow()) { if (getFirstRow() > getLastRow()) {
//swap first row and last row numbers and relativity //swap first row and last row numbers and relativity
//Note: cannot just swap the fields because row relativity is stored in fields 3 and 4 //Note: cannot just swap the fields because row relativity is stored in fields 3 and 4
final int firstRow = getFirstRow(); final int firstRow = getFirstRow();
final boolean firstRowRel = isFirstRowRelative(); final boolean firstRowRel = isFirstRowRelative();
setFirstRow(getLastRow()); setFirstRow(getLastRow());
setFirstRowRelative(isLastRowRelative()); setFirstRowRelative(isLastRowRelative());
setLastRow(firstRow); setLastRow(firstRow);
setLastRowRelative(firstRowRel); setLastRowRelative(firstRowRel);
} }
if (getFirstColumn() > getLastColumn()) { if (getFirstColumn() > getLastColumn()) {
//swap first column and last column numbers and relativity //swap first column and last column numbers and relativity
//Note: cannot just swap the fields because row relativity is stored in fields 3 and 4 //Note: cannot just swap the fields because row relativity is stored in fields 3 and 4
final int firstCol = getFirstColumn(); final int firstCol = getFirstColumn();
final boolean firstColRel = isFirstColRelative(); final boolean firstColRel = isFirstColRelative();
setFirstColumn(getLastColumn()); setFirstColumn(getLastColumn());
setFirstColRelative(isLastColRelative()); setFirstColRelative(isLastColRelative());
setLastColumn(firstCol); setLastColumn(firstCol);
setLastColRelative(firstColRel); setLastColRelative(firstColRel);
} }
} }
protected final void readCoordinates(LittleEndianInput in) { protected final void readCoordinates(LittleEndianInput in) {
field_1_first_row = in.readUShort(); field_1_first_row = in.readUShort();
field_2_last_row = in.readUShort(); field_2_last_row = in.readUShort();
field_3_first_column = in.readUShort(); field_3_first_column = in.readUShort();
field_4_last_column = in.readUShort(); field_4_last_column = in.readUShort();
} }
protected final void writeCoordinates(LittleEndianOutput out) { protected final void writeCoordinates(LittleEndianOutput out) {
out.writeShort(field_1_first_row); out.writeShort(field_1_first_row);
out.writeShort(field_2_last_row); out.writeShort(field_2_last_row);
out.writeShort(field_3_first_column); out.writeShort(field_3_first_column);
out.writeShort(field_4_last_column); out.writeShort(field_4_last_column);
} }
/** /**
* @return the first row in the area * @return the first row in the area
*/ */
public final int getFirstRow() { public final int getFirstRow() {
return field_1_first_row; return field_1_first_row;
} }
/** /**
* sets the first row * sets the first row
* @param rowIx number (0-based) * @param rowIx number (0-based)
*/ */
public final void setFirstRow(int rowIx) { public final void setFirstRow(int rowIx) {
field_1_first_row = rowIx; field_1_first_row = rowIx;
} }
/** /**
* @return last row in the range (x2 in x1,y1-x2,y2) * @return last row in the range (x2 in x1,y1-x2,y2)
*/ */
public final int getLastRow() { public final int getLastRow() {
return field_2_last_row; return field_2_last_row;
} }
/** /**
* @param rowIx last row number in the area * @param rowIx last row number in the area
*/ */
public final void setLastRow(int rowIx) { public final void setLastRow(int rowIx) {
field_2_last_row = rowIx; field_2_last_row = rowIx;
} }
/** /**
* @return the first column number in the area. * @return the first column number in the area.
*/ */
public final int getFirstColumn() { public final int getFirstColumn() {
return columnMask.getValue(field_3_first_column); return columnMask.getValue(field_3_first_column);
} }
/** /**
* @return the first column number + the options bit settings unstripped * @return the first column number + the options bit settings unstripped
*/ */
public final short getFirstColumnRaw() { public final short getFirstColumnRaw() {
return (short) field_3_first_column; // TODO return (short) field_3_first_column; // TODO
} }
/** /**
* @return whether or not the first row is a relative reference or not. * @return whether or not the first row is a relative reference or not.
*/ */
public final boolean isFirstRowRelative() { public final boolean isFirstRowRelative() {
return rowRelative.isSet(field_3_first_column); return rowRelative.isSet(field_3_first_column);
} }
/** /**
* sets the first row to relative or not * sets the first row to relative or not
* @param rel is relative or not. * @param rel is relative or not.
*/ */
public final void setFirstRowRelative(boolean rel) { public final void setFirstRowRelative(boolean rel) {
field_3_first_column=rowRelative.setBoolean(field_3_first_column,rel); field_3_first_column=rowRelative.setBoolean(field_3_first_column,rel);
} }
/** /**
* @return isrelative first column to relative or not * @return isrelative first column to relative or not
*/ */
public final boolean isFirstColRelative() { public final boolean isFirstColRelative() {
return colRelative.isSet(field_3_first_column); return colRelative.isSet(field_3_first_column);
} }
/** /**
* set whether the first column is relative * set whether the first column is relative
*/ */
public final void setFirstColRelative(boolean rel) { public final void setFirstColRelative(boolean rel) {
field_3_first_column=colRelative.setBoolean(field_3_first_column,rel); field_3_first_column=colRelative.setBoolean(field_3_first_column,rel);
} }
/** /**
* set the first column in the area * set the first column in the area
*/ */
public final void setFirstColumn(int colIx) { public final void setFirstColumn(int colIx) {
field_3_first_column=columnMask.setValue(field_3_first_column, colIx); field_3_first_column=columnMask.setValue(field_3_first_column, colIx);
} }
/** /**
* set the first column irrespective of the bitmasks * set the first column irrespective of the bitmasks
*/ */
public final void setFirstColumnRaw(int column) { public final void setFirstColumnRaw(int column) {
field_3_first_column = column; field_3_first_column = column;
} }
/** /**
* @return lastcolumn in the area * @return lastcolumn in the area
*/ */
public final int getLastColumn() { public final int getLastColumn() {
return columnMask.getValue(field_4_last_column); return columnMask.getValue(field_4_last_column);
} }
/** /**
* @return last column and bitmask (the raw field) * @return last column and bitmask (the raw field)
*/ */
public final short getLastColumnRaw() { public final short getLastColumnRaw() {
return (short) field_4_last_column; return (short) field_4_last_column;
} }
/** /**
* @return last row relative or not * @return last row relative or not
*/ */
public final boolean isLastRowRelative() { public final boolean isLastRowRelative() {
return rowRelative.isSet(field_4_last_column); return rowRelative.isSet(field_4_last_column);
} }
/** /**
* set whether the last row is relative or not * set whether the last row is relative or not
* @param rel <code>true</code> if the last row relative, else * @param rel <code>true</code> if the last row relative, else
* <code>false</code> * <code>false</code>
*/ */
public final void setLastRowRelative(boolean rel) { public final void setLastRowRelative(boolean rel) {
field_4_last_column=rowRelative.setBoolean(field_4_last_column,rel); field_4_last_column=rowRelative.setBoolean(field_4_last_column,rel);
} }
/** /**
* @return lastcol relative or not * @return lastcol relative or not
*/ */
public final boolean isLastColRelative() { public final boolean isLastColRelative() {
return colRelative.isSet(field_4_last_column); return colRelative.isSet(field_4_last_column);
} }
/** /**
* set whether the last column should be relative or not * set whether the last column should be relative or not
*/ */
public final void setLastColRelative(boolean rel) { public final void setLastColRelative(boolean rel) {
field_4_last_column=colRelative.setBoolean(field_4_last_column,rel); field_4_last_column=colRelative.setBoolean(field_4_last_column,rel);
} }
/** /**
* set the last column in the area * set the last column in the area
*/ */
public final void setLastColumn(int colIx) { public final void setLastColumn(int colIx) {
field_4_last_column=columnMask.setValue(field_4_last_column, colIx); field_4_last_column=columnMask.setValue(field_4_last_column, colIx);
} }
/** /**
* set the last column irrespective of the bitmasks * set the last column irrespective of the bitmasks
*/ */
public final void setLastColumnRaw(short column) { public final void setLastColumnRaw(short column) {
field_4_last_column = column; field_4_last_column = column;
} }
protected final String formatReferenceAsString() { protected final String formatReferenceAsString() {
CellReference topLeft = new CellReference(getFirstRow(),getFirstColumn(),!isFirstRowRelative(),!isFirstColRelative()); CellReference topLeft = new CellReference(getFirstRow(),getFirstColumn(),!isFirstRowRelative(),!isFirstColRelative());
CellReference botRight = new CellReference(getLastRow(),getLastColumn(),!isLastRowRelative(),!isLastColRelative()); CellReference botRight = new CellReference(getLastRow(),getLastColumn(),!isLastRowRelative(),!isLastColRelative());
if(AreaReference.isWholeColumnReference(SpreadsheetVersion.EXCEL97, topLeft, botRight)) { if(AreaReference.isWholeColumnReference(SpreadsheetVersion.EXCEL97, topLeft, botRight)) {
return (new AreaReference(topLeft, botRight)).formatAsString(); return (new AreaReference(topLeft, botRight)).formatAsString();
} }
return topLeft.formatAsString() + ":" + botRight.formatAsString(); return topLeft.formatAsString() + ":" + botRight.formatAsString();
} }
public String toFormulaString() { public String toFormulaString() {
return formatReferenceAsString(); return formatReferenceAsString();
} }
public byte getDefaultOperandClass() { public byte getDefaultOperandClass() {
return Ptg.CLASS_REF; return Ptg.CLASS_REF;
} }
} }

View File

@ -28,7 +28,7 @@ import org.apache.poi.xssf.usermodel.BaseXSSFEvaluationWorkbook;
* SXSSF wrapper around the SXSSF and XSSF workbooks * SXSSF wrapper around the SXSSF and XSSF workbooks
*/ */
public final class SXSSFEvaluationWorkbook extends BaseXSSFEvaluationWorkbook { public final class SXSSFEvaluationWorkbook extends BaseXSSFEvaluationWorkbook {
private SXSSFWorkbook _uBook; private final SXSSFWorkbook _uBook;
public static SXSSFEvaluationWorkbook create(SXSSFWorkbook book) { public static SXSSFEvaluationWorkbook create(SXSSFWorkbook book) {
if (book == null) { if (book == null) {
@ -41,16 +41,19 @@ public final class SXSSFEvaluationWorkbook extends BaseXSSFEvaluationWorkbook {
super(book.getXSSFWorkbook()); super(book.getXSSFWorkbook());
_uBook = book; _uBook = book;
} }
@Override
public int getSheetIndex(EvaluationSheet evalSheet) { public int getSheetIndex(EvaluationSheet evalSheet) {
SXSSFSheet sheet = ((SXSSFEvaluationSheet)evalSheet).getSXSSFSheet(); SXSSFSheet sheet = ((SXSSFEvaluationSheet)evalSheet).getSXSSFSheet();
return _uBook.getSheetIndex(sheet); return _uBook.getSheetIndex(sheet);
} }
@Override
public EvaluationSheet getSheet(int sheetIndex) { public EvaluationSheet getSheet(int sheetIndex) {
return new SXSSFEvaluationSheet(_uBook.getSheetAt(sheetIndex)); return new SXSSFEvaluationSheet(_uBook.getSheetAt(sheetIndex));
} }
@Override
public Ptg[] getFormulaTokens(EvaluationCell evalCell) { public Ptg[] getFormulaTokens(EvaluationCell evalCell) {
SXSSFCell cell = ((SXSSFEvaluationCell)evalCell).getSXSSFCell(); SXSSFCell cell = ((SXSSFEvaluationCell)evalCell).getSXSSFCell();
SXSSFEvaluationWorkbook frBook = SXSSFEvaluationWorkbook.create(_uBook); SXSSFEvaluationWorkbook frBook = SXSSFEvaluationWorkbook.create(_uBook);

View File

@ -17,7 +17,10 @@
package org.apache.poi.xssf.usermodel; package org.apache.poi.xssf.usermodel;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.Map;
import org.apache.poi.ss.SpreadsheetVersion; import org.apache.poi.ss.SpreadsheetVersion;
import org.apache.poi.ss.formula.EvaluationName; import org.apache.poi.ss.formula.EvaluationName;
@ -36,6 +39,8 @@ import org.apache.poi.ss.formula.ptg.Ptg;
import org.apache.poi.ss.formula.ptg.Ref3DPxg; import org.apache.poi.ss.formula.ptg.Ref3DPxg;
import org.apache.poi.ss.formula.udf.IndexedUDFFinder; import org.apache.poi.ss.formula.udf.IndexedUDFFinder;
import org.apache.poi.ss.formula.udf.UDFFinder; import org.apache.poi.ss.formula.udf.UDFFinder;
import org.apache.poi.ss.usermodel.Table;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.util.AreaReference; import org.apache.poi.ss.util.AreaReference;
import org.apache.poi.ss.util.CellReference; import org.apache.poi.ss.util.CellReference;
import org.apache.poi.util.NotImplemented; import org.apache.poi.util.NotImplemented;
@ -46,31 +51,31 @@ import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTDefinedName;
* Internal POI use only - parent of XSSF and SXSSF evaluation workbooks * Internal POI use only - parent of XSSF and SXSSF evaluation workbooks
*/ */
public abstract class BaseXSSFEvaluationWorkbook implements FormulaRenderingWorkbook, EvaluationWorkbook, FormulaParsingWorkbook { public abstract class BaseXSSFEvaluationWorkbook implements FormulaRenderingWorkbook, EvaluationWorkbook, FormulaParsingWorkbook {
protected final XSSFWorkbook _uBook; protected final XSSFWorkbook _uBook;
protected BaseXSSFEvaluationWorkbook(XSSFWorkbook book) { protected BaseXSSFEvaluationWorkbook(XSSFWorkbook book) {
_uBook = book; _uBook = book;
} }
private int convertFromExternalSheetIndex(int externSheetIndex) { private int convertFromExternalSheetIndex(int externSheetIndex) {
return externSheetIndex; return externSheetIndex;
} }
/** /**
* XSSF doesn't use external sheet indexes, so when asked treat * XSSF doesn't use external sheet indexes, so when asked treat
* it just as a local index * it just as a local index
*/ */
public int convertFromExternSheetIndex(int externSheetIndex) { public int convertFromExternSheetIndex(int externSheetIndex) {
return externSheetIndex; return externSheetIndex;
} }
/** /**
* @return the external sheet index of the sheet with the given internal * @return the external sheet index of the sheet with the given internal
* index. Used by some of the more obscure formula and named range things. * index. Used by some of the more obscure formula and named range things.
* Fairly easy on XSSF (we think...) since the internal and external * Fairly easy on XSSF (we think...) since the internal and external
* indices are the same * indices are the same
*/ */
private int convertToExternalSheetIndex(int sheetIndex) { private int convertToExternalSheetIndex(int sheetIndex) {
return sheetIndex; return sheetIndex;
} }
public int getExternalSheetIndex(String sheetName) { public int getExternalSheetIndex(String sheetName) {
int sheetIndex = _uBook.getSheetIndex(sheetName); int sheetIndex = _uBook.getSheetIndex(sheetName);
@ -131,37 +136,37 @@ public abstract class BaseXSSFEvaluationWorkbook implements FormulaRenderingWork
} }
} }
/** /**
* Return EvaluationName wrapper around the matching XSSFName (named range) * Return EvaluationName wrapper around the matching XSSFName (named range)
* @param name case-aware but case-insensitive named range in workbook * @param name case-aware but case-insensitive named range in workbook
* @param sheetIndex index of sheet if named range scope is limited to one sheet * @param sheetIndex index of sheet if named range scope is limited to one sheet
* if named range scope is global to the workbook, sheetIndex is -1. * if named range scope is global to the workbook, sheetIndex is -1.
* @return If name is a named range in the workbook, returns * @return If name is a named range in the workbook, returns
* EvaluationName corresponding to that named range * EvaluationName corresponding to that named range
* Returns null if there is no named range with the same name and scope in the workbook * Returns null if there is no named range with the same name and scope in the workbook
*/ */
public EvaluationName getName(String name, int sheetIndex) { public EvaluationName getName(String name, int sheetIndex) {
for (int i = 0; i < _uBook.getNumberOfNames(); i++) { for (int i = 0; i < _uBook.getNumberOfNames(); i++) {
XSSFName nm = _uBook.getNameAt(i); XSSFName nm = _uBook.getNameAt(i);
String nameText = nm.getNameName(); String nameText = nm.getNameName();
int nameSheetindex = nm.getSheetIndex(); int nameSheetindex = nm.getSheetIndex();
if (name.equalsIgnoreCase(nameText) && if (name.equalsIgnoreCase(nameText) &&
(nameSheetindex == -1 || nameSheetindex == sheetIndex)) { (nameSheetindex == -1 || nameSheetindex == sheetIndex)) {
return new Name(nm, i, this); return new Name(nm, i, this);
} }
} }
return sheetIndex == -1 ? null : getName(name, -1); return sheetIndex == -1 ? null : getName(name, -1);
} }
public String getSheetName(int sheetIndex) { public String getSheetName(int sheetIndex) {
return _uBook.getSheetName(sheetIndex); return _uBook.getSheetName(sheetIndex);
} }
public ExternalName getExternalName(int externSheetIndex, int externNameIndex) { public ExternalName getExternalName(int externSheetIndex, int externNameIndex) {
throw new IllegalStateException("HSSF-style external references are not supported for XSSF"); throw new IllegalStateException("HSSF-style external references are not supported for XSSF");
} }
public ExternalName getExternalName(String nameName, String sheetName, int externalWorkbookNumber) { public ExternalName getExternalName(String nameName, String sheetName, int externalWorkbookNumber) {
if (externalWorkbookNumber > 0) { if (externalWorkbookNumber > 0) {
// External reference - reference is 1 based, link table is 0 based // External reference - reference is 1 based, link table is 0 based
int linkNumber = externalWorkbookNumber - 1; int linkNumber = externalWorkbookNumber - 1;
@ -186,7 +191,7 @@ public abstract class BaseXSSFEvaluationWorkbook implements FormulaRenderingWork
int nameIdx = _uBook.getNameIndex(nameName); int nameIdx = _uBook.getNameIndex(nameName);
return new ExternalName(nameName, nameIdx, 0); // TODO Is this right? return new ExternalName(nameName, nameIdx, 0); // TODO Is this right?
} }
} }
/** /**
@ -194,7 +199,7 @@ public abstract class BaseXSSFEvaluationWorkbook implements FormulaRenderingWork
*/ */
@Override @Override
public NameXPxg getNameXPtg(String name, SheetIdentifier sheet) { public NameXPxg getNameXPtg(String name, SheetIdentifier sheet) {
// First, try to find it as a User Defined Function // First, try to find it as a User Defined Function
IndexedUDFFinder udfFinder = (IndexedUDFFinder)getUDFFinder(); IndexedUDFFinder udfFinder = (IndexedUDFFinder)getUDFFinder();
FreeRefFunction func = udfFinder.findFunction(name); FreeRefFunction func = udfFinder.findFunction(name);
if (func != null) { if (func != null) {
@ -223,7 +228,7 @@ public abstract class BaseXSSFEvaluationWorkbook implements FormulaRenderingWork
} else { } else {
return new NameXPxg(sheetName, name); return new NameXPxg(sheetName, name);
} }
} }
public Ptg get3DReferencePtg(CellReference cell, SheetIdentifier sheet) { public Ptg get3DReferencePtg(CellReference cell, SheetIdentifier sheet) {
if (sheet._bookName != null) { if (sheet._bookName != null) {
int bookIndex = resolveBookIndex(sheet._bookName); int bookIndex = resolveBookIndex(sheet._bookName);
@ -259,102 +264,151 @@ public abstract class BaseXSSFEvaluationWorkbook implements FormulaRenderingWork
return name; return name;
} }
public ExternalSheet getExternalSheet(int externSheetIndex) { public ExternalSheet getExternalSheet(int externSheetIndex) {
throw new IllegalStateException("HSSF-style external references are not supported for XSSF"); throw new IllegalStateException("HSSF-style external references are not supported for XSSF");
} }
public ExternalSheet getExternalSheet(String firstSheetName, String lastSheetName, int externalWorkbookNumber) { public ExternalSheet getExternalSheet(String firstSheetName, String lastSheetName, int externalWorkbookNumber) {
String workbookName; String workbookName;
if (externalWorkbookNumber > 0) { if (externalWorkbookNumber > 0) {
// External reference - reference is 1 based, link table is 0 based // External reference - reference is 1 based, link table is 0 based
int linkNumber = externalWorkbookNumber - 1; int linkNumber = externalWorkbookNumber - 1;
ExternalLinksTable linkTable = _uBook.getExternalLinksTable().get(linkNumber); ExternalLinksTable linkTable = _uBook.getExternalLinksTable().get(linkNumber);
workbookName = linkTable.getLinkedFileName(); workbookName = linkTable.getLinkedFileName();
} else { } else {
// Internal reference // Internal reference
workbookName = null; workbookName = null;
} }
if (lastSheetName == null || firstSheetName.equals(lastSheetName)) { if (lastSheetName == null || firstSheetName.equals(lastSheetName)) {
return new ExternalSheet(workbookName, firstSheetName); return new ExternalSheet(workbookName, firstSheetName);
} else { } else {
return new ExternalSheetRange(workbookName, firstSheetName, lastSheetName); return new ExternalSheetRange(workbookName, firstSheetName, lastSheetName);
} }
} }
@NotImplemented @NotImplemented
public int getExternalSheetIndex(String workbookName, String sheetName) { public int getExternalSheetIndex(String workbookName, String sheetName) {
throw new RuntimeException("not implemented yet"); throw new RuntimeException("not implemented yet");
} }
public int getSheetIndex(String sheetName) { public int getSheetIndex(String sheetName) {
return _uBook.getSheetIndex(sheetName); return _uBook.getSheetIndex(sheetName);
} }
public String getSheetFirstNameByExternSheet(int externSheetIndex) { public String getSheetFirstNameByExternSheet(int externSheetIndex) {
int sheetIndex = convertFromExternalSheetIndex(externSheetIndex); int sheetIndex = convertFromExternalSheetIndex(externSheetIndex);
return _uBook.getSheetName(sheetIndex); return _uBook.getSheetName(sheetIndex);
} }
public String getSheetLastNameByExternSheet(int externSheetIndex) { public String getSheetLastNameByExternSheet(int externSheetIndex) {
// XSSF does multi-sheet references differently, so this is the same as the first // XSSF does multi-sheet references differently, so this is the same as the first
return getSheetFirstNameByExternSheet(externSheetIndex); return getSheetFirstNameByExternSheet(externSheetIndex);
} }
public String getNameText(NamePtg namePtg) { public String getNameText(NamePtg namePtg) {
return _uBook.getNameAt(namePtg.getIndex()).getNameName(); return _uBook.getNameAt(namePtg.getIndex()).getNameName();
} }
public EvaluationName getName(NamePtg namePtg) { public EvaluationName getName(NamePtg namePtg) {
int ix = namePtg.getIndex(); int ix = namePtg.getIndex();
return new Name(_uBook.getNameAt(ix), ix, this); return new Name(_uBook.getNameAt(ix), ix, this);
} }
@Override @Override
public XSSFName createName() { public XSSFName createName() {
return _uBook.createName(); 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
* only by name (names are global, and case insensitive).
* This map stores names as lower case for case-insensitive lookups.
*
* FIXME: Caching tables by name here for fast formula lookup means the map is out of date if
* a table is renamed or added/removed to a sheet after the map is created.
*
* Perhaps tables can be managed similar to PivotTable references above?
*/
private Map<String, XSSFTable> _tableCache = null;
private Map<String, XSSFTable> getTableCache() {
if ( _tableCache != null ) {
return _tableCache;
}
// FIXME: use org.apache.commons.collections.map.CaseInsensitiveMap
_tableCache = new HashMap<String, XSSFTable>();
for (Sheet sheet : _uBook) {
for (XSSFTable tbl : ((XSSFSheet)sheet).getTables()) {
String lname = caseInsensitive(tbl.getName());
_tableCache.put(lname, tbl);
}
}
return _tableCache;
}
/**
* Returns the data table with the given name (case insensitive).
* Tables are cached for performance (formula evaluation looks them up by name repeatedly).
* After the first table lookup, adding or removing a table from the document structure will cause trouble.
* This is meant to be used on documents whose structure is essentially static at the point formulas are evaluated.
*
* @param name the data table name (case-insensitive)
* @return The Data table in the workbook named <tt>name</tt>, or <tt>null</tt> if no table is named <tt>name</tt>.
* @since 3.15 beta 2
*/
@Override
public XSSFTable getTable(String name) {
if (name == null) return null;
String lname = caseInsensitive(name);
return getTableCache().get(lname);
}
public UDFFinder getUDFFinder(){ public UDFFinder getUDFFinder(){
return _uBook.getUDFFinder(); return _uBook.getUDFFinder();
} }
private static final class Name implements EvaluationName { private static final class Name implements EvaluationName {
private final XSSFName _nameRecord; private final XSSFName _nameRecord;
private final int _index; private final int _index;
private final FormulaParsingWorkbook _fpBook; private final FormulaParsingWorkbook _fpBook;
public Name(XSSFName name, int index, FormulaParsingWorkbook fpBook) { public Name(XSSFName name, int index, FormulaParsingWorkbook fpBook) {
_nameRecord = name; _nameRecord = name;
_index = index; _index = index;
_fpBook = fpBook; _fpBook = fpBook;
} }
public Ptg[] getNameDefinition() { public Ptg[] getNameDefinition() {
return FormulaParser.parse(_nameRecord.getRefersToFormula(), _fpBook, FormulaType.NAMEDRANGE, _nameRecord.getSheetIndex()); return FormulaParser.parse(_nameRecord.getRefersToFormula(), _fpBook, FormulaType.NAMEDRANGE, _nameRecord.getSheetIndex());
} }
public String getNameText() { public String getNameText() {
return _nameRecord.getNameName(); return _nameRecord.getNameName();
} }
public boolean hasFormula() { public boolean hasFormula() {
// TODO - no idea if this is right // TODO - no idea if this is right
CTDefinedName ctn = _nameRecord.getCTName(); CTDefinedName ctn = _nameRecord.getCTName();
String strVal = ctn.getStringValue(); String strVal = ctn.getStringValue();
return !ctn.getFunction() && strVal != null && strVal.length() > 0; return !ctn.getFunction() && strVal != null && strVal.length() > 0;
} }
public boolean isFunctionName() { public boolean isFunctionName() {
return _nameRecord.isFunctionName(); return _nameRecord.isFunctionName();
} }
public boolean isRange() { public boolean isRange() {
return hasFormula(); // TODO - is this right? return hasFormula(); // TODO - is this right?
} }
public NamePtg createPtg() { public NamePtg createPtg() {
return new NamePtg(_index); return new NamePtg(_index);
} }
} }
public SpreadsheetVersion getSpreadsheetVersion(){ public SpreadsheetVersion getSpreadsheetVersion(){
return SpreadsheetVersion.EXCEL2007; return SpreadsheetVersion.EXCEL2007;
} }
} }

View File

@ -406,9 +406,9 @@ public final class XSSFCell implements Cell {
if (cachedValueType != expectedTypeCode) { if (cachedValueType != expectedTypeCode) {
throw typeMismatch(expectedTypeCode, cachedValueType, true); throw typeMismatch(expectedTypeCode, cachedValueType, true);
} }
} }
/** /**
* Set a string value for the cell. * Set a string value for the cell.
* *
* @param str value to set the cell to. For formulas we'll set the formula * @param str value to set the cell to. For formulas we'll set the formula
@ -506,7 +506,7 @@ public final class XSSFCell implements Cell {
XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(sheet.getWorkbook()); XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(sheet.getWorkbook());
SharedFormula sf = new SharedFormula(SpreadsheetVersion.EXCEL2007); SharedFormula sf = new SharedFormula(SpreadsheetVersion.EXCEL2007);
Ptg[] ptgs = FormulaParser.parse(sharedFormula, fpb, FormulaType.CELL, sheetIndex); Ptg[] ptgs = FormulaParser.parse(sharedFormula, fpb, FormulaType.CELL, sheetIndex, getRowIndex());
Ptg[] fmla = sf.convertSharedFormulas(ptgs, Ptg[] fmla = sf.convertSharedFormulas(ptgs,
getRowIndex() - ref.getFirstRow(), getColumnIndex() - ref.getFirstColumn()); getRowIndex() - ref.getFirstRow(), getColumnIndex() - ref.getFirstColumn());
return FormulaRenderer.toFormulaString(fpb, fmla); return FormulaRenderer.toFormulaString(fpb, fmla);
@ -550,7 +550,7 @@ public final class XSSFCell implements Cell {
XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(wb); XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(wb);
//validate through the FormulaParser //validate through the FormulaParser
FormulaParser.parse(formula, fpb, formulaType, wb.getSheetIndex(getSheet())); FormulaParser.parse(formula, fpb, formulaType, wb.getSheetIndex(getSheet()), getRowIndex());
CTCellFormula f = CTCellFormula.Factory.newInstance(); CTCellFormula f = CTCellFormula.Factory.newInstance();
f.setStringValue(formula); f.setStringValue(formula);
@ -925,8 +925,8 @@ public final class XSSFCell implements Cell {
throw new IllegalArgumentException("Illegal cell type: " + cellType); throw new IllegalArgumentException("Illegal cell type: " + cellType);
} }
if (cellType != CELL_TYPE_FORMULA && _cell.isSetF()) { if (cellType != CELL_TYPE_FORMULA && _cell.isSetF()) {
_cell.unsetF(); _cell.unsetF();
} }
} }
/** /**

View File

@ -27,29 +27,32 @@ import org.apache.poi.ss.formula.ptg.Ptg;
* Internal POI use only * Internal POI use only
*/ */
public final class XSSFEvaluationWorkbook extends BaseXSSFEvaluationWorkbook { public final class XSSFEvaluationWorkbook extends BaseXSSFEvaluationWorkbook {
public static XSSFEvaluationWorkbook create(XSSFWorkbook book) { public static XSSFEvaluationWorkbook create(XSSFWorkbook book) {
if (book == null) { if (book == null) {
return null; return null;
} }
return new XSSFEvaluationWorkbook(book); return new XSSFEvaluationWorkbook(book);
} }
private XSSFEvaluationWorkbook(XSSFWorkbook book) { private XSSFEvaluationWorkbook(XSSFWorkbook book) {
super(book); super(book);
} }
public int getSheetIndex(EvaluationSheet evalSheet) { @Override
XSSFSheet sheet = ((XSSFEvaluationSheet)evalSheet).getXSSFSheet(); public int getSheetIndex(EvaluationSheet evalSheet) {
return _uBook.getSheetIndex(sheet); XSSFSheet sheet = ((XSSFEvaluationSheet)evalSheet).getXSSFSheet();
} return _uBook.getSheetIndex(sheet);
}
public EvaluationSheet getSheet(int sheetIndex) { @Override
return new XSSFEvaluationSheet(_uBook.getSheetAt(sheetIndex)); public EvaluationSheet getSheet(int sheetIndex) {
} return new XSSFEvaluationSheet(_uBook.getSheetAt(sheetIndex));
}
@Override
public Ptg[] getFormulaTokens(EvaluationCell evalCell) { public Ptg[] getFormulaTokens(EvaluationCell evalCell) {
XSSFCell cell = ((XSSFEvaluationCell)evalCell).getXSSFCell(); XSSFCell cell = ((XSSFEvaluationCell)evalCell).getXSSFCell();
XSSFEvaluationWorkbook frBook = XSSFEvaluationWorkbook.create(_uBook); XSSFEvaluationWorkbook frBook = XSSFEvaluationWorkbook.create(_uBook);
return FormulaParser.parse(cell.getCellFormula(), frBook, FormulaType.CELL, _uBook.getSheetIndex(cell.getSheet())); return FormulaParser.parse(cell.getCellFormula(), frBook, FormulaType.CELL, _uBook.getSheetIndex(cell.getSheet()), cell.getRowIndex());
} }
} }

View File

@ -192,7 +192,7 @@ public final class XSSFName implements Name {
public void setRefersToFormula(String formulaText) { public void setRefersToFormula(String formulaText) {
XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(_workbook); XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(_workbook);
//validate through the FormulaParser //validate through the FormulaParser
FormulaParser.parse(formulaText, fpb, FormulaType.NAMEDRANGE, getSheetIndex()); FormulaParser.parse(formulaText, fpb, FormulaType.NAMEDRANGE, getSheetIndex(), -1);
_ctName.setStringValue(formulaText); _ctName.setStringValue(formulaText);
} }
@ -203,7 +203,7 @@ public final class XSSFName implements Name {
return false; return false;
} }
XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(_workbook); XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(_workbook);
Ptg[] ptgs = FormulaParser.parse(formulaText, fpb, FormulaType.NAMEDRANGE, getSheetIndex()); Ptg[] ptgs = FormulaParser.parse(formulaText, fpb, FormulaType.NAMEDRANGE, getSheetIndex(), -1);
return Ptg.doesFormulaReferToDeletedCell(ptgs); return Ptg.doesFormulaReferToDeletedCell(ptgs);
} }

View File

@ -24,13 +24,17 @@ import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Locale;
import org.apache.poi.POIXMLDocumentPart; import org.apache.poi.POIXMLDocumentPart;
import org.apache.poi.openxml4j.opc.PackagePart; import org.apache.poi.openxml4j.opc.PackagePart;
import org.apache.poi.openxml4j.opc.PackageRelationship; 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.ss.util.CellReference;
import org.apache.poi.xssf.usermodel.helpers.XSSFXmlColumnPr; import org.apache.poi.xssf.usermodel.helpers.XSSFXmlColumnPr;
import org.apache.poi.util.StringUtil;
import org.apache.xmlbeans.XmlException; import org.apache.xmlbeans.XmlException;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTTable; import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTTable;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTTableColumn; import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTTableColumn;
@ -48,10 +52,12 @@ import org.openxmlformats.schemas.spreadsheetml.x2006.main.TableDocument;
* *
* @author Roberto Manicardi * @author Roberto Manicardi
*/ */
public class XSSFTable extends POIXMLDocumentPart { public class XSSFTable extends POIXMLDocumentPart implements Table {
private CTTable ctTable; private CTTable ctTable;
private List<XSSFXmlColumnPr> xmlColumnPr; private List<XSSFXmlColumnPr> xmlColumnPr;
private CTTableColumn[] ctColumns;
private HashMap<String, Integer> columnMap;
private CellReference startCellReference; private CellReference startCellReference;
private CellReference endCellReference; private CellReference endCellReference;
private String commonXPath; private String commonXPath;
@ -107,7 +113,7 @@ public class XSSFTable extends POIXMLDocumentPart {
out.close(); out.close();
} }
public CTTable getCTTable(){ public CTTable getCTTable() {
return ctTable; return ctTable;
} }
@ -117,32 +123,41 @@ public class XSSFTable extends POIXMLDocumentPart {
* @return true if the Table element contain mappings * @return true if the Table element contain mappings
*/ */
public boolean mapsTo(long id){ public boolean mapsTo(long id){
boolean maps =false;
List<XSSFXmlColumnPr> pointers = getXmlColumnPrs(); List<XSSFXmlColumnPr> pointers = getXmlColumnPrs();
for (XSSFXmlColumnPr pointer: pointers) { for (XSSFXmlColumnPr pointer: pointers) {
if (pointer.getMapId()==id) { if (pointer.getMapId()==id) {
maps=true; return true;
break;
} }
} }
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();
}
return ctColumns;
}
/** /**
* *
* Calculates the xpath of the root element for the table. This will be the common part * Calculates the xpath of the root element for the table. This will be the common part
* of all the mapping's xpaths * 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 * @return the xpath of the table's root element
*/ */
public String getCommonXpath() { public String getCommonXpath() {
if (commonXPath == null) { if (commonXPath == null) {
String[] commonTokens = {}; String[] commonTokens = {};
for (CTTableColumn column :ctTable.getTableColumns().getTableColumnArray()) { for (CTTableColumn column : getTableColumns()) {
if (column.getXmlColumnPr()!=null) { if (column.getXmlColumnPr()!=null) {
String xpath = column.getXmlColumnPr().getXpath(); String xpath = column.getXmlColumnPr().getXpath();
String[] tokens = xpath.split("/"); String[] tokens = xpath.split("/");
@ -166,21 +181,24 @@ public class XSSFTable extends POIXMLDocumentPart {
} }
} }
commonXPath = ""; commonTokens[0] = "";
for (int i = 1 ; i< commonTokens.length;i++) { commonXPath = StringUtil.join(commonTokens, "/");
commonXPath +="/"+commonTokens[i];
}
} }
return commonXPath; 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() { public List<XSSFXmlColumnPr> getXmlColumnPrs() {
if (xmlColumnPr==null) { if (xmlColumnPr==null) {
xmlColumnPr = new ArrayList<XSSFXmlColumnPr>(); xmlColumnPr = new ArrayList<XSSFXmlColumnPr>();
for (CTTableColumn column:ctTable.getTableColumns().getTableColumnArray()) { for (CTTableColumn column: getTableColumns()) {
if (column.getXmlColumnPr()!=null) { if (column.getXmlColumnPr()!=null) {
XSSFXmlColumnPr columnPr = new XSSFXmlColumnPr(this,column,column.getXmlColumnPr()); XSSFXmlColumnPr columnPr = new XSSFXmlColumnPr(this,column,column.getXmlColumnPr());
xmlColumnPr.add(columnPr); xmlColumnPr.add(columnPr);
@ -283,7 +301,7 @@ public class XSSFTable extends POIXMLDocumentPart {
* Headers <em>must</em> be in sync, otherwise Excel will display a * Headers <em>must</em> be in sync, otherwise Excel will display a
* "Found unreadable content" message on startup. * "Found unreadable content" message on startup.
*/ */
public void updateHeaders(){ public void updateHeaders() {
XSSFSheet sheet = (XSSFSheet)getParent(); XSSFSheet sheet = (XSSFSheet)getParent();
CellReference ref = getStartCellReference(); CellReference ref = getStartCellReference();
if(ref == null) return; if(ref == null) return;
@ -301,6 +319,83 @@ public class XSSFTable extends POIXMLDocumentPart {
} }
cellnum++; cellnum++;
} }
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) {
// FIXME: replace with org.apache.commons.collections.map.CaseInsensitiveMap
int count = getTableColumns().length;
columnMap = new HashMap<String, Integer>(count);
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(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();
}
} }

View File

@ -33,6 +33,7 @@ import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.NoSuchElementException; import java.util.NoSuchElementException;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@ -1761,7 +1762,7 @@ public class XSSFWorkbook extends POIXMLDocument implements Workbook {
* Get the document's embedded files. * Get the document's embedded files.
*/ */
@Override @Override
public List<PackagePart> getAllEmbedds() throws OpenXML4JException { public List<PackagePart> getAllEmbedds() throws OpenXML4JException {
List<PackagePart> embedds = new LinkedList<PackagePart>(); List<PackagePart> embedds = new LinkedList<PackagePart>();
for(XSSFSheet sheet : sheets){ for(XSSFSheet sheet : sheets){
@ -1928,7 +1929,7 @@ public class XSSFWorkbook extends POIXMLDocument implements Workbook {
*/ */
@Internal @Internal
public MapInfo getMapInfo(){ public MapInfo getMapInfo(){
return mapInfo; return mapInfo;
} }
/** /**
@ -1945,92 +1946,92 @@ public class XSSFWorkbook extends POIXMLDocument implements Workbook {
throw new RuntimeException("Not Implemented - see bug #57184"); throw new RuntimeException("Not Implemented - see bug #57184");
} }
/** /**
* Specifies a boolean value that indicates whether structure of workbook is locked. <br/> * Specifies a boolean value that indicates whether structure of workbook is locked. <br/>
* A value true indicates the structure of the workbook is locked. Worksheets in the workbook can't be moved, * A value true indicates the structure of the workbook is locked. Worksheets in the workbook can't be moved,
* deleted, hidden, unhidden, or renamed, and new worksheets can't be inserted.<br/> * deleted, hidden, unhidden, or renamed, and new worksheets can't be inserted.<br/>
* A value of false indicates the structure of the workbook is not locked.<br/> * A value of false indicates the structure of the workbook is not locked.<br/>
* *
* @return true if structure of workbook is locked * @return true if structure of workbook is locked
*/ */
public boolean isStructureLocked() { public boolean isStructureLocked() {
return workbookProtectionPresent() && workbook.getWorkbookProtection().getLockStructure(); return workbookProtectionPresent() && workbook.getWorkbookProtection().getLockStructure();
} }
/** /**
* Specifies a boolean value that indicates whether the windows that comprise the workbook are locked. <br/> * Specifies a boolean value that indicates whether the windows that comprise the workbook are locked. <br/>
* A value of true indicates the workbook windows are locked. Windows are the same size and position each time the * A value of true indicates the workbook windows are locked. Windows are the same size and position each time the
* workbook is opened.<br/> * workbook is opened.<br/>
* A value of false indicates the workbook windows are not locked. * A value of false indicates the workbook windows are not locked.
* *
* @return true if windows that comprise the workbook are locked * @return true if windows that comprise the workbook are locked
*/ */
public boolean isWindowsLocked() { public boolean isWindowsLocked() {
return workbookProtectionPresent() && workbook.getWorkbookProtection().getLockWindows(); return workbookProtectionPresent() && workbook.getWorkbookProtection().getLockWindows();
} }
/** /**
* Specifies a boolean value that indicates whether the workbook is locked for revisions. * Specifies a boolean value that indicates whether the workbook is locked for revisions.
* *
* @return true if the workbook is locked for revisions. * @return true if the workbook is locked for revisions.
*/ */
public boolean isRevisionLocked() { public boolean isRevisionLocked() {
return workbookProtectionPresent() && workbook.getWorkbookProtection().getLockRevision(); return workbookProtectionPresent() && workbook.getWorkbookProtection().getLockRevision();
} }
/** /**
* Locks the structure of workbook. * Locks the structure of workbook.
*/ */
public void lockStructure() { public void lockStructure() {
safeGetWorkbookProtection().setLockStructure(true); safeGetWorkbookProtection().setLockStructure(true);
} }
/** /**
* Unlocks the structure of workbook. * Unlocks the structure of workbook.
*/ */
public void unLockStructure() { public void unLockStructure() {
safeGetWorkbookProtection().setLockStructure(false); safeGetWorkbookProtection().setLockStructure(false);
} }
/** /**
* Locks the windows that comprise the workbook. * Locks the windows that comprise the workbook.
*/ */
public void lockWindows() { public void lockWindows() {
safeGetWorkbookProtection().setLockWindows(true); safeGetWorkbookProtection().setLockWindows(true);
} }
/** /**
* Unlocks the windows that comprise the workbook. * Unlocks the windows that comprise the workbook.
*/ */
public void unLockWindows() { public void unLockWindows() {
safeGetWorkbookProtection().setLockWindows(false); safeGetWorkbookProtection().setLockWindows(false);
} }
/** /**
* Locks the workbook for revisions. * Locks the workbook for revisions.
*/ */
public void lockRevision() { public void lockRevision() {
safeGetWorkbookProtection().setLockRevision(true); safeGetWorkbookProtection().setLockRevision(true);
} }
/** /**
* Unlocks the workbook for revisions. * Unlocks the workbook for revisions.
*/ */
public void unLockRevision() { public void unLockRevision() {
safeGetWorkbookProtection().setLockRevision(false); safeGetWorkbookProtection().setLockRevision(false);
} }
/** /**
* Sets the workbook password. * Sets the workbook password.
* *
* @param password if null, the password will be removed * @param password if null, the password will be removed
* @param hashAlgo if null, the password will be set as XOR password (Excel 2010 and earlier) * @param hashAlgo if null, the password will be set as XOR password (Excel 2010 and earlier)
* otherwise the given algorithm is used for calculating the hash password (Excel 2013) * otherwise the given algorithm is used for calculating the hash password (Excel 2013)
*/ */
public void setWorkbookPassword(String password, HashAlgorithm hashAlgo) { public void setWorkbookPassword(String password, HashAlgorithm hashAlgo) {
if (password == null && !workbookProtectionPresent()) return; if (password == null && !workbookProtectionPresent()) return;
setPassword(safeGetWorkbookProtection(), password, hashAlgo, "workbook"); setPassword(safeGetWorkbookProtection(), password, hashAlgo, "workbook");
} }
/** /**
* Validate the password against the stored hash, the hashing method will be determined * Validate the password against the stored hash, the hashing method will be determined
@ -2073,9 +2074,9 @@ public class XSSFWorkbook extends POIXMLDocument implements Workbook {
} }
} }
private boolean workbookProtectionPresent() { private boolean workbookProtectionPresent() {
return workbook.isSetWorkbookProtection(); return workbook.isSetWorkbookProtection();
} }
private CTWorkbookProtection safeGetWorkbookProtection() { private CTWorkbookProtection safeGetWorkbookProtection() {
if (!workbookProtectionPresent()){ if (!workbookProtectionPresent()){
@ -2083,7 +2084,7 @@ public class XSSFWorkbook extends POIXMLDocument implements Workbook {
} }
return workbook.getWorkbookProtection(); return workbook.getWorkbookProtection();
} }
/** /**
* *
* Returns the locator of user-defined functions. * Returns the locator of user-defined functions.
@ -2261,4 +2262,24 @@ public class XSSFWorkbook extends POIXMLDocument implements Workbook {
public SpreadsheetVersion getSpreadsheetVersion() { public SpreadsheetVersion getSpreadsheetVersion() {
return SpreadsheetVersion.EXCEL2007; return SpreadsheetVersion.EXCEL2007;
} }
/**
* Returns the data table with the given name (case insensitive).
*
* @param name the data table name (case-insensitive)
* @return The Data table in the workbook named <tt>name</tt>, or <tt>null</tt> if no table is named <tt>name</tt>.
* @since 3.15 beta 2
*/
public XSSFTable getTable(String name) {
if (name != null && sheets != null) {
for (XSSFSheet sheet : sheets) {
for (XSSFTable tbl : sheet.getTables()) {
if (name.equalsIgnoreCase(tbl.getName())) {
return tbl;
}
}
}
}
return null;
}
} }

View File

@ -94,7 +94,7 @@ public final class XSSFFormulaUtils {
String formula = f.getStringValue(); String formula = f.getStringValue();
if (formula != null && formula.length() > 0) { if (formula != null && formula.length() > 0) {
int sheetIndex = _wb.getSheetIndex(cell.getSheet()); int sheetIndex = _wb.getSheetIndex(cell.getSheet());
Ptg[] ptgs = FormulaParser.parse(formula, _fpwb, FormulaType.CELL, sheetIndex); Ptg[] ptgs = FormulaParser.parse(formula, _fpwb, FormulaType.CELL, sheetIndex, cell.getRowIndex());
for (Ptg ptg : ptgs) { for (Ptg ptg : ptgs) {
updatePtg(ptg, oldName, newName); updatePtg(ptg, oldName, newName);
} }
@ -113,7 +113,8 @@ public final class XSSFFormulaUtils {
String formula = name.getRefersToFormula(); String formula = name.getRefersToFormula();
if (formula != null) { if (formula != null) {
int sheetIndex = name.getSheetIndex(); int sheetIndex = name.getSheetIndex();
Ptg[] ptgs = FormulaParser.parse(formula, _fpwb, FormulaType.NAMEDRANGE, sheetIndex); int rowIndex = -1; //don't care
Ptg[] ptgs = FormulaParser.parse(formula, _fpwb, FormulaType.NAMEDRANGE, sheetIndex, rowIndex);
for (Ptg ptg : ptgs) { for (Ptg ptg : ptgs) {
updatePtg(ptg, oldName, newName); updatePtg(ptg, oldName, newName);
} }

View File

@ -134,8 +134,9 @@ public final class XSSFRowShifter {
XSSFName name = wb.getNameAt(i); XSSFName name = wb.getNameAt(i);
String formula = name.getRefersToFormula(); String formula = name.getRefersToFormula();
int sheetIndex = name.getSheetIndex(); 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); Ptg[] ptgs = FormulaParser.parse(formula, fpb, FormulaType.NAMEDRANGE, sheetIndex, rowIndex);
if (shifter.adjustFormula(ptgs, sheetIndex)) { if (shifter.adjustFormula(ptgs, sheetIndex)) {
String shiftedFmla = FormulaRenderer.toFormulaString(fpb, ptgs); String shiftedFmla = FormulaRenderer.toFormulaString(fpb, ptgs);
name.setRefersToFormula(shiftedFmla); name.setRefersToFormula(shiftedFmla);
@ -218,10 +219,11 @@ public final class XSSFRowShifter {
XSSFSheet sheet = row.getSheet(); XSSFSheet sheet = row.getSheet();
XSSFWorkbook wb = sheet.getWorkbook(); XSSFWorkbook wb = sheet.getWorkbook();
int sheetIndex = wb.getSheetIndex(sheet); int sheetIndex = wb.getSheetIndex(sheet);
final int rowIndex = row.getRowNum();
XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(wb); XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(wb);
try { try {
Ptg[] ptgs = FormulaParser.parse(formula, fpb, FormulaType.CELL, sheetIndex); Ptg[] ptgs = FormulaParser.parse(formula, fpb, FormulaType.CELL, sheetIndex, rowIndex);
String shiftedFmla = null; String shiftedFmla = null;
if (shifter.adjustFormula(ptgs, sheetIndex)) { if (shifter.adjustFormula(ptgs, sheetIndex)) {
shiftedFmla = FormulaRenderer.toFormulaString(fpb, ptgs); shiftedFmla = FormulaRenderer.toFormulaString(fpb, ptgs);
@ -238,6 +240,7 @@ public final class XSSFRowShifter {
public void updateConditionalFormatting(FormulaShifter shifter) { public void updateConditionalFormatting(FormulaShifter shifter) {
XSSFWorkbook wb = sheet.getWorkbook(); XSSFWorkbook wb = sheet.getWorkbook();
int sheetIndex = wb.getSheetIndex(sheet); int sheetIndex = wb.getSheetIndex(sheet);
final int rowIndex = -1; //don't care, structured references not allowed in conditional formatting
XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(wb); XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(wb);
CTWorksheet ctWorksheet = sheet.getCTWorksheet(); CTWorksheet ctWorksheet = sheet.getCTWorksheet();
@ -283,7 +286,7 @@ public final class XSSFRowShifter {
String[] formulaArray = cfRule.getFormulaArray(); String[] formulaArray = cfRule.getFormulaArray();
for (int i = 0; i < formulaArray.length; i++) { for (int i = 0; i < formulaArray.length; i++) {
String formula = formulaArray[i]; String formula = formulaArray[i];
Ptg[] ptgs = FormulaParser.parse(formula, fpb, FormulaType.CELL, sheetIndex); Ptg[] ptgs = FormulaParser.parse(formula, fpb, FormulaType.CELL, sheetIndex, rowIndex);
if (shifter.adjustFormula(ptgs, sheetIndex)) { if (shifter.adjustFormula(ptgs, sheetIndex)) {
String shiftedFmla = FormulaRenderer.toFormulaString(fpb, ptgs); String shiftedFmla = FormulaRenderer.toFormulaString(fpb, ptgs);
cfRule.setFormulaArray(i, shiftedFmla); cfRule.setFormulaArray(i, shiftedFmla);

View File

@ -18,6 +18,11 @@
*/ */
package org.apache.poi.ss.formula; package org.apache.poi.ss.formula;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import org.apache.poi.hssf.usermodel.HSSFEvaluationWorkbook; import org.apache.poi.hssf.usermodel.HSSFEvaluationWorkbook;
import org.apache.poi.hssf.usermodel.HSSFWorkbook; import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.formula.ptg.AbstractFunctionPtg; import org.apache.poi.ss.formula.ptg.AbstractFunctionPtg;
@ -28,7 +33,7 @@ import org.apache.poi.xssf.XSSFTestDataSamples;
import org.apache.poi.xssf.usermodel.XSSFEvaluationWorkbook; import org.apache.poi.xssf.usermodel.XSSFEvaluationWorkbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook; import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import junit.framework.TestCase; import org.junit.Test;
/** /**
* Test {@link FormulaParser}'s handling of row numbers at the edge of the * Test {@link FormulaParser}'s handling of row numbers at the edge of the
@ -36,8 +41,9 @@ import junit.framework.TestCase;
* *
* @author David North * @author David North
*/ */
public class TestFormulaParser extends TestCase { public class TestFormulaParser {
@Test
public void testHSSFFailsForOver65536() { public void testHSSFFailsForOver65536() {
FormulaParsingWorkbook workbook = HSSFEvaluationWorkbook.create(new HSSFWorkbook()); FormulaParsingWorkbook workbook = HSSFEvaluationWorkbook.create(new HSSFWorkbook());
try { try {
@ -49,16 +55,19 @@ public class TestFormulaParser extends TestCase {
} }
} }
@Test
public void testHSSFPassCase() { public void testHSSFPassCase() {
FormulaParsingWorkbook workbook = HSSFEvaluationWorkbook.create(new HSSFWorkbook()); FormulaParsingWorkbook workbook = HSSFEvaluationWorkbook.create(new HSSFWorkbook());
FormulaParser.parse("Sheet1!1:65536", workbook, FormulaType.CELL, 0); FormulaParser.parse("Sheet1!1:65536", workbook, FormulaType.CELL, 0);
} }
@Test
public void testXSSFWorksForOver65536() { public void testXSSFWorksForOver65536() {
FormulaParsingWorkbook workbook = XSSFEvaluationWorkbook.create(new XSSFWorkbook()); FormulaParsingWorkbook workbook = XSSFEvaluationWorkbook.create(new XSSFWorkbook());
FormulaParser.parse("Sheet1!1:65537", workbook, FormulaType.CELL, 0); FormulaParser.parse("Sheet1!1:65537", workbook, FormulaType.CELL, 0);
} }
@Test
public void testXSSFFailCase() { public void testXSSFFailCase() {
FormulaParsingWorkbook workbook = XSSFEvaluationWorkbook.create(new XSSFWorkbook()); FormulaParsingWorkbook workbook = XSSFEvaluationWorkbook.create(new XSSFWorkbook());
try { try {
@ -71,6 +80,7 @@ public class TestFormulaParser extends TestCase {
} }
// copied from org.apache.poi.hssf.model.TestFormulaParser // copied from org.apache.poi.hssf.model.TestFormulaParser
@Test
public void testMacroFunction() throws Exception { public void testMacroFunction() throws Exception {
// testNames.xlsm contains a VB function called 'myFunc' // testNames.xlsm contains a VB function called 'myFunc'
final String testFile = "testNames.xlsm"; final String testFile = "testNames.xlsm";
@ -126,6 +136,7 @@ public class TestFormulaParser extends TestCase {
} }
} }
@Test
public void testParserErrors() throws Exception { public void testParserErrors() throws Exception {
XSSFWorkbook wb = XSSFTestDataSamples.openSampleWorkbook("testNames.xlsm"); XSSFWorkbook wb = XSSFTestDataSamples.openSampleWorkbook("testNames.xlsm");
try { try {

View File

@ -0,0 +1,63 @@
package org.apache.poi.ss.formula;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.fail;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellValue;
import org.apache.poi.ss.usermodel.FormulaEvaluator;
import org.apache.poi.ss.usermodel.Table;
import org.apache.poi.xssf.XSSFTestDataSamples;
import org.apache.poi.xssf.usermodel.XSSFFormulaEvaluator;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.junit.Test;
/**
* Tests Excel Table expressions (structured references)
* @see <a href="https://support.office.com/en-us/article/Using-structured-references-with-Excel-tables-F5ED2452-2337-4F71-BED3-C8AE6D2B276E">
* Excel Structured Reference Syntax
* </a>
*/
public class TestStructuredReferences {
/**
* Test the regular expression used in INDIRECT() evaluation to recognize structured references
*/
@Test
public void testTableExpressionSyntax() {
assertTrue("Valid structured reference syntax didn't match expression", Table.isStructuredReference.matcher("abc[col1]").matches());
assertTrue("Valid structured reference syntax didn't match expression", Table.isStructuredReference.matcher("_abc[col1]").matches());
assertTrue("Valid structured reference syntax didn't match expression", Table.isStructuredReference.matcher("_[col1]").matches());
assertTrue("Valid structured reference syntax didn't match expression", Table.isStructuredReference.matcher("\\[col1]").matches());
assertTrue("Valid structured reference syntax didn't match expression", Table.isStructuredReference.matcher("\\[col1]").matches());
assertTrue("Valid structured reference syntax didn't match expression", Table.isStructuredReference.matcher("\\[#This Row]").matches());
assertTrue("Valid structured reference syntax didn't match expression", Table.isStructuredReference.matcher("\\[ [col1], [col2] ]").matches());
// can't have a space between the table name and open bracket
assertFalse("Invalid structured reference syntax didn't fail expression", Table.isStructuredReference.matcher("\\abc [ [col1], [col2] ]").matches());
}
@Test
public void testTableFormulas() throws Exception {
XSSFWorkbook wb = XSSFTestDataSamples.openSampleWorkbook("StructuredReferences.xlsx");
try {
final FormulaEvaluator eval = new XSSFFormulaEvaluator(wb);
confirm(eval, wb.getSheet("Table").getRow(5).getCell(0), 49);
confirm(eval, wb.getSheet("Formulas").getRow(0).getCell(0), 209);
} finally {
wb.close();
}
}
private static void confirm(FormulaEvaluator fe, Cell cell, double expectedResult) {
fe.clearAllCachedResultValues();
CellValue cv = fe.evaluate(cell);
if (cv.getCellType() != Cell.CELL_TYPE_NUMERIC) {
fail("expected numeric cell type but got " + cv.formatAsString());
}
assertEquals(expectedResult, cv.getNumberValue(), 0.0);
}
}

View File

@ -22,6 +22,8 @@ import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail; import static org.junit.Assert.fail;
import java.io.IOException;
import org.apache.poi.hssf.HSSFTestDataSamples; import org.apache.poi.hssf.HSSFTestDataSamples;
import org.apache.poi.hssf.usermodel.HSSFCell; import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFEvaluationWorkbook; import org.apache.poi.hssf.usermodel.HSSFEvaluationWorkbook;
@ -41,12 +43,15 @@ import org.junit.Test;
import java.util.Arrays; import java.util.Arrays;
public final class TestXSSFFormulaParser { public final class TestXSSFFormulaParser {
private static Ptg[] parse(FormulaParsingWorkbook fpb, String fmla) { private static Ptg[] parse(FormulaParsingWorkbook fpb, String fmla) {
return FormulaParser.parse(fmla, fpb, FormulaType.CELL, -1); return FormulaParser.parse(fmla, fpb, FormulaType.CELL, -1);
} }
private static Ptg[] parse(FormulaParsingWorkbook fpb, String fmla, int rowIndex) {
return FormulaParser.parse(fmla, fpb, FormulaType.CELL, -1, rowIndex);
}
@Test @Test
public void basicParsing() { public void basicParsing() throws IOException {
XSSFWorkbook wb = new XSSFWorkbook(); XSSFWorkbook wb = new XSSFWorkbook();
XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(wb); XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(wb);
Ptg[] ptgs; Ptg[] ptgs;
@ -118,10 +123,12 @@ public final class TestXSSFFormulaParser {
assertEquals(AttrPtg.class, ptgs[1].getClass()); assertEquals(AttrPtg.class, ptgs[1].getClass());
assertEquals("Sheet1!A1:B3", ptgs[0].toFormulaString()); assertEquals("Sheet1!A1:B3", ptgs[0].toFormulaString());
assertEquals("SUM", ptgs[1].toFormulaString()); assertEquals("SUM", ptgs[1].toFormulaString());
wb.close();
} }
@Test @Test
public void builtInFormulas() { public void builtInFormulas() throws IOException {
XSSFWorkbook wb = new XSSFWorkbook(); XSSFWorkbook wb = new XSSFWorkbook();
XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(wb); XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(wb);
Ptg[] ptgs; Ptg[] ptgs;
@ -134,10 +141,12 @@ public final class TestXSSFFormulaParser {
assertEquals(2, ptgs.length); assertEquals(2, ptgs.length);
assertTrue("Had " + Arrays.toString(ptgs), ptgs[0] instanceof IntPtg); assertTrue("Had " + Arrays.toString(ptgs), ptgs[0] instanceof IntPtg);
assertTrue("Had " + Arrays.toString(ptgs), ptgs[1] instanceof FuncPtg); assertTrue("Had " + Arrays.toString(ptgs), ptgs[1] instanceof FuncPtg);
wb.close();
} }
@Test @Test
public void formaulReferncesSameWorkbook() { public void formulaReferencesSameWorkbook() throws IOException {
// Use a test file with "other workbook" style references // Use a test file with "other workbook" style references
// to itself // to itself
XSSFWorkbook wb = XSSFTestDataSamples.openSampleWorkbook("56737.xlsx"); XSSFWorkbook wb = XSSFTestDataSamples.openSampleWorkbook("56737.xlsx");
@ -153,10 +162,12 @@ public final class TestXSSFFormulaParser {
assertEquals(null, ((NameXPxg)ptgs[0]).getSheetName()); assertEquals(null, ((NameXPxg)ptgs[0]).getSheetName());
assertEquals("NR_Global_B2",((NameXPxg)ptgs[0]).getNameName()); assertEquals("NR_Global_B2",((NameXPxg)ptgs[0]).getNameName());
assertEquals("[0]!NR_Global_B2",((NameXPxg)ptgs[0]).toFormulaString()); assertEquals("[0]!NR_Global_B2",((NameXPxg)ptgs[0]).toFormulaString());
wb.close();
} }
@Test @Test
public void formulaReferencesOtherSheets() { public void formulaReferencesOtherSheets() throws IOException {
// Use a test file with the named ranges in place // Use a test file with the named ranges in place
XSSFWorkbook wb = XSSFTestDataSamples.openSampleWorkbook("56737.xlsx"); XSSFWorkbook wb = XSSFTestDataSamples.openSampleWorkbook("56737.xlsx");
XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(wb); XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(wb);
@ -193,10 +204,12 @@ public final class TestXSSFFormulaParser {
assertEquals(1, ptgs.length); assertEquals(1, ptgs.length);
assertEquals(NamePtg.class, ptgs[0].getClass()); assertEquals(NamePtg.class, ptgs[0].getClass());
assertEquals("NR_Global_B2",((NamePtg)ptgs[0]).toFormulaString(fpb)); assertEquals("NR_Global_B2",((NamePtg)ptgs[0]).toFormulaString(fpb));
wb.close();
} }
@Test @Test
public void formulaReferencesOtherWorkbook() { public void formulaReferencesOtherWorkbook() throws IOException {
// Use a test file with the external linked table in place // Use a test file with the external linked table in place
XSSFWorkbook wb = XSSFTestDataSamples.openSampleWorkbook("ref-56737.xlsx"); XSSFWorkbook wb = XSSFTestDataSamples.openSampleWorkbook("ref-56737.xlsx");
XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(wb); XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(wb);
@ -228,6 +241,8 @@ public final class TestXSSFFormulaParser {
assertEquals(null, ((NameXPxg)ptgs[0]).getSheetName()); assertEquals(null, ((NameXPxg)ptgs[0]).getSheetName());
assertEquals("NR_Global_B2",((NameXPxg)ptgs[0]).getNameName()); assertEquals("NR_Global_B2",((NameXPxg)ptgs[0]).getNameName());
assertEquals("[1]!NR_Global_B2",((NameXPxg)ptgs[0]).toFormulaString()); assertEquals("[1]!NR_Global_B2",((NameXPxg)ptgs[0]).toFormulaString());
wb.close();
} }
/** /**
@ -241,7 +256,7 @@ public final class TestXSSFFormulaParser {
* (but not evaluate - that's elsewhere in the test suite) * (but not evaluate - that's elsewhere in the test suite)
*/ */
@Test @Test
public void multiSheetReferencesHSSFandXSSF() throws Exception { public void multiSheetReferencesHSSFandXSSF() throws IOException {
Workbook[] wbs = new Workbook[] { Workbook[] wbs = new Workbook[] {
HSSFTestDataSamples.openSampleWorkbook("55906-MultiSheetRefs.xls"), HSSFTestDataSamples.openSampleWorkbook("55906-MultiSheetRefs.xls"),
XSSFTestDataSamples.openSampleWorkbook("55906-MultiSheetRefs.xlsx") XSSFTestDataSamples.openSampleWorkbook("55906-MultiSheetRefs.xlsx")
@ -363,6 +378,8 @@ public final class TestXSSFFormulaParser {
newF = s1.getRow(0).createCell(11, Cell.CELL_TYPE_FORMULA); newF = s1.getRow(0).createCell(11, Cell.CELL_TYPE_FORMULA);
newF.setCellFormula("MIN(Sheet1:Sheet2!A1:B2)"); newF.setCellFormula("MIN(Sheet1:Sheet2!A1:B2)");
assertEquals("MIN(Sheet1:Sheet2!A1:B2)", newF.getCellFormula()); assertEquals("MIN(Sheet1:Sheet2!A1:B2)", newF.getCellFormula());
wb.close();
} }
} }
@ -374,7 +391,7 @@ public final class TestXSSFFormulaParser {
} }
@Test @Test
public void test58648Single() { public void test58648Single() throws IOException {
XSSFWorkbook wb = new XSSFWorkbook(); XSSFWorkbook wb = new XSSFWorkbook();
XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(wb); XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(wb);
Ptg[] ptgs; Ptg[] ptgs;
@ -384,10 +401,12 @@ public final class TestXSSFFormulaParser {
2, ptgs.length); 2, ptgs.length);
assertTrue("Had " + Arrays.toString(ptgs), ptgs[0] instanceof RefPtg); assertTrue("Had " + Arrays.toString(ptgs), ptgs[0] instanceof RefPtg);
assertTrue("Had " + Arrays.toString(ptgs), ptgs[1] instanceof ParenthesisPtg); assertTrue("Had " + Arrays.toString(ptgs), ptgs[1] instanceof ParenthesisPtg);
wb.close();
} }
@Test @Test
public void test58648Basic() { public void test58648Basic() throws IOException {
XSSFWorkbook wb = new XSSFWorkbook(); XSSFWorkbook wb = new XSSFWorkbook();
XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(wb); XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(wb);
Ptg[] ptgs; Ptg[] ptgs;
@ -431,10 +450,12 @@ public final class TestXSSFFormulaParser {
assertTrue("Had " + Arrays.toString(ptgs), ptgs[0] instanceof RefPtg); assertTrue("Had " + Arrays.toString(ptgs), ptgs[0] instanceof RefPtg);
assertTrue("Had " + Arrays.toString(ptgs), ptgs[1] instanceof ParenthesisPtg); assertTrue("Had " + Arrays.toString(ptgs), ptgs[1] instanceof ParenthesisPtg);
assertTrue("Had " + Arrays.toString(ptgs), ptgs[2] instanceof ParenthesisPtg); assertTrue("Had " + Arrays.toString(ptgs), ptgs[2] instanceof ParenthesisPtg);
wb.close();
} }
@Test @Test
public void test58648FormulaParsing() { public void test58648FormulaParsing() throws IOException {
Workbook wb = XSSFTestDataSamples.openSampleWorkbook("58648.xlsx"); Workbook wb = XSSFTestDataSamples.openSampleWorkbook("58648.xlsx");
FormulaEvaluator evaluator = wb.getCreationHelper().createFormulaEvaluator(); FormulaEvaluator evaluator = wb.getCreationHelper().createFormulaEvaluator();
@ -460,10 +481,12 @@ public final class TestXSSFFormulaParser {
Cell cell = sheet.getRow(1).getCell(4); Cell cell = sheet.getRow(1).getCell(4);
assertEquals(5d, cell.getNumericCellValue(), 0d); assertEquals(5d, cell.getNumericCellValue(), 0d);
wb.close();
} }
@Test @Test
public void testWhitespaceInFormula() { public void testWhitespaceInFormula() throws IOException {
XSSFWorkbook wb = new XSSFWorkbook(); XSSFWorkbook wb = new XSSFWorkbook();
XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(wb); XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(wb);
Ptg[] ptgs; Ptg[] ptgs;
@ -505,10 +528,12 @@ public final class TestXSSFFormulaParser {
assertTrue("Had " + Arrays.toString(ptgs), ptgs[1] instanceof AreaPtg); assertTrue("Had " + Arrays.toString(ptgs), ptgs[1] instanceof AreaPtg);
assertTrue("Had " + Arrays.toString(ptgs), ptgs[2] instanceof AreaPtg); assertTrue("Had " + Arrays.toString(ptgs), ptgs[2] instanceof AreaPtg);
assertTrue("Had " + Arrays.toString(ptgs), ptgs[3] instanceof IntersectionPtg); assertTrue("Had " + Arrays.toString(ptgs), ptgs[3] instanceof IntersectionPtg);
wb.close();
} }
@Test @Test
public void testWhitespaceInComplexFormula() { public void testWhitespaceInComplexFormula() throws IOException {
XSSFWorkbook wb = new XSSFWorkbook(); XSSFWorkbook wb = new XSSFWorkbook();
XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(wb); XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(wb);
Ptg[] ptgs; Ptg[] ptgs;
@ -529,5 +554,172 @@ public final class TestXSSFFormulaParser {
assertTrue("Had " + Arrays.toString(ptgs), ptgs[1] instanceof RefPtg); assertTrue("Had " + Arrays.toString(ptgs), ptgs[1] instanceof RefPtg);
assertTrue("Had " + Arrays.toString(ptgs), ptgs[2] instanceof AreaPtg); assertTrue("Had " + Arrays.toString(ptgs), ptgs[2] instanceof AreaPtg);
assertTrue("Had " + Arrays.toString(ptgs), ptgs[3] instanceof NameXPxg); assertTrue("Had " + Arrays.toString(ptgs), ptgs[3] instanceof NameXPxg);
wb.close();
}
@Test
public void parseStructuredReferences() throws IOException {
XSSFWorkbook wb = XSSFTestDataSamples.openSampleWorkbook("StructuredReferences.xlsx");
XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(wb);
Ptg[] ptgs;
/*
The following cases are tested (copied from FormulaParser.parseStructuredReference)
1 Table1[col]
2 Table1[[#Totals],[col]]
3 Table1[#Totals]
4 Table1[#All]
5 Table1[#Data]
6 Table1[#Headers]
7 Table1[#Totals]
8 Table1[#This Row]
9 Table1[[#All],[col]]
10 Table1[[#Headers],[col]]
11 Table1[[#Totals],[col]]
12 Table1[[#All],[col1]:[col2]]
13 Table1[[#Data],[col1]:[col2]]
14 Table1[[#Headers],[col1]:[col2]]
15 Table1[[#Totals],[col1]:[col2]]
16 Table1[[#Headers],[#Data],[col2]]
17 Table1[[#This Row], [col1]]
18 Table1[ [col1]:[col2] ]
*/
final String tbl = "\\_Prime.1";
final String noTotalsRowReason = ": Tables without a Totals row should return #REF! on [#Totals]";
////// Case 1: Evaluate Table1[col] with apostrophe-escaped #-signs ////////
ptgs = parse(fpb, "SUM("+tbl+"[calc='#*'#])");
assertEquals(2, ptgs.length);
// Area3DPxg [sheet=Table ! A2:A7]
assertTrue(ptgs[0] instanceof Area3DPxg);
Area3DPxg ptg0 = (Area3DPxg) ptgs[0];
assertEquals("Table", ptg0.getSheetName());
assertEquals("A2:A7", ptg0.format2DRefAsString());
// Note: structured references are evaluated and resolved to regular 3D area references.
assertEquals("Table!A2:A7", ptg0.toFormulaString());
// AttrPtg [sum ]
assertTrue(ptgs[1] instanceof AttrPtg);
AttrPtg ptg1 = (AttrPtg) ptgs[1];
assertTrue(ptg1.isSum());
////// Case 1: Evaluate "Table1[col]" ////////
ptgs = parse(fpb, tbl+"[Name]");
assertEquals(1, ptgs.length);
assertEquals("Table1[col]", "Table!B2:B7", ptgs[0].toFormulaString());
////// Case 2: Evaluate "Table1[[#Totals],[col]]" ////////
ptgs = parse(fpb, tbl+"[[#Totals],[col]]");
assertEquals(1, ptgs.length);
assertEquals("Table1[[#Totals],[col]]" + noTotalsRowReason, ErrPtg.REF_INVALID, ptgs[0]);
////// Case 3: Evaluate "Table1[#Totals]" ////////
ptgs = parse(fpb, tbl+"[#Totals]");
assertEquals(1, ptgs.length);
assertEquals("Table1[#Totals]" + noTotalsRowReason, ErrPtg.REF_INVALID, ptgs[0]);
////// Case 4: Evaluate "Table1[#All]" ////////
ptgs = parse(fpb, tbl+"[#All]");
assertEquals(1, ptgs.length);
assertEquals("Table1[#All]", "Table!A1:C7", ptgs[0].toFormulaString());
////// Case 5: Evaluate "Table1[#Data]" (excludes Header and Data rows) ////////
ptgs = parse(fpb, tbl+"[#Data]");
assertEquals(1, ptgs.length);
assertEquals("Table1[#Data]", "Table!A2:C7", ptgs[0].toFormulaString());
////// Case 6: Evaluate "Table1[#Headers]" ////////
ptgs = parse(fpb, tbl+"[#Headers]");
assertEquals(1, ptgs.length);
assertEquals("Table1[#Headers]", "Table!A1:C1", ptgs[0].toFormulaString());
////// Case 7: Evaluate "Table1[#Totals]" ////////
ptgs = parse(fpb, tbl+"[#Totals]");
assertEquals(1, ptgs.length);
assertEquals("Table1[#Totals]" + noTotalsRowReason, ErrPtg.REF_INVALID, ptgs[0]);
////// Case 8: Evaluate "Table1[#This Row]" ////////
ptgs = parse(fpb, tbl+"[#This Row]", 2);
assertEquals(1, ptgs.length);
assertEquals("Table1[#This Row]", "Table!A3:C3", ptgs[0].toFormulaString());
////// Evaluate "Table1[@]" (equivalent to "Table1[#This Row]") ////////
ptgs = parse(fpb, tbl+"[@]", 2);
assertEquals(1, ptgs.length);
assertEquals("Table!A3:C3", ptgs[0].toFormulaString());
////// Evaluate "Table1[#This Row]" when rowIndex is outside Table ////////
ptgs = parse(fpb, tbl+"[#This Row]", 10);
assertEquals(1, ptgs.length);
assertEquals("Table1[#This Row]", ErrPtg.VALUE_INVALID, ptgs[0]);
////// Evaluate "Table1[@]" when rowIndex is outside Table ////////
ptgs = parse(fpb, tbl+"[@]", 10);
assertEquals(1, ptgs.length);
assertEquals("Table1[@]", ErrPtg.VALUE_INVALID, ptgs[0]);
////// Evaluate "Table1[[#Data],[col]]" ////////
ptgs = parse(fpb, tbl+"[[#Data], [Number]]");
assertEquals(1, ptgs.length);
assertEquals("Table1[[#Data],[col]]", "Table!C2:C7", ptgs[0].toFormulaString());
////// Case 9: Evaluate "Table1[[#All],[col]]" ////////
ptgs = parse(fpb, tbl+"[[#All], [Number]]");
assertEquals(1, ptgs.length);
assertEquals("Table1[[#All],[col]]", "Table!C1:C7", ptgs[0].toFormulaString());
////// Case 10: Evaluate "Table1[[#Headers],[col]]" ////////
ptgs = parse(fpb, tbl+"[[#Headers], [Number]]");
assertEquals(1, ptgs.length);
// also acceptable: Table1!B1
assertEquals("Table1[[#Headers],[col]]", "Table!C1:C1", ptgs[0].toFormulaString());
////// Case 11: Evaluate "Table1[[#Totals],[col]]" ////////
ptgs = parse(fpb, tbl+"[[#Totals],[Name]]");
assertEquals(1, ptgs.length);
assertEquals("Table1[[#Totals],[col]]" + noTotalsRowReason, ErrPtg.REF_INVALID, ptgs[0]);
////// Case 12: Evaluate "Table1[[#All],[col1]:[col2]]" ////////
ptgs = parse(fpb, tbl+"[[#All], [Name]:[Number]]");
assertEquals(1, ptgs.length);
assertEquals("Table1[[#All],[col1]:[col2]]", "Table!B1:C7", ptgs[0].toFormulaString());
////// Case 13: Evaluate "Table1[[#Data],[col]:[col2]]" ////////
ptgs = parse(fpb, tbl+"[[#Data], [Name]:[Number]]");
assertEquals(1, ptgs.length);
assertEquals("Table1[[#Data],[col]:[col2]]", "Table!B2:C7", ptgs[0].toFormulaString());
////// Case 14: Evaluate "Table1[[#Headers],[col1]:[col2]]" ////////
ptgs = parse(fpb, tbl+"[[#Headers], [Name]:[Number]]");
assertEquals(1, ptgs.length);
assertEquals("Table1[[#Headers],[col1]:[col2]]", "Table!B1:C1", ptgs[0].toFormulaString());
////// Case 15: Evaluate "Table1[[#Totals],[col]:[col2]]" ////////
ptgs = parse(fpb, tbl+"[[#Totals], [Name]:[Number]]");
assertEquals(1, ptgs.length);
assertEquals("Table1[[#Totals],[col]:[col2]]" + noTotalsRowReason, ErrPtg.REF_INVALID, ptgs[0]);
////// Case 16: Evaluate "Table1[[#Headers],[#Data],[col]]" ////////
ptgs = parse(fpb, tbl+"[[#Headers],[#Data],[Number]]");
assertEquals(1, ptgs.length);
assertEquals("Table1[[#Headers],[#Data],[col]]", "Table!C1:C7", ptgs[0].toFormulaString());
////// Case 17: Evaluate "Table1[[#This Row], [col1]]" ////////
ptgs = parse(fpb, tbl+"[[#This Row], [Number]]", 2);
assertEquals(1, ptgs.length);
// also acceptable: Table!C3
assertEquals("Table1[[#This Row], [col1]]", "Table!C3:C3", ptgs[0].toFormulaString());
////// Case 18: Evaluate "Table1[[col]:[col2]]" ////////
ptgs = parse(fpb, tbl+"[[Name]:[Number]]");
assertEquals(1, ptgs.length);
assertEquals("Table1[[col]:[col2]]", "Table!B2:C7", ptgs[0].toFormulaString());
wb.close();
} }
} }

View File

@ -1137,7 +1137,7 @@ public final class TestXSSFSheet extends BaseTestXSheet {
} }
/** /**
* See bug #50829 * See bug #50829 test data tables
*/ */
@Test @Test
public void tables() throws IOException { public void tables() throws IOException {

View File

@ -27,7 +27,7 @@ import org.junit.runners.Suite;
@Suite.SuiteClasses({ @Suite.SuiteClasses({
TestDrawingManager.class, TestDrawingManager.class,
TestDrawingManager2.class, TestDrawingManager2.class,
TestFormulaParser.class, //TestFormulaParser.class, //converted to junit4
TestFormulaParserEval.class, TestFormulaParserEval.class,
TestFormulaParserIf.class, TestFormulaParserIf.class,
TestLinkTable.class, TestLinkTable.class,

File diff suppressed because it is too large Load Diff

View File

@ -38,67 +38,67 @@ import org.apache.poi.util.LittleEndianInput;
*/ */
public final class TestSharedFormulaRecord extends TestCase { public final class TestSharedFormulaRecord extends TestCase {
/** /**
* A sample spreadsheet known to have one sheet with 4 shared formula ranges * A sample spreadsheet known to have one sheet with 4 shared formula ranges
*/ */
private static final String SHARED_FORMULA_TEST_XLS = "SharedFormulaTest.xls"; private static final String SHARED_FORMULA_TEST_XLS = "SharedFormulaTest.xls";
/** /**
* Binary data for an encoded formula. Taken from attachment 22062 (bugzilla 45123/45421). * Binary data for an encoded formula. Taken from attachment 22062 (bugzilla 45123/45421).
* The shared formula is in Sheet1!C6:C21, with text "SUMPRODUCT(--(End_Acct=$C6),--(End_Bal))" * The shared formula is in Sheet1!C6:C21, with text "SUMPRODUCT(--(End_Acct=$C6),--(End_Bal))"
* This data is found at offset 0x1A4A (within the shared formula record). * This data is found at offset 0x1A4A (within the shared formula record).
* The critical thing about this formula is that it contains shared formula tokens (tRefN*, * The critical thing about this formula is that it contains shared formula tokens (tRefN*,
* tAreaN*) with operand class 'array'. * tAreaN*) with operand class 'array'.
*/ */
private static final byte[] SHARED_FORMULA_WITH_REF_ARRAYS_DATA = { private static final byte[] SHARED_FORMULA_WITH_REF_ARRAYS_DATA = {
0x1A, 0x00, 0x1A, 0x00,
0x63, 0x02, 0x00, 0x00, 0x00, 0x63, 0x02, 0x00, 0x00, 0x00,
0x6C, 0x00, 0x00, 0x02, (byte)0x80, // tRefNA 0x6C, 0x00, 0x00, 0x02, (byte)0x80, // tRefNA
0x0B, 0x0B,
0x15, 0x15,
0x13, 0x13,
0x13, 0x13,
0x63, 0x03, 0x00, 0x00, 0x00, 0x63, 0x03, 0x00, 0x00, 0x00,
0x15, 0x15,
0x13, 0x13,
0x13, 0x13,
0x42, 0x02, (byte)0xE4, 0x00, 0x42, 0x02, (byte)0xE4, 0x00,
}; };
/** /**
* The method <tt>SharedFormulaRecord.convertSharedFormulas()</tt> converts formulas from * The method <tt>SharedFormulaRecord.convertSharedFormulas()</tt> converts formulas from
* 'shared formula' to 'single cell formula' format. It is important that token operand * 'shared formula' to 'single cell formula' format. It is important that token operand
* classes are preserved during this transformation, because Excel may not tolerate the * classes are preserved during this transformation, because Excel may not tolerate the
* incorrect encoding. The formula here is one such example (Excel displays #VALUE!). * incorrect encoding. The formula here is one such example (Excel displays #VALUE!).
*/ */
public void testConvertSharedFormulasOperandClasses_bug45123() { public void testConvertSharedFormulasOperandClasses_bug45123() {
LittleEndianInput in = TestcaseRecordInputStream.createLittleEndian(SHARED_FORMULA_WITH_REF_ARRAYS_DATA); LittleEndianInput in = TestcaseRecordInputStream.createLittleEndian(SHARED_FORMULA_WITH_REF_ARRAYS_DATA);
int encodedLen = in.readUShort(); int encodedLen = in.readUShort();
Ptg[] sharedFormula = Ptg.readTokens(encodedLen, in); Ptg[] sharedFormula = Ptg.readTokens(encodedLen, in);
SharedFormula sf = new SharedFormula(SpreadsheetVersion.EXCEL97); SharedFormula sf = new SharedFormula(SpreadsheetVersion.EXCEL97);
Ptg[] convertedFormula = sf.convertSharedFormulas(sharedFormula, 100, 200); Ptg[] convertedFormula = sf.convertSharedFormulas(sharedFormula, 100, 200);
RefPtg refPtg = (RefPtg) convertedFormula[1]; RefPtg refPtg = (RefPtg) convertedFormula[1];
assertEquals("$C101", refPtg.toFormulaString()); assertEquals("$C101", refPtg.toFormulaString());
if (refPtg.getPtgClass() == Ptg.CLASS_REF) { if (refPtg.getPtgClass() == Ptg.CLASS_REF) {
throw new AssertionFailedError("Identified bug 45123"); throw new AssertionFailedError("Identified bug 45123");
} }
confirmOperandClasses(sharedFormula, convertedFormula); confirmOperandClasses(sharedFormula, convertedFormula);
} }
private static void confirmOperandClasses(Ptg[] originalPtgs, Ptg[] convertedPtgs) { private static void confirmOperandClasses(Ptg[] originalPtgs, Ptg[] convertedPtgs) {
assertEquals(originalPtgs.length, convertedPtgs.length); assertEquals(originalPtgs.length, convertedPtgs.length);
for (int i = 0; i < convertedPtgs.length; i++) { for (int i = 0; i < convertedPtgs.length; i++) {
Ptg originalPtg = originalPtgs[i]; Ptg originalPtg = originalPtgs[i];
Ptg convertedPtg = convertedPtgs[i]; Ptg convertedPtg = convertedPtgs[i];
if (originalPtg.getPtgClass() != convertedPtg.getPtgClass()) { if (originalPtg.getPtgClass() != convertedPtg.getPtgClass()) {
throw new ComparisonFailure("Different operand class for token[" + i + "]", throw new ComparisonFailure("Different operand class for token[" + i + "]",
String.valueOf(originalPtg.getPtgClass()), String.valueOf(convertedPtg.getPtgClass())); String.valueOf(originalPtg.getPtgClass()), String.valueOf(convertedPtg.getPtgClass()));
} }
} }
} }
public void testConvertSharedFormulas() { public void testConvertSharedFormulas() {
HSSFWorkbook wb = new HSSFWorkbook(); HSSFWorkbook wb = new HSSFWorkbook();
@ -138,111 +138,111 @@ public final class TestSharedFormulaRecord extends TestCase {
} }
/** /**
* Make sure that POI preserves {@link SharedFormulaRecord}s * Make sure that POI preserves {@link SharedFormulaRecord}s
*/ */
public void testPreserveOnReserialize() { public void testPreserveOnReserialize() {
HSSFWorkbook wb; HSSFWorkbook wb;
HSSFSheet sheet; HSSFSheet sheet;
HSSFCell cellB32769; HSSFCell cellB32769;
HSSFCell cellC32769; HSSFCell cellC32769;
// Reading directly from XLS file // Reading directly from XLS file
wb = HSSFTestDataSamples.openSampleWorkbook(SHARED_FORMULA_TEST_XLS); wb = HSSFTestDataSamples.openSampleWorkbook(SHARED_FORMULA_TEST_XLS);
sheet = wb.getSheetAt(0); sheet = wb.getSheetAt(0);
cellB32769 = sheet.getRow(32768).getCell(1); cellB32769 = sheet.getRow(32768).getCell(1);
cellC32769 = sheet.getRow(32768).getCell(2); cellC32769 = sheet.getRow(32768).getCell(2);
// check reading of formulas which are shared (two cells from a 1R x 8C range) // check reading of formulas which are shared (two cells from a 1R x 8C range)
assertEquals("B32770*2", cellB32769.getCellFormula()); assertEquals("B32770*2", cellB32769.getCellFormula());
assertEquals("C32770*2", cellC32769.getCellFormula()); assertEquals("C32770*2", cellC32769.getCellFormula());
confirmCellEvaluation(wb, cellB32769, 4); confirmCellEvaluation(wb, cellB32769, 4);
confirmCellEvaluation(wb, cellC32769, 6); confirmCellEvaluation(wb, cellC32769, 6);
// Confirm this example really does have SharedFormulas. // Confirm this example really does have SharedFormulas.
// there are 3 others besides the one at A32769:H32769 // there are 3 others besides the one at A32769:H32769
assertEquals(4, countSharedFormulas(sheet)); assertEquals(4, countSharedFormulas(sheet));
// Re-serialize and check again // Re-serialize and check again
wb = HSSFTestDataSamples.writeOutAndReadBack(wb); wb = HSSFTestDataSamples.writeOutAndReadBack(wb);
sheet = wb.getSheetAt(0); sheet = wb.getSheetAt(0);
cellB32769 = sheet.getRow(32768).getCell(1); cellB32769 = sheet.getRow(32768).getCell(1);
cellC32769 = sheet.getRow(32768).getCell(2); cellC32769 = sheet.getRow(32768).getCell(2);
assertEquals("B32770*2", cellB32769.getCellFormula()); assertEquals("B32770*2", cellB32769.getCellFormula());
confirmCellEvaluation(wb, cellB32769, 4); confirmCellEvaluation(wb, cellB32769, 4);
assertEquals(4, countSharedFormulas(sheet)); assertEquals(4, countSharedFormulas(sheet));
} }
public void testUnshareFormulaDueToChangeFormula() { public void testUnshareFormulaDueToChangeFormula() {
HSSFWorkbook wb; HSSFWorkbook wb;
HSSFSheet sheet; HSSFSheet sheet;
HSSFCell cellB32769; HSSFCell cellB32769;
HSSFCell cellC32769; HSSFCell cellC32769;
wb = HSSFTestDataSamples.openSampleWorkbook(SHARED_FORMULA_TEST_XLS); wb = HSSFTestDataSamples.openSampleWorkbook(SHARED_FORMULA_TEST_XLS);
sheet = wb.getSheetAt(0); sheet = wb.getSheetAt(0);
cellB32769 = sheet.getRow(32768).getCell(1); cellB32769 = sheet.getRow(32768).getCell(1);
cellC32769 = sheet.getRow(32768).getCell(2); cellC32769 = sheet.getRow(32768).getCell(2);
// Updating cell formula, causing it to become unshared // Updating cell formula, causing it to become unshared
cellB32769.setCellFormula("1+1"); cellB32769.setCellFormula("1+1");
confirmCellEvaluation(wb, cellB32769, 2); confirmCellEvaluation(wb, cellB32769, 2);
// currently (Oct 2008) POI handles this by exploding the whole shared formula group // currently (Oct 2008) POI handles this by exploding the whole shared formula group
assertEquals(3, countSharedFormulas(sheet)); // one less now assertEquals(3, countSharedFormulas(sheet)); // one less now
// check that nearby cell of the same group still has the same formula // check that nearby cell of the same group still has the same formula
assertEquals("C32770*2", cellC32769.getCellFormula()); assertEquals("C32770*2", cellC32769.getCellFormula());
confirmCellEvaluation(wb, cellC32769, 6); confirmCellEvaluation(wb, cellC32769, 6);
} }
public void testUnshareFormulaDueToDelete() { public void testUnshareFormulaDueToDelete() {
HSSFWorkbook wb; HSSFWorkbook wb;
HSSFSheet sheet; HSSFSheet sheet;
HSSFCell cell; HSSFCell cell;
final int ROW_IX = 2; final int ROW_IX = 2;
// changing shared formula cell to blank // changing shared formula cell to blank
wb = HSSFTestDataSamples.openSampleWorkbook(SHARED_FORMULA_TEST_XLS); wb = HSSFTestDataSamples.openSampleWorkbook(SHARED_FORMULA_TEST_XLS);
sheet = wb.getSheetAt(0); sheet = wb.getSheetAt(0);
assertEquals("A$1*2", sheet.getRow(ROW_IX).getCell(1).getCellFormula()); assertEquals("A$1*2", sheet.getRow(ROW_IX).getCell(1).getCellFormula());
cell = sheet.getRow(ROW_IX).getCell(1); cell = sheet.getRow(ROW_IX).getCell(1);
cell.setCellType(HSSFCell.CELL_TYPE_BLANK); cell.setCellType(HSSFCell.CELL_TYPE_BLANK);
assertEquals(3, countSharedFormulas(sheet)); assertEquals(3, countSharedFormulas(sheet));
wb = HSSFTestDataSamples.writeOutAndReadBack(wb); wb = HSSFTestDataSamples.writeOutAndReadBack(wb);
sheet = wb.getSheetAt(0); sheet = wb.getSheetAt(0);
assertEquals("A$1*2", sheet.getRow(ROW_IX+1).getCell(1).getCellFormula()); assertEquals("A$1*2", sheet.getRow(ROW_IX+1).getCell(1).getCellFormula());
// deleting shared formula cell // deleting shared formula cell
wb = HSSFTestDataSamples.openSampleWorkbook(SHARED_FORMULA_TEST_XLS); wb = HSSFTestDataSamples.openSampleWorkbook(SHARED_FORMULA_TEST_XLS);
sheet = wb.getSheetAt(0); sheet = wb.getSheetAt(0);
assertEquals("A$1*2", sheet.getRow(ROW_IX).getCell(1).getCellFormula()); assertEquals("A$1*2", sheet.getRow(ROW_IX).getCell(1).getCellFormula());
cell = sheet.getRow(ROW_IX).getCell(1); cell = sheet.getRow(ROW_IX).getCell(1);
sheet.getRow(ROW_IX).removeCell(cell); sheet.getRow(ROW_IX).removeCell(cell);
assertEquals(3, countSharedFormulas(sheet)); assertEquals(3, countSharedFormulas(sheet));
wb = HSSFTestDataSamples.writeOutAndReadBack(wb); wb = HSSFTestDataSamples.writeOutAndReadBack(wb);
sheet = wb.getSheetAt(0); sheet = wb.getSheetAt(0);
assertEquals("A$1*2", sheet.getRow(ROW_IX+1).getCell(1).getCellFormula()); assertEquals("A$1*2", sheet.getRow(ROW_IX+1).getCell(1).getCellFormula());
} }
private static void confirmCellEvaluation(HSSFWorkbook wb, HSSFCell cell, double expectedValue) { private static void confirmCellEvaluation(HSSFWorkbook wb, HSSFCell cell, double expectedValue) {
HSSFFormulaEvaluator fe = new HSSFFormulaEvaluator(wb); HSSFFormulaEvaluator fe = new HSSFFormulaEvaluator(wb);
CellValue cv = fe.evaluate(cell); CellValue cv = fe.evaluate(cell);
assertEquals(HSSFCell.CELL_TYPE_NUMERIC, cv.getCellType()); assertEquals(HSSFCell.CELL_TYPE_NUMERIC, cv.getCellType());
assertEquals(expectedValue, cv.getNumberValue(), 0.0); assertEquals(expectedValue, cv.getNumberValue(), 0.0);
} }
/** /**
* @return the number of {@link SharedFormulaRecord}s encoded for the specified sheet * @return the number of {@link SharedFormulaRecord}s encoded for the specified sheet
*/ */
private static int countSharedFormulas(HSSFSheet sheet) { private static int countSharedFormulas(HSSFSheet sheet) {
Record[] records = RecordInspector.getRecords(sheet, 0); Record[] records = RecordInspector.getRecords(sheet, 0);
int count = 0; int count = 0;
for (int i = 0; i < records.length; i++) { for (int i = 0; i < records.length; i++) {
Record rec = records[i]; Record rec = records[i];
if(rec instanceof SharedFormulaRecord) { if(rec instanceof SharedFormulaRecord) {
count++; count++;
} }
} }
return count; return count;
} }
} }

View File

@ -36,32 +36,32 @@ import org.junit.Test;
* Tests for the INDIRECT() function.</p> * Tests for the INDIRECT() function.</p>
*/ */
public final class TestIndirect { public final class TestIndirect {
// convenient access to namespace // convenient access to namespace
// private static final ErrorEval EE = null; // private static final ErrorEval EE = null;
private static void createDataRow(HSSFSheet sheet, int rowIndex, double... vals) { private static void createDataRow(HSSFSheet sheet, int rowIndex, double... vals) {
HSSFRow row = sheet.createRow(rowIndex); HSSFRow row = sheet.createRow(rowIndex);
for (int i = 0; i < vals.length; i++) { for (int i = 0; i < vals.length; i++) {
row.createCell(i).setCellValue(vals[i]); row.createCell(i).setCellValue(vals[i]);
} }
} }
private static HSSFWorkbook createWBA() { private static HSSFWorkbook createWBA() {
HSSFWorkbook wb = new HSSFWorkbook(); HSSFWorkbook wb = new HSSFWorkbook();
HSSFSheet sheet1 = wb.createSheet("Sheet1"); HSSFSheet sheet1 = wb.createSheet("Sheet1");
HSSFSheet sheet2 = wb.createSheet("Sheet2"); HSSFSheet sheet2 = wb.createSheet("Sheet2");
HSSFSheet sheet3 = wb.createSheet("John's sales"); HSSFSheet sheet3 = wb.createSheet("John's sales");
createDataRow(sheet1, 0, 11, 12, 13, 14); createDataRow(sheet1, 0, 11, 12, 13, 14);
createDataRow(sheet1, 1, 21, 22, 23, 24); createDataRow(sheet1, 1, 21, 22, 23, 24);
createDataRow(sheet1, 2, 31, 32, 33, 34); createDataRow(sheet1, 2, 31, 32, 33, 34);
createDataRow(sheet2, 0, 50, 55, 60, 65); createDataRow(sheet2, 0, 50, 55, 60, 65);
createDataRow(sheet2, 1, 51, 56, 61, 66); createDataRow(sheet2, 1, 51, 56, 61, 66);
createDataRow(sheet2, 2, 52, 57, 62, 67); createDataRow(sheet2, 2, 52, 57, 62, 67);
createDataRow(sheet3, 0, 30, 31, 32); createDataRow(sheet3, 0, 30, 31, 32);
createDataRow(sheet3, 1, 33, 34, 35); createDataRow(sheet3, 1, 33, 34, 35);
HSSFName name1 = wb.createName(); HSSFName name1 = wb.createName();
name1.setNameName("sales1"); name1.setNameName("sales1");
@ -75,131 +75,131 @@ public final class TestIndirect {
row.createCell(0).setCellValue("sales1"); //A4 row.createCell(0).setCellValue("sales1"); //A4
row.createCell(1).setCellValue("sales2"); //B4 row.createCell(1).setCellValue("sales2"); //B4
return wb; return wb;
} }
private static HSSFWorkbook createWBB() { private static HSSFWorkbook createWBB() {
HSSFWorkbook wb = new HSSFWorkbook(); HSSFWorkbook wb = new HSSFWorkbook();
HSSFSheet sheet1 = wb.createSheet("Sheet1"); HSSFSheet sheet1 = wb.createSheet("Sheet1");
HSSFSheet sheet2 = wb.createSheet("Sheet2"); HSSFSheet sheet2 = wb.createSheet("Sheet2");
HSSFSheet sheet3 = wb.createSheet("## Look here!"); HSSFSheet sheet3 = wb.createSheet("## Look here!");
createDataRow(sheet1, 0, 400, 440, 480, 520); createDataRow(sheet1, 0, 400, 440, 480, 520);
createDataRow(sheet1, 1, 420, 460, 500, 540); createDataRow(sheet1, 1, 420, 460, 500, 540);
createDataRow(sheet2, 0, 50, 55, 60, 65); createDataRow(sheet2, 0, 50, 55, 60, 65);
createDataRow(sheet2, 1, 51, 56, 61, 66); createDataRow(sheet2, 1, 51, 56, 61, 66);
createDataRow(sheet3, 0, 42); createDataRow(sheet3, 0, 42);
return wb; return wb;
} }
@Test @Test
public void testBasic() throws Exception { public void testBasic() throws Exception {
HSSFWorkbook wbA = createWBA(); HSSFWorkbook wbA = createWBA();
HSSFCell c = wbA.getSheetAt(0).createRow(5).createCell(2); HSSFCell c = wbA.getSheetAt(0).createRow(5).createCell(2);
HSSFFormulaEvaluator feA = new HSSFFormulaEvaluator(wbA); HSSFFormulaEvaluator feA = new HSSFFormulaEvaluator(wbA);
// non-error cases // non-error cases
confirm(feA, c, "INDIRECT(\"C2\")", 23); confirm(feA, c, "INDIRECT(\"C2\")", 23);
confirm(feA, c, "INDIRECT(\"$C2\")", 23); confirm(feA, c, "INDIRECT(\"$C2\")", 23);
confirm(feA, c, "INDIRECT(\"C$2\")", 23); confirm(feA, c, "INDIRECT(\"C$2\")", 23);
confirm(feA, c, "SUM(INDIRECT(\"Sheet2!B1:C3\"))", 351); // area ref confirm(feA, c, "SUM(INDIRECT(\"Sheet2!B1:C3\"))", 351); // area ref
confirm(feA, c, "SUM(INDIRECT(\"Sheet2! B1 : C3 \"))", 351); // spaces in area ref confirm(feA, c, "SUM(INDIRECT(\"Sheet2! B1 : C3 \"))", 351); // spaces in area ref
confirm(feA, c, "SUM(INDIRECT(\"'John''s sales'!A1:C1\"))", 93); // special chars in sheet name confirm(feA, c, "SUM(INDIRECT(\"'John''s sales'!A1:C1\"))", 93); // special chars in sheet name
confirm(feA, c, "INDIRECT(\"'Sheet1'!B3\")", 32); // redundant sheet name quotes confirm(feA, c, "INDIRECT(\"'Sheet1'!B3\")", 32); // redundant sheet name quotes
confirm(feA, c, "INDIRECT(\"sHeet1!B3\")", 32); // case-insensitive sheet name confirm(feA, c, "INDIRECT(\"sHeet1!B3\")", 32); // case-insensitive sheet name
confirm(feA, c, "INDIRECT(\" D3 \")", 34); // spaces around cell ref confirm(feA, c, "INDIRECT(\" D3 \")", 34); // spaces around cell ref
confirm(feA, c, "INDIRECT(\"Sheet1! D3 \")", 34); // spaces around cell ref confirm(feA, c, "INDIRECT(\"Sheet1! D3 \")", 34); // spaces around cell ref
confirm(feA, c, "INDIRECT(\"A1\", TRUE)", 11); // explicit arg1. only TRUE supported so far confirm(feA, c, "INDIRECT(\"A1\", TRUE)", 11); // explicit arg1. only TRUE supported so far
confirm(feA, c, "INDIRECT(\"A1:G1\")", 13); // de-reference area ref (note formula is in C4) confirm(feA, c, "INDIRECT(\"A1:G1\")", 13); // de-reference area ref (note formula is in C4)
confirm(feA, c, "SUM(INDIRECT(A4))", 50); // indirect defined name confirm(feA, c, "SUM(INDIRECT(A4))", 50); // indirect defined name
confirm(feA, c, "SUM(INDIRECT(B4))", 351); // indirect defined name pointinh to other sheet confirm(feA, c, "SUM(INDIRECT(B4))", 351); // indirect defined name pointinh to other sheet
// simple error propagation: // simple error propagation:
// arg0 is evaluated to text first // arg0 is evaluated to text first
confirm(feA, c, "INDIRECT(#DIV/0!)", ErrorEval.DIV_ZERO); confirm(feA, c, "INDIRECT(#DIV/0!)", ErrorEval.DIV_ZERO);
confirm(feA, c, "INDIRECT(#DIV/0!)", ErrorEval.DIV_ZERO); confirm(feA, c, "INDIRECT(#DIV/0!)", ErrorEval.DIV_ZERO);
confirm(feA, c, "INDIRECT(#NAME?, \"x\")", ErrorEval.NAME_INVALID); confirm(feA, c, "INDIRECT(#NAME?, \"x\")", ErrorEval.NAME_INVALID);
confirm(feA, c, "INDIRECT(#NUM!, #N/A)", ErrorEval.NUM_ERROR); confirm(feA, c, "INDIRECT(#NUM!, #N/A)", ErrorEval.NUM_ERROR);
// arg1 is evaluated to boolean before arg0 is decoded // arg1 is evaluated to boolean before arg0 is decoded
confirm(feA, c, "INDIRECT(\"garbage\", #N/A)", ErrorEval.NA); confirm(feA, c, "INDIRECT(\"garbage\", #N/A)", ErrorEval.NA);
confirm(feA, c, "INDIRECT(\"garbage\", \"\")", ErrorEval.VALUE_INVALID); // empty string is not valid boolean confirm(feA, c, "INDIRECT(\"garbage\", \"\")", ErrorEval.VALUE_INVALID); // empty string is not valid boolean
confirm(feA, c, "INDIRECT(\"garbage\", \"flase\")", ErrorEval.VALUE_INVALID); // must be "TRUE" or "FALSE" confirm(feA, c, "INDIRECT(\"garbage\", \"flase\")", ErrorEval.VALUE_INVALID); // must be "TRUE" or "FALSE"
// spaces around sheet name (with or without quotes makes no difference) // spaces around sheet name (with or without quotes makes no difference)
confirm(feA, c, "INDIRECT(\"'Sheet1 '!D3\")", ErrorEval.REF_INVALID); confirm(feA, c, "INDIRECT(\"'Sheet1 '!D3\")", ErrorEval.REF_INVALID);
confirm(feA, c, "INDIRECT(\" Sheet1!D3\")", ErrorEval.REF_INVALID); confirm(feA, c, "INDIRECT(\" Sheet1!D3\")", ErrorEval.REF_INVALID);
confirm(feA, c, "INDIRECT(\"'Sheet1' !D3\")", ErrorEval.REF_INVALID); confirm(feA, c, "INDIRECT(\"'Sheet1' !D3\")", ErrorEval.REF_INVALID);
confirm(feA, c, "SUM(INDIRECT(\"'John's sales'!A1:C1\"))", ErrorEval.REF_INVALID); // bad quote escaping confirm(feA, c, "SUM(INDIRECT(\"'John's sales'!A1:C1\"))", ErrorEval.REF_INVALID); // bad quote escaping
confirm(feA, c, "INDIRECT(\"[Book1]Sheet1!A1\")", ErrorEval.REF_INVALID); // unknown external workbook confirm(feA, c, "INDIRECT(\"[Book1]Sheet1!A1\")", ErrorEval.REF_INVALID); // unknown external workbook
confirm(feA, c, "INDIRECT(\"Sheet3!A1\")", ErrorEval.REF_INVALID); // unknown sheet confirm(feA, c, "INDIRECT(\"Sheet3!A1\")", ErrorEval.REF_INVALID); // unknown sheet
// if (false) { // TODO - support evaluation of defined names // if (false) { // TODO - support evaluation of defined names
// confirm(feA, c, "INDIRECT(\"Sheet1!IW1\")", ErrorEval.REF_INVALID); // bad column // confirm(feA, c, "INDIRECT(\"Sheet1!IW1\")", ErrorEval.REF_INVALID); // bad column
// confirm(feA, c, "INDIRECT(\"Sheet1!A65537\")", ErrorEval.REF_INVALID); // bad row // confirm(feA, c, "INDIRECT(\"Sheet1!A65537\")", ErrorEval.REF_INVALID); // bad row
// } // }
confirm(feA, c, "INDIRECT(\"Sheet1!A 1\")", ErrorEval.REF_INVALID); // space in cell ref confirm(feA, c, "INDIRECT(\"Sheet1!A 1\")", ErrorEval.REF_INVALID); // space in cell ref
wbA.close(); wbA.close();
} }
@Test @Test
public void testMultipleWorkbooks() throws Exception { public void testMultipleWorkbooks() throws Exception {
HSSFWorkbook wbA = createWBA(); HSSFWorkbook wbA = createWBA();
HSSFCell cellA = wbA.getSheetAt(0).createRow(10).createCell(0); HSSFCell cellA = wbA.getSheetAt(0).createRow(10).createCell(0);
HSSFFormulaEvaluator feA = new HSSFFormulaEvaluator(wbA); HSSFFormulaEvaluator feA = new HSSFFormulaEvaluator(wbA);
HSSFWorkbook wbB = createWBB(); HSSFWorkbook wbB = createWBB();
HSSFCell cellB = wbB.getSheetAt(0).createRow(10).createCell(0); HSSFCell cellB = wbB.getSheetAt(0).createRow(10).createCell(0);
HSSFFormulaEvaluator feB = new HSSFFormulaEvaluator(wbB); HSSFFormulaEvaluator feB = new HSSFFormulaEvaluator(wbB);
String[] workbookNames = { "MyBook", "Figures for January", }; String[] workbookNames = { "MyBook", "Figures for January", };
HSSFFormulaEvaluator[] evaluators = { feA, feB, }; HSSFFormulaEvaluator[] evaluators = { feA, feB, };
HSSFFormulaEvaluator.setupEnvironment(workbookNames, evaluators); HSSFFormulaEvaluator.setupEnvironment(workbookNames, evaluators);
confirm(feB, cellB, "INDIRECT(\"'[Figures for January]## Look here!'!A1\")", 42); // same wb confirm(feB, cellB, "INDIRECT(\"'[Figures for January]## Look here!'!A1\")", 42); // same wb
confirm(feA, cellA, "INDIRECT(\"'[Figures for January]## Look here!'!A1\")", 42); // across workbooks confirm(feA, cellA, "INDIRECT(\"'[Figures for January]## Look here!'!A1\")", 42); // across workbooks
// 2 level recursion // 2 level recursion
confirm(feB, cellB, "INDIRECT(\"[MyBook]Sheet2!A1\")", 50); // set up (and check) first level confirm(feB, cellB, "INDIRECT(\"[MyBook]Sheet2!A1\")", 50); // set up (and check) first level
confirm(feA, cellA, "INDIRECT(\"'[Figures for January]Sheet1'!A11\")", 50); // points to cellB confirm(feA, cellA, "INDIRECT(\"'[Figures for January]Sheet1'!A11\")", 50); // points to cellB
wbB.close(); wbB.close();
wbA.close(); wbA.close();
} }
private static void confirm(FormulaEvaluator fe, Cell cell, String formula, private static void confirm(FormulaEvaluator fe, Cell cell, String formula,
double expectedResult) { double expectedResult) {
fe.clearAllCachedResultValues(); fe.clearAllCachedResultValues();
cell.setCellFormula(formula); cell.setCellFormula(formula);
CellValue cv = fe.evaluate(cell); CellValue cv = fe.evaluate(cell);
if (cv.getCellType() != Cell.CELL_TYPE_NUMERIC) { if (cv.getCellType() != Cell.CELL_TYPE_NUMERIC) {
fail("expected numeric cell type but got " + cv.formatAsString()); fail("expected numeric cell type but got " + cv.formatAsString());
} }
assertEquals(expectedResult, cv.getNumberValue(), 0.0); assertEquals(expectedResult, cv.getNumberValue(), 0.0);
} }
private static void confirm(FormulaEvaluator fe, Cell cell, String formula, private static void confirm(FormulaEvaluator fe, Cell cell, String formula,
ErrorEval expectedResult) { ErrorEval expectedResult) {
fe.clearAllCachedResultValues(); fe.clearAllCachedResultValues();
cell.setCellFormula(formula); cell.setCellFormula(formula);
CellValue cv = fe.evaluate(cell); CellValue cv = fe.evaluate(cell);
if (cv.getCellType() != Cell.CELL_TYPE_ERROR) { if (cv.getCellType() != Cell.CELL_TYPE_ERROR) {
fail("expected error cell type but got " + cv.formatAsString()); fail("expected error cell type but got " + cv.formatAsString());
} }
int expCode = expectedResult.getErrorCode(); int expCode = expectedResult.getErrorCode();
if (cv.getErrorValue() != expCode) { if (cv.getErrorValue() != expCode) {
fail("Expected error '" + ErrorEval.getText(expCode) fail("Expected error '" + ErrorEval.getText(expCode)
+ "' but got '" + cv.formatAsString() + "'."); + "' but got '" + cv.formatAsString() + "'.");
} }
} }
} }