diff --git a/src/java/org/apache/poi/hssf/model/HSSFFormulaParser.java b/src/java/org/apache/poi/hssf/model/HSSFFormulaParser.java index 1f295d0f3..baf2b97d0 100644 --- a/src/java/org/apache/poi/hssf/model/HSSFFormulaParser.java +++ b/src/java/org/apache/poi/hssf/model/HSSFFormulaParser.java @@ -31,53 +31,53 @@ import org.apache.poi.ss.formula.FormulaType; */ public final class HSSFFormulaParser { - private static FormulaParsingWorkbook createParsingWorkbook(HSSFWorkbook book) { - return HSSFEvaluationWorkbook.create(book); - } + private static FormulaParsingWorkbook createParsingWorkbook(HSSFWorkbook book) { + return HSSFEvaluationWorkbook.create(book); + } - private HSSFFormulaParser() { - // no instances of this class - } + private HSSFFormulaParser() { + // no instances of this class + } - /** - * Convenience method for parsing cell formulas. see {@link #parse(String, HSSFWorkbook, int, int)} - */ - public static Ptg[] parse(String formula, HSSFWorkbook workbook) throws FormulaParseException { - return parse(formula, workbook, FormulaType.CELL); - } + /** + * Convenience method for parsing cell formulas. see {@link #parse(String, HSSFWorkbook, int, int)} + */ + public static Ptg[] parse(String formula, HSSFWorkbook workbook) throws FormulaParseException { + return parse(formula, workbook, FormulaType.CELL); + } - /** - * @param formulaType a constant from {@link FormulaType} - * @return the parsed formula tokens + /** + * @param formulaType a constant from {@link FormulaType} + * @return the parsed formula tokens * @throws FormulaParseException if the formula has incorrect syntax or is otherwise invalid - */ - public static Ptg[] parse(String formula, HSSFWorkbook workbook, int formulaType) throws FormulaParseException { - return parse(formula, workbook, formulaType, -1); - } + */ + public static Ptg[] parse(String formula, HSSFWorkbook workbook, int formulaType) throws FormulaParseException { + return parse(formula, workbook, formulaType, -1); + } - /** - * @param formula the formula to parse - * @param workbook the parent workbook - * @param formulaType a constant from {@link FormulaType} - * @param sheetIndex the 0-based index of the sheet this formula belongs to. - * The sheet index is required to resolve sheet-level names. -1 means that - * the scope of the name will be ignored and the parser will match named ranges only by name - * - * @return the parsed formula tokens + /** + * @param formula the formula to parse + * @param workbook the parent workbook + * @param formulaType a constant from {@link FormulaType} + * @param sheetIndex the 0-based index of the sheet this formula belongs to. + * The sheet index is required to resolve sheet-level names. -1 means that + * the scope of the name will be ignored and the parser will match named ranges only by name + * + * @return the parsed formula tokens * @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 { - return FormulaParser.parse(formula, createParsingWorkbook(workbook), formulaType, sheetIndex); - } + */ + public static Ptg[] parse(String formula, HSSFWorkbook workbook, int formulaType, int sheetIndex) throws FormulaParseException { + return FormulaParser.parse(formula, createParsingWorkbook(workbook), formulaType, sheetIndex); + } - /** - * Static method to convert an array of {@link Ptg}s in RPN order - * to a human readable string format in infix mode. - * @param book used for defined names and 3D references - * @param ptgs must not be null - * @return a human readable String - */ - public static String toFormulaString(HSSFWorkbook book, Ptg[] ptgs) { - return FormulaRenderer.toFormulaString(HSSFEvaluationWorkbook.create(book), ptgs); - } + /** + * Static method to convert an array of {@link Ptg}s in RPN order + * to a human readable string format in infix mode. + * @param book used for defined names and 3D references + * @param ptgs must not be null + * @return a human readable String + */ + public static String toFormulaString(HSSFWorkbook book, Ptg[] ptgs) { + return FormulaRenderer.toFormulaString(HSSFEvaluationWorkbook.create(book), ptgs); + } } diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFEvaluationWorkbook.java b/src/java/org/apache/poi/hssf/usermodel/HSSFEvaluationWorkbook.java index dbf85b869..81eef9005 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFEvaluationWorkbook.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFEvaluationWorkbook.java @@ -6,7 +6,7 @@ (the "License"); you may not use this file except in compliance with 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 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.Ref3DPtg; 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.CellReference; import org.apache.poi.util.POILogFactory; @@ -264,7 +265,16 @@ public final class HSSFEvaluationWorkbook implements FormulaRenderingWorkbook, E return extIx; } + @Override public SpreadsheetVersion getSpreadsheetVersion(){ 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"); + } } diff --git a/src/java/org/apache/poi/ss/formula/FormulaParser.java b/src/java/org/apache/poi/ss/formula/FormulaParser.java index 3820043d1..3ddcd7463 100644 --- a/src/java/org/apache/poi/ss/formula/FormulaParser.java +++ b/src/java/org/apache/poi/ss/formula/FormulaParser.java @@ -6,7 +6,7 @@ (the "License"); you may not use this file except in compliance with 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 distributed under the License is distributed on an "AS IS" BASIS, @@ -28,6 +28,7 @@ import org.apache.poi.ss.formula.function.FunctionMetadata; import org.apache.poi.ss.formula.function.FunctionMetadataRegistry; import org.apache.poi.ss.formula.ptg.AbstractFunctionPtg; import org.apache.poi.ss.formula.ptg.AddPtg; +import org.apache.poi.ss.formula.ptg.Area3DPxg; import org.apache.poi.ss.formula.ptg.AreaPtg; import org.apache.poi.ss.formula.ptg.ArrayPtg; import org.apache.poi.ss.formula.ptg.AttrPtg; @@ -69,6 +70,7 @@ import org.apache.poi.ss.formula.ptg.UnionPtg; import org.apache.poi.ss.formula.ptg.ValueOperatorPtg; import org.apache.poi.ss.usermodel.FormulaError; 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.CellReference; import org.apache.poi.ss.util.CellReference.NameType; @@ -78,7 +80,7 @@ import org.apache.poi.util.POILogger; /** * This class parses a formula string into a List of tokens in RPN order. * Inspired by - * Lets Build a Compiler, by Jack Crenshaw + * Lets Build a Compiler, by Jack Crenshaw * BNF for the formula expression is : * ::= [ ]* * ::= [ ]* @@ -89,339 +91,383 @@ import org.apache.poi.util.POILogger; *

*/ public final class FormulaParser { - private final static POILogger log = POILogFactory.getLogger(FormulaParser.class); - private final String _formulaString; - private final int _formulaLength; - /** points at the next character to be read (after the {@link #look} char) */ - private int _pointer; + private final static POILogger log = POILogFactory.getLogger(FormulaParser.class); + private final String _formulaString; + private final int _formulaLength; + /** points at the next character to be read (after the {@link #look} char) */ + private int _pointer; - private ParseNode _rootNode; + private ParseNode _rootNode; - private final static char TAB = '\t'; // HSSF + XSSF - private final static char CR = '\r'; // Normally just XSSF - private final static char LF = '\n'; // Normally just XSSF + private final static char TAB = '\t'; // HSSF + XSSF + private final static char CR = '\r'; // Normally just XSSF + private final static char LF = '\n'; // Normally just XSSF - /** - * Lookahead Character. - * gets value '\0' when the input string is exhausted - */ - private char look; + /** + * Lookahead Character. + * gets value '\0' when the input string is exhausted + */ + private char look; /** * Tracks whether the run of whitespace preceeding "look" could be an * intersection operator. See GetChar. */ - private boolean _inIntersection = false; + private boolean _inIntersection = false; - private final FormulaParsingWorkbook _book; - private final SpreadsheetVersion _ssVersion; + private final FormulaParsingWorkbook _book; + private final SpreadsheetVersion _ssVersion; - private final int _sheetIndex; + private final int _sheetIndex; + private final int _rowIndex; // 0-based - /** - * Create the formula parser, with the string that is to be - * parsed against the supplied workbook. - * A later call the parse() method to return ptg list in - * rpn order, then call the getRPNPtg() to retrieve the - * parse results. - * This class is recommended only for single threaded use. - * - * If you only have a usermodel.HSSFWorkbook, and not a - * model.Workbook, then use the convenience method on - * usermodel.HSSFFormulaEvaluator - */ - private FormulaParser(String formula, FormulaParsingWorkbook book, int sheetIndex){ - _formulaString = formula; - _pointer=0; - _book = book; - _ssVersion = book == null ? SpreadsheetVersion.EXCEL97 : book.getSpreadsheetVersion(); - _formulaLength = _formulaString.length(); - _sheetIndex = sheetIndex; - } + /** + * Create the formula parser, with the string that is to be + * parsed against the supplied workbook. + * A later call the parse() method to return ptg list in + * rpn order, then call the getRPNPtg() to retrieve the + * parse results. + * This class is recommended only for single threaded use. + * + * If you only have a usermodel.HSSFWorkbook, and not a + * model.Workbook, then use the convenience method on + * usermodel.HSSFFormulaEvaluator + */ + private FormulaParser(String formula, FormulaParsingWorkbook book, int sheetIndex, int rowIndex){ + _formulaString = formula; + _pointer=0; + _book = book; + _ssVersion = book == null ? SpreadsheetVersion.EXCEL97 : book.getSpreadsheetVersion(); + _formulaLength = _formulaString.length(); + _sheetIndex = sheetIndex; + _rowIndex = rowIndex; + } - /** - * Parse a formula into a array of tokens - * Side effect: creates name (Workbook.createName) if formula contains unrecognized names (names are likely UDFs) - * - * @param formula the formula to parse - * @param workbook the parent workbook - * @param formulaType the type of the formula, see {@link FormulaType} - * @param sheetIndex the 0-based index of the sheet this formula belongs to. - * The sheet index is required to resolve sheet-level names. -1 means that - * the scope of the name will be ignored and the parser will match names only by name - * - * @return array of parsed tokens - * @throws FormulaParseException if the formula has incorrect syntax or is otherwise invalid - */ - public static Ptg[] parse(String formula, FormulaParsingWorkbook workbook, int formulaType, int sheetIndex) { - FormulaParser fp = new FormulaParser(formula, workbook, sheetIndex); - fp.parse(); - return fp.getRPNPtg(formulaType); - } - - /** Read New Character From Input Stream */ - private void GetChar() { - // The intersection operator is a space. We track whether the run of - // whitespace preceeding "look" counts as an intersection operator. - if (IsWhite(look)) { - if (look == ' ') { - _inIntersection = true; - } - } - else { - _inIntersection = false; - } - - // Check to see if we've walked off the end of the string. - if (_pointer > _formulaLength) { - throw new RuntimeException("too far"); - } - if (_pointer < _formulaLength) { - look=_formulaString.charAt(_pointer); - } else { - // Just return if so and reset 'look' to something to keep - // SkipWhitespace from spinning - look = (char)0; - _inIntersection = false; - } - _pointer++; - //System.out.println("Got char: "+ look); - } - private void resetPointer(int ptr) { - _pointer = ptr; - if (_pointer <= _formulaLength) { - look=_formulaString.charAt(_pointer-1); - } else { - // Just return if so and reset 'look' to something to keep - // SkipWhitespace from spinning - look = (char)0; - } - } + /** + * Parse a formula into an array of tokens + * Side effect: creates name ({@link org.apache.poi.ss.usermodel.Workbook#createName}) + * if formula contains unrecognized names (names are likely UDFs) + * + * @param formula the formula to parse + * @param workbook the parent workbook + * @param formulaType the type of the formula, see {@link FormulaType} + * @param sheetIndex the 0-based index of the sheet this formula belongs to. + * The sheet index is required to resolve sheet-level names. -1 means that + * the scope of the name will be ignored and the parser will match names only by name + * @param rowIndex - the related cell's row index in 0-based form (-1 if the formula is not cell related) + * used to handle structured references that have the "#This Row" quantifier. + * Use rowIndex=-1 or {@link #parseStructuredReference(String, FormulaParsingWorkbook, int, int) if formula + * does not contain structured references. + * + * @return array of parsed tokens + * @throws FormulaParseException if the formula has incorrect syntax or is otherwise invalid + */ + public static Ptg[] parse(String formula, FormulaParsingWorkbook workbook, int formulaType, int sheetIndex, int rowIndex) { + FormulaParser fp = new FormulaParser(formula, workbook, sheetIndex, rowIndex); + fp.parse(); + return fp.getRPNPtg(formulaType); + } - /** Report What Was Expected */ - private RuntimeException expected(String s) { - String msg; + /** + * Parse a formula into an array of tokens + * Side effect: creates name ({@link org.apache.poi.ss.usermodel.Workbook#createName}) + * if formula contains unrecognized names (names are likely UDFs) + * + * @param formula the formula to parse + * @param workbook the parent workbook + * @param formulaType the type of the formula, see {@link FormulaType} + * @param sheetIndex the 0-based index of the sheet this formula belongs to. + * The sheet index is required to resolve sheet-level names. -1 means that + * the scope of the name will be ignored and the parser will match names only by name + * + * @return array of parsed tokens + * @throws FormulaParseException if the formula has incorrect syntax or is otherwise invalid + */ + public static Ptg[] parse(String formula, FormulaParsingWorkbook workbook, int formulaType, int sheetIndex) { + return parse(formula, workbook, formulaType, sheetIndex, -1); + } - if (look == '=' && _formulaString.substring(0, _pointer-1).trim().length() < 1) { - msg = "The specified formula '" + _formulaString - + "' starts with an equals sign which is not allowed."; - } else { - msg = "Parse error near char " + (_pointer-1) + " '" + look + "'" - + " in specified formula '" + _formulaString + "'. Expected " - + s; - } - return new FormulaParseException(msg); - } + /** + * Parse a structured reference. Converts the structured + * reference to the area that represent it. + * + * @param tableText - The structured reference text + * @param workbook - the parent workbook + * @param rowIndex - the 0-based cell's row index ( used to handle "#This Row" quantifiers ) + * @return the area that being represented by the structured reference. + */ + public static Area3DPxg parseStructuredReference(String tableText, FormulaParsingWorkbook workbook, int rowIndex) { + final int sheetIndex = -1; //don't care? + Ptg[] arr = FormulaParser.parse(tableText, workbook, FormulaType.CELL, sheetIndex, rowIndex); + if (arr.length != 1 || !(arr[0] instanceof Area3DPxg) ) { + throw new IllegalStateException("Illegal structured reference"); + } + return (Area3DPxg) arr[0]; + } + + /** Read New Character From Input Stream */ + private void GetChar() { + // The intersection operator is a space. We track whether the run of + // whitespace preceeding "look" counts as an intersection operator. + if (IsWhite(look)) { + if (look == ' ') { + _inIntersection = true; + } + } + else { + _inIntersection = false; + } + + // Check to see if we've walked off the end of the string. + if (_pointer > _formulaLength) { + throw new RuntimeException("too far"); + } + if (_pointer < _formulaLength) { + look=_formulaString.charAt(_pointer); + } else { + // Just return if so and reset 'look' to something to keep + // SkipWhitespace from spinning + look = (char)0; + _inIntersection = false; + } + _pointer++; + //System.out.println("Got char: "+ look); + } + private void resetPointer(int ptr) { + _pointer = ptr; + if (_pointer <= _formulaLength) { + look=_formulaString.charAt(_pointer-1); + } else { + // Just return if so and reset 'look' to something to keep + // SkipWhitespace from spinning + look = (char)0; + } + } - /** Recognize an Alpha Character */ - private static boolean IsAlpha(char c) { - return Character.isLetter(c) || c == '$' || c=='_'; - } + /** Report What Was Expected */ + private RuntimeException expected(String s) { + String msg; - /** Recognize a Decimal Digit */ - private static boolean IsDigit(char c) { - return Character.isDigit(c); - } + if (look == '=' && _formulaString.substring(0, _pointer-1).trim().length() < 1) { + msg = "The specified formula '" + _formulaString + + "' starts with an equals sign which is not allowed."; + } else { + msg = "Parse error near char " + (_pointer-1) + " '" + look + "'" + + " in specified formula '" + _formulaString + "'. Expected " + + s; + } + return new FormulaParseException(msg); + } - /** Recognize White Space */ - private static boolean IsWhite( char c) { - return c ==' ' || c== TAB || c == CR || c == LF; - } + /** Recognize an Alpha Character */ + private static boolean IsAlpha(char c) { + return Character.isLetter(c) || c == '$' || c=='_'; + } - /** Skip Over Leading White Space */ - private void SkipWhite() { - while (IsWhite(look)) { - GetChar(); - } - } + /** Recognize a Decimal Digit */ + private static boolean IsDigit(char c) { + return Character.isDigit(c); + } - /** - * Consumes the next input character if it is equal to the one specified otherwise throws an - * unchecked exception. This method does not consume whitespace (before or after the - * matched character). - */ - private void Match(char x) { - if (look != x) { - throw expected("'" + x + "'"); - } - GetChar(); - } + /** Recognize White Space */ + private static boolean IsWhite( char c) { + return c ==' ' || c== TAB || c == CR || c == LF; + } - /** Get a Number */ - private String GetNum() { - StringBuffer value = new StringBuffer(); + /** Skip Over Leading White Space */ + private void SkipWhite() { + while (IsWhite(look)) { + GetChar(); + } + } - while (IsDigit(this.look)){ - value.append(this.look); - GetChar(); - } - return value.length() == 0 ? null : value.toString(); - } + /** + * Consumes the next input character if it is equal to the one specified otherwise throws an + * unchecked exception. This method does not consume whitespace (before or after the + * matched character). + */ + private void Match(char x) { + if (look != x) { + throw expected("'" + x + "'"); + } + GetChar(); + } - private ParseNode parseRangeExpression() { - ParseNode result = parseRangeable(); - boolean hasRange = false; - while (look == ':') { - int pos = _pointer; - GetChar(); - ParseNode nextPart = parseRangeable(); - // Note - no range simplification here. An expr like "A1:B2:C3:D4:E5" should be - // grouped into area ref pairs like: "(A1:B2):(C3:D4):E5" - // Furthermore, Excel doesn't seem to simplify - // expressions like "Sheet1!A1:Sheet1:B2" into "Sheet1!A1:B2" + /** Get a Number */ + private String GetNum() { + StringBuffer value = new StringBuffer(); - checkValidRangeOperand("LHS", pos, result); - checkValidRangeOperand("RHS", pos, nextPart); + while (IsDigit(this.look)){ + value.append(this.look); + GetChar(); + } + return value.length() == 0 ? null : value.toString(); + } - ParseNode[] children = { result, nextPart, }; - result = new ParseNode(RangePtg.instance, children); - hasRange = true; - } - if (hasRange) { - return augmentWithMemPtg(result); - } - return result; - } + private ParseNode parseRangeExpression() { + ParseNode result = parseRangeable(); + boolean hasRange = false; + while (look == ':') { + int pos = _pointer; + GetChar(); + ParseNode nextPart = parseRangeable(); + // Note - no range simplification here. An expr like "A1:B2:C3:D4:E5" should be + // grouped into area ref pairs like: "(A1:B2):(C3:D4):E5" + // Furthermore, Excel doesn't seem to simplify + // expressions like "Sheet1!A1:Sheet1:B2" into "Sheet1!A1:B2" - private static ParseNode augmentWithMemPtg(ParseNode root) { - Ptg memPtg; - if (needsMemFunc(root)) { - memPtg = new MemFuncPtg(root.getEncodedSize()); - } else { - memPtg = new MemAreaPtg(root.getEncodedSize()); - } - return new ParseNode(memPtg, root); - } - /** - * From OOO doc: "Whenever one operand of the reference subexpression is a function, - * a defined name, a 3D reference, or an external reference (and no error occurs), - * a tMemFunc token is used" - * - */ - private static boolean needsMemFunc(ParseNode root) { - Ptg token = root.getToken(); - if (token instanceof AbstractFunctionPtg) { - return true; - } - if (token instanceof ExternSheetReferenceToken) { // 3D refs - return true; - } - if (token instanceof NamePtg || token instanceof NameXPtg) { // 3D refs - return true; - } + checkValidRangeOperand("LHS", pos, result); + checkValidRangeOperand("RHS", pos, nextPart); - if (token instanceof OperationPtg || token instanceof ParenthesisPtg) { - // expect RangePtg, but perhaps also UnionPtg, IntersectionPtg etc - for(ParseNode child : root.getChildren()) { - if (needsMemFunc(child)) { - return true; - } - } - return false; - } - if (token instanceof OperandPtg) { - return false; - } - if (token instanceof OperationPtg) { - return true; - } + ParseNode[] children = { result, nextPart, }; + result = new ParseNode(RangePtg.instance, children); + hasRange = true; + } + if (hasRange) { + return augmentWithMemPtg(result); + } + return result; + } - return false; - } + private static ParseNode augmentWithMemPtg(ParseNode root) { + Ptg memPtg; + if (needsMemFunc(root)) { + memPtg = new MemFuncPtg(root.getEncodedSize()); + } else { + memPtg = new MemAreaPtg(root.getEncodedSize()); + } + return new ParseNode(memPtg, root); + } + /** + * From OOO doc: "Whenever one operand of the reference subexpression is a function, + * a defined name, a 3D reference, or an external reference (and no error occurs), + * a tMemFunc token is used" + * + */ + private static boolean needsMemFunc(ParseNode root) { + Ptg token = root.getToken(); + if (token instanceof AbstractFunctionPtg) { + return true; + } + if (token instanceof ExternSheetReferenceToken) { // 3D refs + return true; + } + if (token instanceof NamePtg || token instanceof NameXPtg) { // 3D refs + return true; + } - /** - * @param currentParsePosition used to format a potential error message - */ - private static void checkValidRangeOperand(String sideName, int currentParsePosition, ParseNode pn) { - if (!isValidRangeOperand(pn)) { - throw new FormulaParseException("The " + sideName - + " of the range operator ':' at position " - + currentParsePosition + " is not a proper reference."); - } - } + if (token instanceof OperationPtg || token instanceof ParenthesisPtg) { + // expect RangePtg, but perhaps also UnionPtg, IntersectionPtg etc + for(ParseNode child : root.getChildren()) { + if (needsMemFunc(child)) { + return true; + } + } + return false; + } + if (token instanceof OperandPtg) { + return false; + } + if (token instanceof OperationPtg) { + return true; + } - /** - * @return false if sub-expression represented the specified ParseNode definitely - * cannot appear on either side of the range (':') operator - */ - private static boolean isValidRangeOperand(ParseNode a) { - Ptg tkn = a.getToken(); - // Note - order is important for these instance-of checks - if (tkn instanceof OperandPtg) { - // notably cell refs and area refs - return true; - } + return false; + } - // next 2 are special cases of OperationPtg - if (tkn instanceof AbstractFunctionPtg) { - AbstractFunctionPtg afp = (AbstractFunctionPtg) tkn; - byte returnClass = afp.getDefaultOperandClass(); - return Ptg.CLASS_REF == returnClass; - } - if (tkn instanceof ValueOperatorPtg) { - return false; - } - if (tkn instanceof OperationPtg) { - return true; - } + /** + * @param currentParsePosition used to format a potential error message + */ + private static void checkValidRangeOperand(String sideName, int currentParsePosition, ParseNode pn) { + if (!isValidRangeOperand(pn)) { + throw new FormulaParseException("The " + sideName + + " of the range operator ':' at position " + + currentParsePosition + " is not a proper reference."); + } + } - // one special case of ControlPtg - if (tkn instanceof ParenthesisPtg) { - // parenthesis Ptg should have only one child - return isValidRangeOperand(a.getChildren()[0]); - } + /** + * @return false if sub-expression represented the specified ParseNode definitely + * cannot appear on either side of the range (':') operator + */ + private static boolean isValidRangeOperand(ParseNode a) { + Ptg tkn = a.getToken(); + // Note - order is important for these instance-of checks + if (tkn instanceof OperandPtg) { + // notably cell refs and area refs + return true; + } - // one special case of ScalarConstantPtg - if (tkn == ErrPtg.REF_INVALID) { - return true; - } + // next 2 are special cases of OperationPtg + if (tkn instanceof AbstractFunctionPtg) { + AbstractFunctionPtg afp = (AbstractFunctionPtg) tkn; + byte returnClass = afp.getDefaultOperandClass(); + return Ptg.CLASS_REF == returnClass; + } + if (tkn instanceof ValueOperatorPtg) { + return false; + } + if (tkn instanceof OperationPtg) { + return true; + } - // All other ControlPtgs and ScalarConstantPtgs cannot be used with ':' - return false; - } + // one special case of ControlPtg + if (tkn instanceof ParenthesisPtg) { + // parenthesis Ptg should have only one child + return isValidRangeOperand(a.getChildren()[0]); + } - /** - * Parses area refs (things which could be the operand of ':') and simple factors - * Examples - *

-	 *   A$1
-	 *   $A$1 :  $B1
-	 *   A1 .......	C2
-	 *   Sheet1 !$A1
-	 *   a..b!A1
-	 *   'my sheet'!A1
-	 *   .my.sheet!A1
-	 *   'my sheet':'my alt sheet'!A1
-	 *   .my.sheet1:.my.sheet2!$B$2
-	 *   my.named..range.
-	 *   'my sheet'!my.named.range
-	 *   .my.sheet!my.named.range
-	 *   foo.bar(123.456, "abc")
-	 *   123.456
-	 *   "abc"
-	 *   true
+        // one special case of ScalarConstantPtg
+        if (tkn == ErrPtg.REF_INVALID) {
+            return true;
+        }
+
+        // All other ControlPtgs and ScalarConstantPtgs cannot be used with ':'
+        return false;
+    }
+
+    /**
+     * Parses area refs (things which could be the operand of ':') and simple factors
+     * Examples
+     * 
+     *   A$1
+     *   $A$1 :  $B1
+     *   A1 .......    C2
+     *   Sheet1 !$A1
+     *   a..b!A1
+     *   'my sheet'!A1
+     *   .my.sheet!A1
+     *   'my sheet':'my alt sheet'!A1
+     *   .my.sheet1:.my.sheet2!$B$2
+     *   my.named..range.
+     *   'my sheet'!my.named.range
+     *   .my.sheet!my.named.range
+     *   foo.bar(123.456, "abc")
+     *   123.456
+     *   "abc"
+     *   true
      *   [Foo.xls]!$A$1
-	 *   [Foo.xls]'my sheet'!$A$1
-	 *   [Foo.xls]!my.named.range
-	 * 
- * - */ - private ParseNode parseRangeable() { - SkipWhite(); - int savePointer = _pointer; - SheetIdentifier sheetIden = parseSheetName(); - - if (sheetIden == null) { - resetPointer(savePointer); - } else { - SkipWhite(); - savePointer = _pointer; - } + * [Foo.xls]'my sheet'!$A$1 + * [Foo.xls]!my.named.range + *
+ * + */ + private ParseNode parseRangeable() { + SkipWhite(); + int savePointer = _pointer; + SheetIdentifier sheetIden = parseSheetName(); + + if (sheetIden == null) { + resetPointer(savePointer); + } else { + SkipWhite(); + savePointer = _pointer; + } - SimpleRangePart part1 = parseSimpleRangePart(); - if (part1 == null) { - if (sheetIden != null) { + SimpleRangePart part1 = parseSimpleRangePart(); + if (part1 == null) { + if (sheetIden != null) { if(look == '#'){ // error ref like MySheet!#REF! return new ParseNode(ErrPtg.valueOf(parseErrorLiteral())); } else { @@ -438,152 +484,434 @@ public final class FormulaParser { } return new ParseNode(nameXPtg); } - } - return parseNonRange(savePointer); - } - boolean whiteAfterPart1 = IsWhite(look); - if (whiteAfterPart1) { - SkipWhite(); - } + } + return parseNonRange(savePointer); + } + boolean whiteAfterPart1 = IsWhite(look); + if (whiteAfterPart1) { + SkipWhite(); + } - if (look == ':') { - int colonPos = _pointer; - GetChar(); - SkipWhite(); - SimpleRangePart part2 = parseSimpleRangePart(); - if (part2 != null && !part1.isCompatibleForArea(part2)) { - // second part is not compatible with an area ref e.g. S!A1:S!B2 - // where S might be a sheet name (that looks like a column name) + if (look == ':') { + int colonPos = _pointer; + GetChar(); + SkipWhite(); + SimpleRangePart part2 = parseSimpleRangePart(); + if (part2 != null && !part1.isCompatibleForArea(part2)) { + // second part is not compatible with an area ref e.g. S!A1:S!B2 + // where S might be a sheet name (that looks like a column name) - part2 = null; - } - if (part2 == null) { - // second part is not compatible with an area ref e.g. A1:OFFSET(B2, 1, 2) - // reset and let caller use explicit range operator - resetPointer(colonPos); - if (!part1.isCell()) { - String prefix = ""; - if (sheetIden != null) { - prefix = "'" + sheetIden.getSheetIdentifier().getName() + '!'; - } - throw new FormulaParseException(prefix + part1.getRep() + "' is not a proper reference."); - } - } - return createAreaRefParseNode(sheetIden, part1, part2); - } + part2 = null; + } + if (part2 == null) { + // second part is not compatible with an area ref e.g. A1:OFFSET(B2, 1, 2) + // reset and let caller use explicit range operator + resetPointer(colonPos); + if (!part1.isCell()) { + String prefix = ""; + if (sheetIden != null) { + prefix = "'" + sheetIden.getSheetIdentifier().getName() + '!'; + } + throw new FormulaParseException(prefix + part1.getRep() + "' is not a proper reference."); + } + } + return createAreaRefParseNode(sheetIden, part1, part2); + } - if (look == '.') { - GetChar(); - int dotCount = 1; - while (look =='.') { - dotCount ++; - GetChar(); - } - boolean whiteBeforePart2 = IsWhite(look); + if (look == '.') { + GetChar(); + int dotCount = 1; + while (look =='.') { + dotCount ++; + GetChar(); + } + boolean whiteBeforePart2 = IsWhite(look); - SkipWhite(); - SimpleRangePart part2 = parseSimpleRangePart(); - String part1And2 = _formulaString.substring(savePointer-1, _pointer-1); - if (part2 == null) { - if (sheetIden != null) { - throw new FormulaParseException("Complete area reference expected after sheet name at index " - + _pointer + "."); - } - return parseNonRange(savePointer); - } + SkipWhite(); + SimpleRangePart part2 = parseSimpleRangePart(); + String part1And2 = _formulaString.substring(savePointer-1, _pointer-1); + if (part2 == null) { + if (sheetIden != null) { + throw new FormulaParseException("Complete area reference expected after sheet name at index " + + _pointer + "."); + } + return parseNonRange(savePointer); + } - if (whiteAfterPart1 || whiteBeforePart2) { - if (part1.isRowOrColumn() || part2.isRowOrColumn()) { - // "A .. B" not valid syntax for "A:B" - // and there's no other valid expression that fits this grammar - throw new FormulaParseException("Dotted range (full row or column) expression '" - + part1And2 + "' must not contain whitespace."); - } - return createAreaRefParseNode(sheetIden, part1, part2); - } + if (whiteAfterPart1 || whiteBeforePart2) { + if (part1.isRowOrColumn() || part2.isRowOrColumn()) { + // "A .. B" not valid syntax for "A:B" + // and there's no other valid expression that fits this grammar + throw new FormulaParseException("Dotted range (full row or column) expression '" + + part1And2 + "' must not contain whitespace."); + } + return createAreaRefParseNode(sheetIden, part1, part2); + } - if (dotCount == 1 && part1.isRow() && part2.isRow()) { - // actually, this is looking more like a number - return parseNonRange(savePointer); - } + if (dotCount == 1 && part1.isRow() && part2.isRow()) { + // actually, this is looking more like a number + return parseNonRange(savePointer); + } - if (part1.isRowOrColumn() || part2.isRowOrColumn()) { - if (dotCount != 2) { - throw new FormulaParseException("Dotted range (full row or column) expression '" + part1And2 - + "' must have exactly 2 dots."); - } - } - return createAreaRefParseNode(sheetIden, part1, part2); - } - if (part1.isCell() && isValidCellReference(part1.getRep())) { - return createAreaRefParseNode(sheetIden, part1, null); - } - if (sheetIden != null) { - throw new FormulaParseException("Second part of cell reference expected after sheet name at index " - + _pointer + "."); - } + if (part1.isRowOrColumn() || part2.isRowOrColumn()) { + if (dotCount != 2) { + throw new FormulaParseException("Dotted range (full row or column) expression '" + part1And2 + + "' must have exactly 2 dots."); + } + } + return createAreaRefParseNode(sheetIden, part1, part2); + } + if (part1.isCell() && isValidCellReference(part1.getRep())) { + return createAreaRefParseNode(sheetIden, part1, null); + } + if (sheetIden != null) { + throw new FormulaParseException("Second part of cell reference expected after sheet name at index " + + _pointer + "."); + } - return parseNonRange(savePointer); - } + return parseNonRange(savePointer); + } + + private final static String specHeaders = "Headers"; + private final static String specAll = "All"; + private final static String specData = "Data"; + private final static String specTotals = "Totals"; + private final static String specThisRow = "This Row"; + + /** + * Parses a structured reference, returns it as area reference. + * Examples: + *
+     * Table1[col]
+     * Table1[[#Totals],[col]]
+     * Table1[#Totals]
+     * Table1[#All]
+     * Table1[#Data]
+     * Table1[#Headers]
+     * Table1[#Totals]
+     * Table1[#This Row]
+     * Table1[[#All],[col]]
+     * Table1[[#Headers],[col]]
+     * Table1[[#Totals],[col]]
+     * Table1[[#All],[col1]:[col2]]
+     * Table1[[#Data],[col1]:[col2]]
+     * Table1[[#Headers],[col1]:[col2]]
+     * Table1[[#Totals],[col1]:[col2]]
+     * Table1[[#Headers],[#Data],[col2]]
+     * Table1[[#This Row], [col1]]
+     * Table1[ [col1]:[col2] ]
+     * 
+ * @param tableName + * @return + */ + private ParseNode parseStructuredReference(String tableName) { + + if ( ! (_ssVersion.equals(SpreadsheetVersion.EXCEL2007)) ) { + throw new FormulaParseException("Structured references work only on XSSF (Excel 2007+)!"); + } + Table tbl = _book.getTable(tableName); + if (tbl == null) { + throw new FormulaParseException("Illegal table name: '" + tableName + "'"); + } + String sheetName = tbl.getSheetName(); + + int startCol = tbl.getStartColIndex(); + int endCol = tbl.getEndColIndex(); + int startRow = tbl.getStartRowIndex(); + int endRow = tbl.getEndRowIndex(); + + // Do NOT return before done reading all the structured reference tokens from the input stream. + // Throwing exceptions is okay. + int savePtr0 = _pointer; + GetChar(); + + boolean isTotalsSpec = false; + boolean isThisRowSpec = false; + boolean isDataSpec = false; + boolean isHeadersSpec = false; + boolean isAllSpec = false; + int nSpecQuantifiers = 0; // The number of special quantifiers + while (true) { + int savePtr1 = _pointer; + String specName = parseAsSpecialQuantifier(); + if (specName == null) { + resetPointer(savePtr1); + break; + } + if (specName.equals(specAll)) { + isAllSpec = true; + } else if (specName.equals(specData)) { + isDataSpec = true; + } else if (specName.equals(specHeaders)) { + isHeadersSpec = true; + } else if (specName.equals(specThisRow)) { + isThisRowSpec = true; + } else if (specName.equals(specTotals)) { + isTotalsSpec = true; + } else { + throw new FormulaParseException("Unknown special quantifier "+ specName); + } + nSpecQuantifiers++; + if (look == ','){ + GetChar(); + } else { + break; + } + } + boolean isThisRow = false; + SkipWhite(); + if (look == '@') { + isThisRow = true; + GetChar(); + } + // parse column quantifier + String startColumnName = null; + String endColumnName = null; + int nColQuantifiers = 0; + int savePtr1 = _pointer; + startColumnName = parseAsColumnQuantifier(); + if (startColumnName == null) { + resetPointer(savePtr1); + } else { + nColQuantifiers++; + if (look == ','){ + throw new FormulaParseException("The formula "+ _formulaString + "is illegal: you should not use ',' with column quantifiers"); + } else if (look == ':') { + GetChar(); + endColumnName = parseAsColumnQuantifier(); + nColQuantifiers++; + if (endColumnName == null) { + throw new FormulaParseException("The formula "+ _formulaString + "is illegal: the string after ':' must be column quantifier"); + } + } + } + + if(nColQuantifiers == 0 && nSpecQuantifiers == 0){ + resetPointer(savePtr0); + savePtr0 = _pointer; + startColumnName = parseAsColumnQuantifier(); + if (startColumnName != null) { + nColQuantifiers++; + } else { + resetPointer(savePtr0); + String name = parseAsSpecialQuantifier(); + if (name!=null) { + if (name.equals(specAll)) { + isAllSpec = true; + } else if (name.equals(specData)) { + isDataSpec = true; + } else if (name.equals(specHeaders)) { + isHeadersSpec = true; + } else if (name.equals(specThisRow)) { + isThisRowSpec = true; + } else if (name.equals(specTotals)) { + isTotalsSpec = true; + } else { + throw new FormulaParseException("Unknown special quantifier "+ name); + } + nSpecQuantifiers++; + } else { + throw new FormulaParseException("The formula "+ _formulaString + " is illegal"); + } + } + } else { + Match(']'); + } + // Done reading from input stream + // Ok to return now - /** - * Parses simple factors that are not primitive ranges or range components - * i.e. '!', ':'(and equiv '...') do not appear - * Examples - *
-	 *   my.named...range.
-	 *   foo.bar(123.456, "abc")
-	 *   123.456
-	 *   "abc"
-	 *   true
-	 * 
- */ - private ParseNode parseNonRange(int savePointer) { - resetPointer(savePointer); + if (isTotalsSpec && !tbl.isHasTotalsRow()) { + return new ParseNode(ErrPtg.REF_INVALID); + } + if ((isThisRow || isThisRowSpec) && (_rowIndex < startRow || endRow < _rowIndex)) { + // structured reference is trying to reference a row above or below the table with [#This Row] or [@] + if (_rowIndex >= 0) { + return new ParseNode(ErrPtg.VALUE_INVALID); + } else { + throw new FormulaParseException( + "Formula contained [#This Row] or [@] structured reference but this row < 0. " + + "Row index must be specified for row-referencing structured references."); + } + } + + int actualStartRow = startRow; + int actualEndRow = endRow; + int actualStartCol = startCol; + int actualEndCol = endCol; + if (nSpecQuantifiers > 0) { + //Selecting rows + if (nSpecQuantifiers == 1 && isAllSpec) { + //do nothing + } else if (isDataSpec && isHeadersSpec) { + if (tbl.isHasTotalsRow()) { + actualEndRow = endRow - 1; + } + } else if (isDataSpec && isTotalsSpec) { + actualStartRow = startRow + 1; + } else if (nSpecQuantifiers == 1 && isDataSpec) { + actualStartRow = startRow + 1; + if (tbl.isHasTotalsRow()) { + actualEndRow = endRow - 1; + } + } else if (nSpecQuantifiers == 1 && isHeadersSpec) { + actualEndRow = actualStartRow; + } else if (nSpecQuantifiers == 1 && isTotalsSpec) { + actualStartRow = actualEndRow; + } else if ((nSpecQuantifiers == 1 && isThisRowSpec) || isThisRow) { + actualStartRow = _rowIndex; //The rowNum is 0 based + actualEndRow = _rowIndex; + } else { + throw new FormulaParseException("The formula "+ _formulaString + " is illegal"); + } + } else { + if (isThisRow) { // there is a @ + actualStartRow = _rowIndex; //The rowNum is 0 based + actualEndRow = _rowIndex; + } else { // Really no special quantifiers + actualStartRow++; + } + } - if (Character.isDigit(look)) { - return new ParseNode(parseNumber()); - } - if (look == '"') { - return new ParseNode(new StringPtg(parseStringLiteral())); - } - - // from now on we can only be dealing with non-quoted identifiers - // which will either be named ranges or functions - String name = parseAsName(); + //Selecting cols - if (look == '(') { - return function(name); - } - if (name.equalsIgnoreCase("TRUE") || name.equalsIgnoreCase("FALSE")) { - return new ParseNode(BoolPtg.valueOf(name.equalsIgnoreCase("TRUE"))); - } - if (_book == null) { - // Only test cases omit the book (expecting it not to be needed) - throw new IllegalStateException("Need book to evaluate name '" + name + "'"); - } - EvaluationName evalName = _book.getName(name, _sheetIndex); - if (evalName == null) { - throw new FormulaParseException("Specified named range '" - + name + "' does not exist in the current workbook."); - } - if (evalName.isRange()) { - return new ParseNode(evalName.createPtg()); - } - // TODO - what about NameX ? - throw new FormulaParseException("Specified name '" - + name + "' is not a range as expected."); - } - - private String parseAsName() { + if (nColQuantifiers == 2) { + if (startColumnName == null || endColumnName == null) { + throw new IllegalStateException("Fatal error"); + } + int startIdx = tbl.findColumnIndex(startColumnName); + int endIdx = tbl.findColumnIndex(endColumnName); + if (startIdx == -1 || endIdx == -1) { + throw new FormulaParseException("One of the columns "+ startColumnName +", "+ endColumnName +" doesn't exist in table "+ tbl.getName()); + } + actualStartCol = startCol+ startIdx; + actualEndCol = startCol + endIdx; + + } else if (nColQuantifiers == 1 && !isThisRow) { + if (startColumnName == null) { + throw new IllegalStateException("Fatal error"); + } + int idx = tbl.findColumnIndex(startColumnName); + if (idx == -1) { + throw new FormulaParseException("The column "+ startColumnName + " doesn't exist in table "+ tbl.getName()); + } + actualStartCol = startCol + idx; + actualEndCol = actualStartCol; + } + CellReference topLeft = new CellReference(actualStartRow, actualStartCol); + CellReference bottomRight = new CellReference(actualEndRow, actualEndCol); + SheetIdentifier sheetIden = new SheetIdentifier( null, new NameIdentifier(sheetName, true)); + Ptg ptg = _book.get3DReferencePtg(new AreaReference(topLeft, bottomRight), sheetIden); + return new ParseNode(ptg); + } + + /** + * Tries to parse the next as column - can contain whitespace + * Caller should save pointer. + * @return + */ + private String parseAsColumnQuantifier() { + if ( look != '[') { + return null; + } + GetChar(); + String name = ""; + if (look == '#') { + return null; + } + if (look == '@') { + GetChar(); + } + while (look!=']') { + name += look; + GetChar(); + } + Match(']'); + return name; + } + /** + * Tries to parse the next as special quantifier + * Caller should save pointer. + * @return + */ + private String parseAsSpecialQuantifier(){ + if ( look != '[') { + return null; + } + GetChar(); + if( look != '#') { + return null; + } + GetChar(); + String name = parseAsName(); + if ( name.equals("This")) { + name = name + ' ' + parseAsName(); + } + Match(']'); + return name; + } + + + /** + * Parses simple factors that are not primitive ranges or range components + * i.e. '!', ':'(and equiv '...') do not appear + * Examples + *
+     *   my.named...range.
+     *   foo.bar(123.456, "abc")
+     *   123.456
+     *   "abc"
+     *   true
+     * 
+ */ + private ParseNode parseNonRange(int savePointer) { + resetPointer(savePointer); + + if (Character.isDigit(look)) { + return new ParseNode(parseNumber()); + } + if (look == '"') { + return new ParseNode(new StringPtg(parseStringLiteral())); + } + + // from now on we can only be dealing with non-quoted identifiers + // which will either be named ranges or functions + String name = parseAsName(); + + if (look == '(') { + return function(name); + } + if(look == '['){ + return parseStructuredReference(name); + } + if (name.equalsIgnoreCase("TRUE") || name.equalsIgnoreCase("FALSE")) { + return new ParseNode(BoolPtg.valueOf(name.equalsIgnoreCase("TRUE"))); + } + if (_book == null) { + // Only test cases omit the book (expecting it not to be needed) + throw new IllegalStateException("Need book to evaluate name '" + name + "'"); + } + EvaluationName evalName = _book.getName(name, _sheetIndex); + if (evalName == null) { + throw new FormulaParseException("Specified named range '" + + name + "' does not exist in the current workbook."); + } + if (evalName.isRange()) { + return new ParseNode(evalName.createPtg()); + } + // TODO - what about NameX ? + throw new FormulaParseException("Specified name '" + + name + "' is not a range as expected."); + } + + private String parseAsName() { StringBuilder sb = new StringBuilder(); - // defined names may begin with a letter or underscore - if (!Character.isLetter(look) && look != '_') { - throw expected("number, string, or defined name"); + // defined names may begin with a letter or underscore or backslash + if (!Character.isLetter(look) && look != '_' && look != '\\') { + throw expected("number, string, defined name, or data table"); } while (isValidDefinedNameChar(look)) { sb.append(look); @@ -592,1060 +920,1062 @@ public final class FormulaParser { SkipWhite(); return sb.toString(); - } + } - /** - * - * @return true if the specified character may be used in a defined name - */ - private static boolean isValidDefinedNameChar(char ch) { - if (Character.isLetterOrDigit(ch)) { - return true; - } - switch (ch) { - case '.': - case '_': - case '?': - case '\\': // of all things - return true; - } - return false; - } - - /** - * - * @param sheetIden may be null - * @param part1 - * @param part2 may be null - */ - private ParseNode createAreaRefParseNode(SheetIdentifier sheetIden, SimpleRangePart part1, - SimpleRangePart part2) throws FormulaParseException { - Ptg ptg; - if (part2 == null) { - CellReference cr = part1.getCellReference(); - if (sheetIden == null) { - ptg = new RefPtg(cr); - } else { - ptg = _book.get3DReferencePtg(cr, sheetIden); - } - } else { - AreaReference areaRef = createAreaRef(part1, part2); - - if (sheetIden == null) { - ptg = new AreaPtg(areaRef); - } else { - ptg = _book.get3DReferencePtg(areaRef, sheetIden); - } - } - return new ParseNode(ptg); - } - - private AreaReference createAreaRef(SimpleRangePart part1, SimpleRangePart part2) { - if (!part1.isCompatibleForArea(part2)) { - throw new FormulaParseException("has incompatible parts: '" - + part1.getRep() + "' and '" + part2.getRep() + "'."); - } - if (part1.isRow()) { - return AreaReference.getWholeRow(_ssVersion, part1.getRep(), part2.getRep()); - } - if (part1.isColumn()) { - return AreaReference.getWholeColumn(_ssVersion, part1.getRep(), part2.getRep()); - } - return new AreaReference(part1.getCellReference(), part2.getCellReference()); - } - - /** - * Matches a zero or one letter-runs followed by zero or one digit-runs. - * Either or both runs man optionally be prefixed with a single '$'. - * (copied+modified from {@link org.apache.poi.ss.util.CellReference#CELL_REF_PATTERN}) - */ - private static final Pattern CELL_REF_PATTERN = Pattern.compile("(\\$?[A-Za-z]+)?(\\$?[0-9]+)?"); - - /** - * Parses out a potential LHS or RHS of a ':' intended to produce a plain AreaRef. Normally these are - * proper cell references but they could also be row or column refs like "$AC" or "10" - * @return null (and leaves {@link #_pointer} unchanged if a proper range part does not parse out - */ - private SimpleRangePart parseSimpleRangePart() { - int ptr = _pointer-1; // TODO avoid StringIndexOutOfBounds - boolean hasDigits = false; - boolean hasLetters = false; - while (ptr < _formulaLength) { - char ch = _formulaString.charAt(ptr); - if (Character.isDigit(ch)) { - hasDigits = true; - } else if (Character.isLetter(ch)) { - hasLetters = true; - } else if (ch =='$' || ch =='_') { - // - } else { - break; - } - ptr++; - } - if (ptr <= _pointer-1) { - return null; - } - String rep = _formulaString.substring(_pointer-1, ptr); - if (!CELL_REF_PATTERN.matcher(rep).matches()) { - return null; - } - // Check range bounds against grid max - if (hasLetters && hasDigits) { - if (!isValidCellReference(rep)) { - return null; - } - } else if (hasLetters) { - if (!CellReference.isColumnWithnRange(rep.replace("$", ""), _ssVersion)) { - return null; - } - } else if (hasDigits) { - int i; - try { - i = Integer.parseInt(rep.replace("$", "")); - } catch (NumberFormatException e) { - return null; - } - if (i<1 || i>_ssVersion.getMaxRows()) { - return null; - } - } else { - // just dollars ? can this happen? - return null; - } - - - resetPointer(ptr+1); // stepping forward - return new SimpleRangePart(rep, hasLetters, hasDigits); - } - - - /** - * A1, $A1, A$1, $A$1, A, 1 - */ - private static final class SimpleRangePart { - private enum Type { - CELL, ROW, COLUMN; - - public static Type get(boolean hasLetters, boolean hasDigits) { - if (hasLetters) { - return hasDigits ? CELL : COLUMN; - } - if (!hasDigits) { - throw new IllegalArgumentException("must have either letters or numbers"); - } - return ROW; - } - } - - private final Type _type; - private final String _rep; - - public SimpleRangePart(String rep, boolean hasLetters, boolean hasNumbers) { - _rep = rep; - _type = Type.get(hasLetters, hasNumbers); - } - - public boolean isCell() { - return _type == Type.CELL; - } - - public boolean isRowOrColumn() { - return _type != Type.CELL; - } - - public CellReference getCellReference() { - if (_type != Type.CELL) { - throw new IllegalStateException("Not applicable to this type"); - } - return new CellReference(_rep); - } - - public boolean isColumn() { - return _type == Type.COLUMN; - } - - public boolean isRow() { - return _type == Type.ROW; - } - - public String getRep() { - return _rep; - } - - /** - * @return true if the two range parts can be combined in an - * {@link AreaPtg} ( Note - the explicit range operator (:) may still be valid - * when this method returns false ) - */ - public boolean isCompatibleForArea(SimpleRangePart part2) { - return _type == part2._type; - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(64); - sb.append(getClass().getName()).append(" ["); - sb.append(_rep); - sb.append("]"); - return sb.toString(); - } - } - - /** - * Note - caller should reset {@link #_pointer} upon null result - * @return The sheet name as an identifier null if '!' is not found in the right place - */ - private SheetIdentifier parseSheetName() { - String bookName; - if (look == '[') { - StringBuilder sb = new StringBuilder(); - GetChar(); - while (look != ']') { - sb.append(look); - GetChar(); - } - GetChar(); - bookName = sb.toString(); - } else { - bookName = null; - } - - if (look == '\'') { - StringBuffer sb = new StringBuffer(); - - Match('\''); - boolean done = look == '\''; - while(!done) { - sb.append(look); - GetChar(); - if(look == '\'') - { - Match('\''); - done = look != '\''; - } - } - - NameIdentifier iden = new NameIdentifier(sb.toString(), true); - // quoted identifier - can't concatenate anything more - SkipWhite(); - if (look == '!') { - GetChar(); - return new SheetIdentifier(bookName, iden); - } - // See if it's a multi-sheet range, eg Sheet1:Sheet3!A1 - if (look == ':') { - return parseSheetRange(bookName, iden); + /** + * + * @return true if the specified character may be used in a defined name + */ + private static boolean isValidDefinedNameChar(char ch) { + if (Character.isLetterOrDigit(ch)) { + return true; + } + switch (ch) { + case '.': + case '_': + case '?': + case '\\': // of all things + return true; + } + return false; + } + + /** + * + * @param sheetIden may be null + * @param part1 + * @param part2 may be null + */ + private ParseNode createAreaRefParseNode(SheetIdentifier sheetIden, SimpleRangePart part1, + SimpleRangePart part2) throws FormulaParseException { + Ptg ptg; + if (part2 == null) { + CellReference cr = part1.getCellReference(); + if (sheetIden == null) { + ptg = new RefPtg(cr); + } else { + ptg = _book.get3DReferencePtg(cr, sheetIden); } - return null; - } + } else { + AreaReference areaRef = createAreaRef(part1, part2); - // unquoted sheet names must start with underscore or a letter - if (look =='_' || Character.isLetter(look)) { - StringBuilder sb = new StringBuilder(); - // can concatenate idens with dots - while (isUnquotedSheetNameChar(look)) { - sb.append(look); - GetChar(); - } - NameIdentifier iden = new NameIdentifier(sb.toString(), false); - SkipWhite(); - if (look == '!') { - GetChar(); - return new SheetIdentifier(bookName, iden); - } + if (sheetIden == null) { + ptg = new AreaPtg(areaRef); + } else { + ptg = _book.get3DReferencePtg(areaRef, sheetIden); + } + } + return new ParseNode(ptg); + } + + private AreaReference createAreaRef(SimpleRangePart part1, SimpleRangePart part2) { + if (!part1.isCompatibleForArea(part2)) { + throw new FormulaParseException("has incompatible parts: '" + + part1.getRep() + "' and '" + part2.getRep() + "'."); + } + if (part1.isRow()) { + return AreaReference.getWholeRow(_ssVersion, part1.getRep(), part2.getRep()); + } + if (part1.isColumn()) { + return AreaReference.getWholeColumn(_ssVersion, part1.getRep(), part2.getRep()); + } + return new AreaReference(part1.getCellReference(), part2.getCellReference()); + } + + /** + * Matches a zero or one letter-runs followed by zero or one digit-runs. + * Either or both runs man optionally be prefixed with a single '$'. + * (copied+modified from {@link org.apache.poi.ss.util.CellReference#CELL_REF_PATTERN}) + */ + private static final Pattern CELL_REF_PATTERN = Pattern.compile("(\\$?[A-Za-z]+)?(\\$?[0-9]+)?"); + + /** + * Parses out a potential LHS or RHS of a ':' intended to produce a plain AreaRef. Normally these are + * proper cell references but they could also be row or column refs like "$AC" or "10" + * @return null (and leaves {@link #_pointer} unchanged if a proper range part does not parse out + */ + private SimpleRangePart parseSimpleRangePart() { + int ptr = _pointer-1; // TODO avoid StringIndexOutOfBounds + boolean hasDigits = false; + boolean hasLetters = false; + while (ptr < _formulaLength) { + char ch = _formulaString.charAt(ptr); + if (Character.isDigit(ch)) { + hasDigits = true; + } else if (Character.isLetter(ch)) { + hasLetters = true; + } else if (ch =='$' || ch =='_') { + // + } else { + break; + } + ptr++; + } + if (ptr <= _pointer-1) { + return null; + } + String rep = _formulaString.substring(_pointer-1, ptr); + if (!CELL_REF_PATTERN.matcher(rep).matches()) { + return null; + } + // Check range bounds against grid max + if (hasLetters && hasDigits) { + if (!isValidCellReference(rep)) { + return null; + } + } else if (hasLetters) { + if (!CellReference.isColumnWithnRange(rep.replace("$", ""), _ssVersion)) { + return null; + } + } else if (hasDigits) { + int i; + try { + i = Integer.parseInt(rep.replace("$", "")); + } catch (NumberFormatException e) { + return null; + } + if (i<1 || i>_ssVersion.getMaxRows()) { + return null; + } + } else { + // just dollars ? can this happen? + return null; + } + + + resetPointer(ptr+1); // stepping forward + return new SimpleRangePart(rep, hasLetters, hasDigits); + } + + + /** + * A1, $A1, A$1, $A$1, A, 1 + */ + private static final class SimpleRangePart { + private enum Type { + CELL, ROW, COLUMN; + + public static Type get(boolean hasLetters, boolean hasDigits) { + if (hasLetters) { + return hasDigits ? CELL : COLUMN; + } + if (!hasDigits) { + throw new IllegalArgumentException("must have either letters or numbers"); + } + return ROW; + } + } + + private final Type _type; + private final String _rep; + + public SimpleRangePart(String rep, boolean hasLetters, boolean hasNumbers) { + _rep = rep; + _type = Type.get(hasLetters, hasNumbers); + } + + public boolean isCell() { + return _type == Type.CELL; + } + + public boolean isRowOrColumn() { + return _type != Type.CELL; + } + + public CellReference getCellReference() { + if (_type != Type.CELL) { + throw new IllegalStateException("Not applicable to this type"); + } + return new CellReference(_rep); + } + + public boolean isColumn() { + return _type == Type.COLUMN; + } + + public boolean isRow() { + return _type == Type.ROW; + } + + public String getRep() { + return _rep; + } + + /** + * @return true if the two range parts can be combined in an + * {@link AreaPtg} ( Note - the explicit range operator (:) may still be valid + * when this method returns false ) + */ + public boolean isCompatibleForArea(SimpleRangePart part2) { + return _type == part2._type; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(64); + sb.append(getClass().getName()).append(" ["); + sb.append(_rep); + sb.append("]"); + return sb.toString(); + } + } + + /** + * Note - caller should reset {@link #_pointer} upon null result + * @return The sheet name as an identifier null if '!' is not found in the right place + */ + private SheetIdentifier parseSheetName() { + String bookName; + if (look == '[') { + StringBuilder sb = new StringBuilder(); + GetChar(); + while (look != ']') { + sb.append(look); + GetChar(); + } + GetChar(); + bookName = sb.toString(); + } else { + bookName = null; + } + + if (look == '\'') { + StringBuffer sb = new StringBuffer(); + + Match('\''); + boolean done = look == '\''; + while(!done) { + sb.append(look); + GetChar(); + if(look == '\'') + { + Match('\''); + done = look != '\''; + } + } + + NameIdentifier iden = new NameIdentifier(sb.toString(), true); + // quoted identifier - can't concatenate anything more + SkipWhite(); + if (look == '!') { + GetChar(); + return new SheetIdentifier(bookName, iden); + } // See if it's a multi-sheet range, eg Sheet1:Sheet3!A1 if (look == ':') { return parseSheetRange(bookName, iden); } - return null; - } - if (look == '!' && bookName != null) { - // Raw book reference, without a sheet + return null; + } + + // unquoted sheet names must start with underscore or a letter + if (look =='_' || Character.isLetter(look)) { + StringBuilder sb = new StringBuilder(); + // can concatenate idens with dots + while (isUnquotedSheetNameChar(look)) { + sb.append(look); + GetChar(); + } + NameIdentifier iden = new NameIdentifier(sb.toString(), false); + SkipWhite(); + if (look == '!') { + GetChar(); + return new SheetIdentifier(bookName, iden); + } + // See if it's a multi-sheet range, eg Sheet1:Sheet3!A1 + if (look == ':') { + return parseSheetRange(bookName, iden); + } + return null; + } + if (look == '!' && bookName != null) { + // Raw book reference, without a sheet GetChar(); - return new SheetIdentifier(bookName, null); - } - return null; - } - - /** - * If we have something that looks like [book]Sheet1: or - * Sheet1, see if it's actually a range eg Sheet1:Sheet2! - */ - private SheetIdentifier parseSheetRange(String bookname, NameIdentifier sheet1Name) { + return new SheetIdentifier(bookName, null); + } + return null; + } + + /** + * If we have something that looks like [book]Sheet1: or + * Sheet1, see if it's actually a range eg Sheet1:Sheet2! + */ + private SheetIdentifier parseSheetRange(String bookname, NameIdentifier sheet1Name) { GetChar(); SheetIdentifier sheet2 = parseSheetName(); if (sheet2 != null) { return new SheetRangeIdentifier(bookname, sheet1Name, sheet2.getSheetIdentifier()); } return null; - } + } - /** - * very similar to {@link SheetNameFormatter#isSpecialChar(char)} - */ - private static boolean isUnquotedSheetNameChar(char ch) { - if(Character.isLetterOrDigit(ch)) { - return true; - } - switch(ch) { - case '.': // dot is OK - case '_': // underscore is OK - return true; - } - return false; - } + /** + * very similar to {@link SheetNameFormatter#isSpecialChar(char)} + */ + private static boolean isUnquotedSheetNameChar(char ch) { + if(Character.isLetterOrDigit(ch)) { + return true; + } + switch(ch) { + case '.': // dot is OK + case '_': // underscore is OK + return true; + } + return false; + } - /** - * @return true if the specified name is a valid cell reference - */ - private boolean isValidCellReference(String str) { - //check range bounds against grid max - boolean result = CellReference.classifyCellReference(str, _ssVersion) == NameType.CELL; + /** + * @return true if the specified name is a valid cell reference + */ + private boolean isValidCellReference(String str) { + //check range bounds against grid max + boolean result = CellReference.classifyCellReference(str, _ssVersion) == NameType.CELL; - if(result){ - /** - * Check if the argument is a function. Certain names can be either a cell reference or a function name - * depending on the contenxt. Compare the following examples in Excel 2007: - * (a) LOG10(100) + 1 - * (b) LOG10 + 1 - * In (a) LOG10 is a name of a built-in function. In (b) LOG10 is a cell reference - */ - boolean isFunc = FunctionMetadataRegistry.getFunctionByName(str.toUpperCase(Locale.ROOT)) != null; - if(isFunc){ - int savePointer = _pointer; - resetPointer(_pointer + str.length()); - SkipWhite(); - // open bracket indicates that the argument is a function, - // the returning value should be false, i.e. "not a valid cell reference" - result = look != '('; - resetPointer(savePointer); - } - } - return result; - } + if(result){ + /** + * Check if the argument is a function. Certain names can be either a cell reference or a function name + * depending on the contenxt. Compare the following examples in Excel 2007: + * (a) LOG10(100) + 1 + * (b) LOG10 + 1 + * In (a) LOG10 is a name of a built-in function. In (b) LOG10 is a cell reference + */ + boolean isFunc = FunctionMetadataRegistry.getFunctionByName(str.toUpperCase(Locale.ROOT)) != null; + if(isFunc){ + int savePointer = _pointer; + resetPointer(_pointer + str.length()); + SkipWhite(); + // open bracket indicates that the argument is a function, + // the returning value should be false, i.e. "not a valid cell reference" + result = look != '('; + resetPointer(savePointer); + } + } + return result; + } - /** - * Note - Excel function names are 'case aware but not case sensitive'. This method may end - * up creating a defined name record in the workbook if the specified name is not an internal - * Excel function, and has not been encountered before. - * - * Side effect: creates workbook name if name is not recognized (name is probably a UDF) - * - * @param name case preserved function name (as it was entered/appeared in the formula). - */ - private ParseNode function(String name) { - Ptg nameToken = null; - if(!AbstractFunctionPtg.isBuiltInFunctionName(name)) { - // user defined function - // in the token tree, the name is more or less the first argument + /** + * Note - Excel function names are 'case aware but not case sensitive'. This method may end + * up creating a defined name record in the workbook if the specified name is not an internal + * Excel function, and has not been encountered before. + * + * Side effect: creates workbook name if name is not recognized (name is probably a UDF) + * + * @param name case preserved function name (as it was entered/appeared in the formula). + */ + private ParseNode function(String name) { + Ptg nameToken = null; + if(!AbstractFunctionPtg.isBuiltInFunctionName(name)) { + // user defined function + // in the token tree, the name is more or less the first argument - if (_book == null) { - // Only test cases omit the book (expecting it not to be needed) - throw new IllegalStateException("Need book to evaluate name '" + name + "'"); - } - // Check to see if name is a named range in the workbook - EvaluationName hName = _book.getName(name, _sheetIndex); - if (hName != null) { - if (!hName.isFunctionName()) { - throw new FormulaParseException("Attempt to use name '" + name - + "' as a function, but defined name in workbook does not refer to a function"); - } - - // calls to user-defined functions within the workbook - // get a Name token which points to a defined name record - nameToken = hName.createPtg(); - } else { - // Check if name is an external names table - nameToken = _book.getNameXPtg(name, null); - if (nameToken == null) { - // name is not an internal or external name - if (log.check(POILogger.WARN)) { - log.log(POILogger.WARN, - "FormulaParser.function: Name '" + name + "' is completely unknown in the current workbook."); - } - // name is probably the name of an unregistered User-Defined Function - switch (_book.getSpreadsheetVersion()) { - case EXCEL97: - // HSSFWorkbooks require a name to be added to Workbook defined names table - addName(name); - hName = _book.getName(name, _sheetIndex); - nameToken = hName.createPtg(); - break; - case EXCEL2007: - // XSSFWorkbooks store formula names as strings. - nameToken = new NameXPxg(name); - break; - default: - throw new IllegalStateException("Unexpected spreadsheet version: " + _book.getSpreadsheetVersion().name()); - } - } - } - } + if (_book == null) { + // Only test cases omit the book (expecting it not to be needed) + throw new IllegalStateException("Need book to evaluate name '" + name + "'"); + } + // Check to see if name is a named range in the workbook + EvaluationName hName = _book.getName(name, _sheetIndex); + if (hName != null) { + if (!hName.isFunctionName()) { + throw new FormulaParseException("Attempt to use name '" + name + + "' as a function, but defined name in workbook does not refer to a function"); + } + + // calls to user-defined functions within the workbook + // get a Name token which points to a defined name record + nameToken = hName.createPtg(); + } else { + // Check if name is an external names table + nameToken = _book.getNameXPtg(name, null); + if (nameToken == null) { + // name is not an internal or external name + if (log.check(POILogger.WARN)) { + log.log(POILogger.WARN, + "FormulaParser.function: Name '" + name + "' is completely unknown in the current workbook."); + } + // name is probably the name of an unregistered User-Defined Function + switch (_book.getSpreadsheetVersion()) { + case EXCEL97: + // HSSFWorkbooks require a name to be added to Workbook defined names table + addName(name); + hName = _book.getName(name, _sheetIndex); + nameToken = hName.createPtg(); + break; + case EXCEL2007: + // XSSFWorkbooks store formula names as strings. + nameToken = new NameXPxg(name); + break; + default: + throw new IllegalStateException("Unexpected spreadsheet version: " + _book.getSpreadsheetVersion().name()); + } + } + } + } - Match('('); - ParseNode[] args = Arguments(); - Match(')'); + Match('('); + ParseNode[] args = Arguments(); + Match(')'); - return getFunction(name, nameToken, args); - } - - /** - * Adds a name (named range or user defined function) to underlying workbook's names table - * @param functionName - */ - private final void addName(String functionName) { - final Name name = _book.createName(); - name.setFunction(true); - name.setNameName(functionName); - name.setSheetIndex(_sheetIndex); - } + return getFunction(name, nameToken, args); + } + + /** + * Adds a name (named range or user defined function) to underlying workbook's names table + * @param functionName + */ + private final void addName(String functionName) { + final Name name = _book.createName(); + name.setFunction(true); + name.setNameName(functionName); + name.setSheetIndex(_sheetIndex); + } - /** - * Generates the variable function ptg for the formula. - *

- * For IF Formulas, additional PTGs are added to the tokens - * @param name a {@link NamePtg} or {@link NameXPtg} or null - * @return Ptg a null is returned if we're in an IF formula, it needs extreme manipulation and is handled in this function - */ - private ParseNode getFunction(String name, Ptg namePtg, ParseNode[] args) { + /** + * Generates the variable function ptg for the formula. + *

+ * For IF Formulas, additional PTGs are added to the tokens + * @param name a {@link NamePtg} or {@link NameXPtg} or null + * @return Ptg a null is returned if we're in an IF formula, it needs extreme manipulation and is handled in this function + */ + private ParseNode getFunction(String name, Ptg namePtg, ParseNode[] args) { - FunctionMetadata fm = FunctionMetadataRegistry.getFunctionByName(name.toUpperCase(Locale.ROOT)); - int numArgs = args.length; - if(fm == null) { - if (namePtg == null) { - throw new IllegalStateException("NamePtg must be supplied for external functions"); - } - // must be external function - ParseNode[] allArgs = new ParseNode[numArgs+1]; - allArgs[0] = new ParseNode(namePtg); - System.arraycopy(args, 0, allArgs, 1, numArgs); - return new ParseNode(FuncVarPtg.create(name, numArgs+1), allArgs); - } + FunctionMetadata fm = FunctionMetadataRegistry.getFunctionByName(name.toUpperCase(Locale.ROOT)); + int numArgs = args.length; + if(fm == null) { + if (namePtg == null) { + throw new IllegalStateException("NamePtg must be supplied for external functions"); + } + // must be external function + ParseNode[] allArgs = new ParseNode[numArgs+1]; + allArgs[0] = new ParseNode(namePtg); + System.arraycopy(args, 0, allArgs, 1, numArgs); + return new ParseNode(FuncVarPtg.create(name, numArgs+1), allArgs); + } - if (namePtg != null) { - throw new IllegalStateException("NamePtg no applicable to internal functions"); - } - boolean isVarArgs = !fm.hasFixedArgsLength(); - int funcIx = fm.getIndex(); - if (funcIx == FunctionMetadataRegistry.FUNCTION_INDEX_SUM && args.length == 1) { - // Excel encodes the sum of a single argument as tAttrSum - // POI does the same for consistency, but this is not critical - return new ParseNode(AttrPtg.getSumSingle(), args); - // The code below would encode tFuncVar(SUM) which seems to do no harm - } - validateNumArgs(args.length, fm); + if (namePtg != null) { + throw new IllegalStateException("NamePtg no applicable to internal functions"); + } + boolean isVarArgs = !fm.hasFixedArgsLength(); + int funcIx = fm.getIndex(); + if (funcIx == FunctionMetadataRegistry.FUNCTION_INDEX_SUM && args.length == 1) { + // Excel encodes the sum of a single argument as tAttrSum + // POI does the same for consistency, but this is not critical + return new ParseNode(AttrPtg.getSumSingle(), args); + // The code below would encode tFuncVar(SUM) which seems to do no harm + } + validateNumArgs(args.length, fm); - AbstractFunctionPtg retval; - if(isVarArgs) { - retval = FuncVarPtg.create(name, numArgs); - } else { - retval = FuncPtg.create(funcIx); - } - return new ParseNode(retval, args); - } + AbstractFunctionPtg retval; + if(isVarArgs) { + retval = FuncVarPtg.create(name, numArgs); + } else { + retval = FuncPtg.create(funcIx); + } + return new ParseNode(retval, args); + } - private void validateNumArgs(int numArgs, FunctionMetadata fm) { - if(numArgs < fm.getMinParams()) { - String msg = "Too few arguments to function '" + fm.getName() + "'. "; - if(fm.hasFixedArgsLength()) { - msg += "Expected " + fm.getMinParams(); - } else { - msg += "At least " + fm.getMinParams() + " were expected"; - } - msg += " but got " + numArgs + "."; - throw new FormulaParseException(msg); - } - //the maximum number of arguments depends on the Excel version - int maxArgs; - if (fm.hasUnlimitedVarags()) { - if(_book != null) { - maxArgs = _book.getSpreadsheetVersion().getMaxFunctionArgs(); - } else { - //_book can be omitted by test cases - maxArgs = fm.getMaxParams(); // just use BIFF8 - } - } else { - maxArgs = fm.getMaxParams(); - } + private void validateNumArgs(int numArgs, FunctionMetadata fm) { + if(numArgs < fm.getMinParams()) { + String msg = "Too few arguments to function '" + fm.getName() + "'. "; + if(fm.hasFixedArgsLength()) { + msg += "Expected " + fm.getMinParams(); + } else { + msg += "At least " + fm.getMinParams() + " were expected"; + } + msg += " but got " + numArgs + "."; + throw new FormulaParseException(msg); + } + //the maximum number of arguments depends on the Excel version + int maxArgs; + if (fm.hasUnlimitedVarags()) { + if(_book != null) { + maxArgs = _book.getSpreadsheetVersion().getMaxFunctionArgs(); + } else { + //_book can be omitted by test cases + maxArgs = fm.getMaxParams(); // just use BIFF8 + } + } else { + maxArgs = fm.getMaxParams(); + } - if(numArgs > maxArgs) { - String msg = "Too many arguments to function '" + fm.getName() + "'. "; - if(fm.hasFixedArgsLength()) { - msg += "Expected " + maxArgs; - } else { - msg += "At most " + maxArgs + " were expected"; - } - msg += " but got " + numArgs + "."; - throw new FormulaParseException(msg); - } - } + if(numArgs > maxArgs) { + String msg = "Too many arguments to function '" + fm.getName() + "'. "; + if(fm.hasFixedArgsLength()) { + msg += "Expected " + maxArgs; + } else { + msg += "At most " + maxArgs + " were expected"; + } + msg += " but got " + numArgs + "."; + throw new FormulaParseException(msg); + } + } - private static boolean isArgumentDelimiter(char ch) { - return ch == ',' || ch == ')'; - } + private static boolean isArgumentDelimiter(char ch) { + return ch == ',' || ch == ')'; + } - /** get arguments to a function */ - private ParseNode[] Arguments() { - //average 2 args per function - List temp = new ArrayList(2); - SkipWhite(); - if(look == ')') { - return ParseNode.EMPTY_ARRAY; - } + /** get arguments to a function */ + private ParseNode[] Arguments() { + //average 2 args per function + List temp = new ArrayList(2); + SkipWhite(); + if(look == ')') { + return ParseNode.EMPTY_ARRAY; + } - boolean missedPrevArg = true; - while (true) { - SkipWhite(); - if (isArgumentDelimiter(look)) { - if (missedPrevArg) { - temp.add(new ParseNode(MissingArgPtg.instance)); - } - if (look == ')') { - break; - } - Match(','); - missedPrevArg = true; - continue; - } - temp.add(comparisonExpression()); - missedPrevArg = false; - SkipWhite(); - if (!isArgumentDelimiter(look)) { - throw expected("',' or ')'"); - } - } - ParseNode[] result = new ParseNode[temp.size()]; - temp.toArray(result); - return result; - } + boolean missedPrevArg = true; + while (true) { + SkipWhite(); + if (isArgumentDelimiter(look)) { + if (missedPrevArg) { + temp.add(new ParseNode(MissingArgPtg.instance)); + } + if (look == ')') { + break; + } + Match(','); + missedPrevArg = true; + continue; + } + temp.add(comparisonExpression()); + missedPrevArg = false; + SkipWhite(); + if (!isArgumentDelimiter(look)) { + throw expected("',' or ')'"); + } + } + ParseNode[] result = new ParseNode[temp.size()]; + temp.toArray(result); + return result; + } /** Parse and Translate a Math Factor */ - private ParseNode powerFactor() { - ParseNode result = percentFactor(); - while(true) { - SkipWhite(); - if(look != '^') { - return result; - } - Match('^'); - ParseNode other = percentFactor(); - result = new ParseNode(PowerPtg.instance, result, other); - } - } - - private ParseNode percentFactor() { - ParseNode result = parseSimpleFactor(); - while(true) { - SkipWhite(); - if(look != '%') { - return result; - } - Match('%'); - result = new ParseNode(PercentPtg.instance, result); - } - } - - - /** - * factors (without ^ or % ) - */ - private ParseNode parseSimpleFactor() { - SkipWhite(); - switch(look) { - case '#': - return new ParseNode(ErrPtg.valueOf(parseErrorLiteral())); - case '-': - Match('-'); - return parseUnary(false); - case '+': - Match('+'); - return parseUnary(true); - case '(': - Match('('); - ParseNode inside = unionExpression(); - Match(')'); - return new ParseNode(ParenthesisPtg.instance, inside); - case '"': - return new ParseNode(new StringPtg(parseStringLiteral())); - case '{': - Match('{'); - ParseNode arrayNode = parseArray(); - Match('}'); - return arrayNode; - } - if (IsAlpha(look) || Character.isDigit(look) || look == '\'' || look == '['){ - return parseRangeExpression(); - } - if (look == '.') { - return new ParseNode(parseNumber()); - } - throw expected("cell ref or constant literal"); - } - - - private ParseNode parseUnary(boolean isPlus) { - - boolean numberFollows = IsDigit(look) || look=='.'; - ParseNode factor = powerFactor(); - - if (numberFollows) { - // + or - directly next to a number is parsed with the number - - Ptg token = factor.getToken(); - if (token instanceof NumberPtg) { - if (isPlus) { - return factor; - } - token = new NumberPtg(-((NumberPtg)token).getValue()); - return new ParseNode(token); - } - if (token instanceof IntPtg) { - if (isPlus) { - return factor; - } - int intVal = ((IntPtg)token).getValue(); - // note - cannot use IntPtg for negatives - token = new NumberPtg(-intVal); - return new ParseNode(token); - } - } - return new ParseNode(isPlus ? UnaryPlusPtg.instance : UnaryMinusPtg.instance, factor); - } - - private ParseNode parseArray() { - List rowsData = new ArrayList(); - while(true) { - Object[] singleRowData = parseArrayRow(); - rowsData.add(singleRowData); - if (look == '}') { - break; - } - if (look != ';') { - throw expected("'}' or ';'"); - } - Match(';'); - } - int nRows = rowsData.size(); - Object[][] values2d = new Object[nRows][]; - rowsData.toArray(values2d); - int nColumns = values2d[0].length; - checkRowLengths(values2d, nColumns); - - return new ParseNode(new ArrayPtg(values2d)); - } - private void checkRowLengths(Object[][] values2d, int nColumns) { - for (int i = 0; i < values2d.length; i++) { - int rowLen = values2d[i].length; - if (rowLen != nColumns) { - throw new FormulaParseException("Array row " + i + " has length " + rowLen - + " but row 0 has length " + nColumns); - } - } - } - - private Object[] parseArrayRow() { - List temp = new ArrayList(); - while (true) { - temp.add(parseArrayItem()); - SkipWhite(); - switch(look) { - case '}': - case ';': - break; - case ',': - Match(','); - continue; - default: - throw expected("'}' or ','"); - - } - break; - } - - Object[] result = new Object[temp.size()]; - temp.toArray(result); - return result; - } - - private Object parseArrayItem() { - SkipWhite(); - switch(look) { - case '"': return parseStringLiteral(); - case '#': return ErrorConstant.valueOf(parseErrorLiteral()); - case 'F': case 'f': - case 'T': case 't': - return parseBooleanLiteral(); - case '-': - Match('-'); - SkipWhite(); - return convertArrayNumber(parseNumber(), false); - } - // else assume number - return convertArrayNumber(parseNumber(), true); - } - - private Boolean parseBooleanLiteral() { - String iden = parseUnquotedIdentifier(); - if ("TRUE".equalsIgnoreCase(iden)) { - return Boolean.TRUE; - } - if ("FALSE".equalsIgnoreCase(iden)) { - return Boolean.FALSE; - } - throw expected("'TRUE' or 'FALSE'"); - } - - private static Double convertArrayNumber(Ptg ptg, boolean isPositive) { - double value; - if (ptg instanceof IntPtg) { - value = ((IntPtg)ptg).getValue(); - } else if (ptg instanceof NumberPtg) { - value = ((NumberPtg)ptg).getValue(); - } else { - throw new RuntimeException("Unexpected ptg (" + ptg.getClass().getName() + ")"); - } - if (!isPositive) { - value = -value; - } - return new Double(value); - } - - private Ptg parseNumber() { - String number2 = null; - String exponent = null; - String number1 = GetNum(); - - if (look == '.') { - GetChar(); - number2 = GetNum(); - } - - if (look == 'E') { - GetChar(); - - String sign = ""; - if (look == '+') { - GetChar(); - } else if (look == '-') { - GetChar(); - sign = "-"; - } - - String number = GetNum(); - if (number == null) { - throw expected("Integer"); - } - exponent = sign + number; - } - - if (number1 == null && number2 == null) { - throw expected("Integer"); - } - - return getNumberPtgFromString(number1, number2, exponent); - } - - - private int parseErrorLiteral() { - Match('#'); - String part1 = parseUnquotedIdentifier().toUpperCase(Locale.ROOT); - if (part1 == null) { - throw expected("remainder of error constant literal"); - } - - switch(part1.charAt(0)) { - case 'V': { - FormulaError fe = FormulaError.VALUE; - if(part1.equals(fe.name())) { - Match('!'); - return fe.getCode(); - } - throw expected(fe.getString()); - } - case 'R': { - FormulaError fe = FormulaError.REF; - if(part1.equals(fe.name())) { - Match('!'); - return fe.getCode(); - } - throw expected(fe.getString()); + private ParseNode powerFactor() { + ParseNode result = percentFactor(); + while(true) { + SkipWhite(); + if(look != '^') { + return result; } - case 'D': { - FormulaError fe = FormulaError.DIV0; - if(part1.equals("DIV")) { - Match('/'); - Match('0'); - Match('!'); - return fe.getCode(); - } - throw expected(fe.getString()); - } - case 'N': { - FormulaError fe = FormulaError.NAME; - if(part1.equals(fe.name())) { - // only one that ends in '?' - Match('?'); - return fe.getCode(); - } - fe = FormulaError.NUM; - if(part1.equals(fe.name())) { - Match('!'); - return fe.getCode(); - } - fe = FormulaError.NULL; - if(part1.equals(fe.name())) { - Match('!'); - return fe.getCode(); - } - fe = FormulaError.NA; - if(part1.equals("N")) { - Match('/'); - if(look != 'A' && look != 'a') { - throw expected(fe.getString()); - } - Match(look); - // Note - no '!' or '?' suffix - return fe.getCode(); - } - throw expected("#NAME?, #NUM!, #NULL! or #N/A"); - } - } - throw expected("#VALUE!, #REF!, #DIV/0!, #NAME?, #NUM!, #NULL! or #N/A"); - } + Match('^'); + ParseNode other = percentFactor(); + result = new ParseNode(PowerPtg.instance, result, other); + } + } - private String parseUnquotedIdentifier() { - if (look == '\'') { - throw expected("unquoted identifier"); - } - StringBuilder sb = new StringBuilder(); - while (Character.isLetterOrDigit(look) || look == '.') { - sb.append(look); - GetChar(); - } - if (sb.length() < 1) { - return null; - } - - return sb.toString(); - } - - /** - * Get a PTG for an integer from its string representation. - * return Int or Number Ptg based on size of input - */ - private static Ptg getNumberPtgFromString(String number1, String number2, String exponent) { - StringBuffer number = new StringBuffer(); - - if (number2 == null) { - number.append(number1); - - if (exponent != null) { - number.append('E'); - number.append(exponent); - } - - String numberStr = number.toString(); - int intVal; - try { - intVal = Integer.parseInt(numberStr); - } catch (NumberFormatException e) { - return new NumberPtg(numberStr); - } - if (IntPtg.isInRange(intVal)) { - return new IntPtg(intVal); - } - return new NumberPtg(numberStr); - } - - if (number1 != null) { - number.append(number1); - } - - number.append('.'); - number.append(number2); - - if (exponent != null) { - number.append('E'); - number.append(exponent); - } - - return new NumberPtg(number.toString()); - } + private ParseNode percentFactor() { + ParseNode result = parseSimpleFactor(); + while(true) { + SkipWhite(); + if(look != '%') { + return result; + } + Match('%'); + result = new ParseNode(PercentPtg.instance, result); + } + } - private String parseStringLiteral() { - Match('"'); + /** + * factors (without ^ or % ) + */ + private ParseNode parseSimpleFactor() { + SkipWhite(); + switch(look) { + case '#': + return new ParseNode(ErrPtg.valueOf(parseErrorLiteral())); + case '-': + Match('-'); + return parseUnary(false); + case '+': + Match('+'); + return parseUnary(true); + case '(': + Match('('); + ParseNode inside = unionExpression(); + Match(')'); + return new ParseNode(ParenthesisPtg.instance, inside); + case '"': + return new ParseNode(new StringPtg(parseStringLiteral())); + case '{': + Match('{'); + ParseNode arrayNode = parseArray(); + Match('}'); + return arrayNode; + } + // named ranges and tables can start with underscore or backslash + // see https://support.office.com/en-us/article/Define-and-use-names-in-formulas-4d0f13ac-53b7-422e-afd2-abd7ff379c64?ui=en-US&rs=en-US&ad=US#bmsyntax_rules_for_names + if (IsAlpha(look) || Character.isDigit(look) || look == '\'' || look == '[' || look == '_' || look == '\\' ) { + return parseRangeExpression(); + } + if (look == '.') { + return new ParseNode(parseNumber()); + } + throw expected("cell ref or constant literal"); + } - StringBuffer token = new StringBuffer(); - while (true) { - if (look == '"') { - GetChar(); - if (look != '"') { - break; - } - } - token.append(look); - GetChar(); - } - return token.toString(); - } - /** Parse and Translate a Math Term */ - private ParseNode Term() { - ParseNode result = powerFactor(); - while(true) { - SkipWhite(); - Ptg operator; - switch(look) { - case '*': - Match('*'); - operator = MultiplyPtg.instance; - break; - case '/': - Match('/'); - operator = DividePtg.instance; - break; - default: - return result; // finished with Term - } - ParseNode other = powerFactor(); - result = new ParseNode(operator, result, other); - } - } + private ParseNode parseUnary(boolean isPlus) { - private ParseNode unionExpression() { - ParseNode result = intersectionExpression(); - boolean hasUnions = false; - while (true) { - SkipWhite(); - switch(look) { - case ',': - GetChar(); - hasUnions = true; - ParseNode other = intersectionExpression(); - result = new ParseNode(UnionPtg.instance, result, other); - continue; - } - if (hasUnions) { - return augmentWithMemPtg(result); - } - return result; - } - } + boolean numberFollows = IsDigit(look) || look=='.'; + ParseNode factor = powerFactor(); + + if (numberFollows) { + // + or - directly next to a number is parsed with the number + + Ptg token = factor.getToken(); + if (token instanceof NumberPtg) { + if (isPlus) { + return factor; + } + token = new NumberPtg(-((NumberPtg)token).getValue()); + return new ParseNode(token); + } + if (token instanceof IntPtg) { + if (isPlus) { + return factor; + } + int intVal = ((IntPtg)token).getValue(); + // note - cannot use IntPtg for negatives + token = new NumberPtg(-intVal); + return new ParseNode(token); + } + } + return new ParseNode(isPlus ? UnaryPlusPtg.instance : UnaryMinusPtg.instance, factor); + } + + private ParseNode parseArray() { + List rowsData = new ArrayList(); + while(true) { + Object[] singleRowData = parseArrayRow(); + rowsData.add(singleRowData); + if (look == '}') { + break; + } + if (look != ';') { + throw expected("'}' or ';'"); + } + Match(';'); + } + int nRows = rowsData.size(); + Object[][] values2d = new Object[nRows][]; + rowsData.toArray(values2d); + int nColumns = values2d[0].length; + checkRowLengths(values2d, nColumns); + + return new ParseNode(new ArrayPtg(values2d)); + } + private void checkRowLengths(Object[][] values2d, int nColumns) { + for (int i = 0; i < values2d.length; i++) { + int rowLen = values2d[i].length; + if (rowLen != nColumns) { + throw new FormulaParseException("Array row " + i + " has length " + rowLen + + " but row 0 has length " + nColumns); + } + } + } + + private Object[] parseArrayRow() { + List temp = new ArrayList(); + while (true) { + temp.add(parseArrayItem()); + SkipWhite(); + switch(look) { + case '}': + case ';': + break; + case ',': + Match(','); + continue; + default: + throw expected("'}' or ','"); + + } + break; + } + + Object[] result = new Object[temp.size()]; + temp.toArray(result); + return result; + } + + private Object parseArrayItem() { + SkipWhite(); + switch(look) { + case '"': return parseStringLiteral(); + case '#': return ErrorConstant.valueOf(parseErrorLiteral()); + case 'F': case 'f': + case 'T': case 't': + return parseBooleanLiteral(); + case '-': + Match('-'); + SkipWhite(); + return convertArrayNumber(parseNumber(), false); + } + // else assume number + return convertArrayNumber(parseNumber(), true); + } + + private Boolean parseBooleanLiteral() { + String iden = parseUnquotedIdentifier(); + if ("TRUE".equalsIgnoreCase(iden)) { + return Boolean.TRUE; + } + if ("FALSE".equalsIgnoreCase(iden)) { + return Boolean.FALSE; + } + throw expected("'TRUE' or 'FALSE'"); + } + + private static Double convertArrayNumber(Ptg ptg, boolean isPositive) { + double value; + if (ptg instanceof IntPtg) { + value = ((IntPtg)ptg).getValue(); + } else if (ptg instanceof NumberPtg) { + value = ((NumberPtg)ptg).getValue(); + } else { + throw new RuntimeException("Unexpected ptg (" + ptg.getClass().getName() + ")"); + } + if (!isPositive) { + value = -value; + } + return new Double(value); + } + + private Ptg parseNumber() { + String number2 = null; + String exponent = null; + String number1 = GetNum(); + + if (look == '.') { + GetChar(); + number2 = GetNum(); + } + + if (look == 'E') { + GetChar(); + + String sign = ""; + if (look == '+') { + GetChar(); + } else if (look == '-') { + GetChar(); + sign = "-"; + } + + String number = GetNum(); + if (number == null) { + throw expected("Integer"); + } + exponent = sign + number; + } + + if (number1 == null && number2 == null) { + throw expected("Integer"); + } + + return getNumberPtgFromString(number1, number2, exponent); + } + + + private int parseErrorLiteral() { + Match('#'); + String part1 = parseUnquotedIdentifier().toUpperCase(Locale.ROOT); + if (part1 == null) { + throw expected("remainder of error constant literal"); + } + + switch(part1.charAt(0)) { + case 'V': { + FormulaError fe = FormulaError.VALUE; + if(part1.equals(fe.name())) { + Match('!'); + return fe.getCode(); + } + throw expected(fe.getString()); + } + case 'R': { + FormulaError fe = FormulaError.REF; + if(part1.equals(fe.name())) { + Match('!'); + return fe.getCode(); + } + throw expected(fe.getString()); + } + case 'D': { + FormulaError fe = FormulaError.DIV0; + if(part1.equals("DIV")) { + Match('/'); + Match('0'); + Match('!'); + return fe.getCode(); + } + throw expected(fe.getString()); + } + case 'N': { + FormulaError fe = FormulaError.NAME; + if(part1.equals(fe.name())) { + // only one that ends in '?' + Match('?'); + return fe.getCode(); + } + fe = FormulaError.NUM; + if(part1.equals(fe.name())) { + Match('!'); + return fe.getCode(); + } + fe = FormulaError.NULL; + if(part1.equals(fe.name())) { + Match('!'); + return fe.getCode(); + } + fe = FormulaError.NA; + if(part1.equals("N")) { + Match('/'); + if(look != 'A' && look != 'a') { + throw expected(fe.getString()); + } + Match(look); + // Note - no '!' or '?' suffix + return fe.getCode(); + } + throw expected("#NAME?, #NUM!, #NULL! or #N/A"); + } + } + throw expected("#VALUE!, #REF!, #DIV/0!, #NAME?, #NUM!, #NULL! or #N/A"); + } + + private String parseUnquotedIdentifier() { + if (look == '\'') { + throw expected("unquoted identifier"); + } + StringBuilder sb = new StringBuilder(); + while (Character.isLetterOrDigit(look) || look == '.') { + sb.append(look); + GetChar(); + } + if (sb.length() < 1) { + return null; + } + + return sb.toString(); + } + + /** + * Get a PTG for an integer from its string representation. + * return Int or Number Ptg based on size of input + */ + private static Ptg getNumberPtgFromString(String number1, String number2, String exponent) { + StringBuffer number = new StringBuffer(); + + if (number2 == null) { + number.append(number1); + + if (exponent != null) { + number.append('E'); + number.append(exponent); + } + + String numberStr = number.toString(); + int intVal; + try { + intVal = Integer.parseInt(numberStr); + } catch (NumberFormatException e) { + return new NumberPtg(numberStr); + } + if (IntPtg.isInRange(intVal)) { + return new IntPtg(intVal); + } + return new NumberPtg(numberStr); + } + + if (number1 != null) { + number.append(number1); + } + + number.append('.'); + number.append(number2); + + if (exponent != null) { + number.append('E'); + number.append(exponent); + } + + return new NumberPtg(number.toString()); + } + + + private String parseStringLiteral() { + Match('"'); + + StringBuffer token = new StringBuffer(); + while (true) { + if (look == '"') { + GetChar(); + if (look != '"') { + break; + } + } + token.append(look); + GetChar(); + } + return token.toString(); + } + + /** Parse and Translate a Math Term */ + private ParseNode Term() { + ParseNode result = powerFactor(); + while(true) { + SkipWhite(); + Ptg operator; + switch(look) { + case '*': + Match('*'); + operator = MultiplyPtg.instance; + break; + case '/': + Match('/'); + operator = DividePtg.instance; + break; + default: + return result; // finished with Term + } + ParseNode other = powerFactor(); + result = new ParseNode(operator, result, other); + } + } + + private ParseNode unionExpression() { + ParseNode result = intersectionExpression(); + boolean hasUnions = false; + while (true) { + SkipWhite(); + switch(look) { + case ',': + GetChar(); + hasUnions = true; + ParseNode other = intersectionExpression(); + result = new ParseNode(UnionPtg.instance, result, other); + continue; + } + if (hasUnions) { + return augmentWithMemPtg(result); + } + return result; + } + } private ParseNode intersectionExpression() { - ParseNode result = comparisonExpression(); - boolean hasIntersections = false; - while (true) { - SkipWhite(); - if (_inIntersection) { - int savePointer = _pointer; + ParseNode result = comparisonExpression(); + boolean hasIntersections = false; + while (true) { + SkipWhite(); + if (_inIntersection) { + int savePointer = _pointer; - // Don't getChar() as the space has already been eaten and recorded by SkipWhite(). - try { - ParseNode other = comparisonExpression(); - result = new ParseNode(IntersectionPtg.instance, result, other); - hasIntersections = true; - continue; - } catch (FormulaParseException e) { - // if parsing for intersection fails we assume that we actually had an arbitrary - // whitespace and thus should simply skip this whitespace - resetPointer(savePointer); - } - } - if (hasIntersections) { - return augmentWithMemPtg(result); - } - return result; - } - } - - private ParseNode comparisonExpression() { - ParseNode result = concatExpression(); - while (true) { - SkipWhite(); - switch(look) { - case '=': - case '>': - case '<': - Ptg comparisonToken = getComparisonToken(); - ParseNode other = concatExpression(); - result = new ParseNode(comparisonToken, result, other); - continue; - } - return result; // finished with predicate expression - } - } + // Don't getChar() as the space has already been eaten and recorded by SkipWhite(). + try { + ParseNode other = comparisonExpression(); + result = new ParseNode(IntersectionPtg.instance, result, other); + hasIntersections = true; + continue; + } catch (FormulaParseException e) { + // if parsing for intersection fails we assume that we actually had an arbitrary + // whitespace and thus should simply skip this whitespace + resetPointer(savePointer); + } + } + if (hasIntersections) { + return augmentWithMemPtg(result); + } + return result; + } + } + + private ParseNode comparisonExpression() { + ParseNode result = concatExpression(); + while (true) { + SkipWhite(); + switch(look) { + case '=': + case '>': + case '<': + Ptg comparisonToken = getComparisonToken(); + ParseNode other = concatExpression(); + result = new ParseNode(comparisonToken, result, other); + continue; + } + return result; // finished with predicate expression + } + } - private Ptg getComparisonToken() { - if(look == '=') { - Match(look); - return EqualPtg.instance; - } - boolean isGreater = look == '>'; - Match(look); - if(isGreater) { - if(look == '=') { - Match('='); - return GreaterEqualPtg.instance; - } - return GreaterThanPtg.instance; - } - switch(look) { - case '=': - Match('='); - return LessEqualPtg.instance; - case '>': - Match('>'); - return NotEqualPtg.instance; - } - return LessThanPtg.instance; - } + private Ptg getComparisonToken() { + if(look == '=') { + Match(look); + return EqualPtg.instance; + } + boolean isGreater = look == '>'; + Match(look); + if(isGreater) { + if(look == '=') { + Match('='); + return GreaterEqualPtg.instance; + } + return GreaterThanPtg.instance; + } + switch(look) { + case '=': + Match('='); + return LessEqualPtg.instance; + case '>': + Match('>'); + return NotEqualPtg.instance; + } + return LessThanPtg.instance; + } - private ParseNode concatExpression() { - ParseNode result = additiveExpression(); - while (true) { - SkipWhite(); - if(look != '&') { - break; // finished with concat expression - } - Match('&'); - ParseNode other = additiveExpression(); - result = new ParseNode(ConcatPtg.instance, result, other); - } - return result; - } + private ParseNode concatExpression() { + ParseNode result = additiveExpression(); + while (true) { + SkipWhite(); + if(look != '&') { + break; // finished with concat expression + } + Match('&'); + ParseNode other = additiveExpression(); + result = new ParseNode(ConcatPtg.instance, result, other); + } + return result; + } - /** Parse and Translate an Expression */ - private ParseNode additiveExpression() { - ParseNode result = Term(); - while (true) { - SkipWhite(); - Ptg operator; - switch(look) { - case '+': - Match('+'); - operator = AddPtg.instance; - break; - case '-': - Match('-'); - operator = SubtractPtg.instance; - break; - default: - return result; // finished with additive expression - } - ParseNode other = Term(); - result = new ParseNode(operator, result, other); - } - } + /** Parse and Translate an Expression */ + private ParseNode additiveExpression() { + ParseNode result = Term(); + while (true) { + SkipWhite(); + Ptg operator; + switch(look) { + case '+': + Match('+'); + operator = AddPtg.instance; + break; + case '-': + Match('-'); + operator = SubtractPtg.instance; + break; + default: + return result; // finished with additive expression + } + ParseNode other = Term(); + result = new ParseNode(operator, result, other); + } + } - //{--------------------------------------------------------------} - //{ Parse and Translate an Assignment Statement } - /** + //{--------------------------------------------------------------} + //{ Parse and Translate an Assignment Statement } + /** procedure Assignment; var Name: string[8]; begin @@ -1654,29 +1984,29 @@ begin Expression; end; - **/ + **/ - /** - * API call to execute the parsing of the formula - * - */ - private void parse() { - _pointer=0; - GetChar(); - _rootNode = unionExpression(); + /** + * API call to execute the parsing of the formula + * + */ + private void parse() { + _pointer=0; + GetChar(); + _rootNode = unionExpression(); - if(_pointer <= _formulaLength) { - String msg = "Unused input [" + _formulaString.substring(_pointer-1) - + "] after attempting to parse the formula [" + _formulaString + "]"; - throw new FormulaParseException(msg); - } - } + if(_pointer <= _formulaLength) { + String msg = "Unused input [" + _formulaString.substring(_pointer-1) + + "] after attempting to parse the formula [" + _formulaString + "]"; + throw new FormulaParseException(msg); + } + } - private Ptg[] getRPNPtg(int formulaType) { - OperandClassTransformer oct = new OperandClassTransformer(formulaType); - // RVA is for 'operand class': 'reference', 'value', 'array' - oct.transformFormula(_rootNode); - return ParseNode.toTokenArray(_rootNode); - } + private Ptg[] getRPNPtg(int formulaType) { + OperandClassTransformer oct = new OperandClassTransformer(formulaType); + // RVA is for 'operand class': 'reference', 'value', 'array' + oct.transformFormula(_rootNode); + return ParseNode.toTokenArray(_rootNode); + } } diff --git a/src/java/org/apache/poi/ss/formula/FormulaParsingWorkbook.java b/src/java/org/apache/poi/ss/formula/FormulaParsingWorkbook.java index fe907fbb1..7ddcc944c 100644 --- a/src/java/org/apache/poi/ss/formula/FormulaParsingWorkbook.java +++ b/src/java/org/apache/poi/ss/formula/FormulaParsingWorkbook.java @@ -20,6 +20,7 @@ package org.apache.poi.ss.formula; import org.apache.poi.ss.SpreadsheetVersion; import org.apache.poi.ss.formula.ptg.Ptg; 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.CellReference; @@ -31,46 +32,51 @@ import org.apache.poi.ss.util.CellReference; * @author Josh Micich */ public interface FormulaParsingWorkbook { - /** - * named range name matching is case insensitive - */ - EvaluationName getName(String name, int sheetIndex); - - /** - * Return the underlying workbook - */ - Name createName(); + /** + * named range name matching is case insensitive + */ + EvaluationName getName(String name, int sheetIndex); + + /** + * Return the underlying workbook + */ + Name createName(); - /** - * Return an external name (named range, function, user-defined function) Ptg - */ - Ptg getNameXPtg(String name, SheetIdentifier sheet); - - /** - * Produce the appropriate Ptg for a 3d cell reference - */ - Ptg get3DReferencePtg(CellReference cell, SheetIdentifier sheet); + /** + * XSSF Only - gets a table that exists in the worksheet + */ + Table getTable(String name); + + /** + * Return an external name (named range, function, user-defined function) Ptg + */ + 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 */ Ptg get3DReferencePtg(AreaReference area, SheetIdentifier sheet); - /** - * gets the externSheet index for a sheet from this workbook - */ - int getExternalSheetIndex(String sheetName); - /** - * gets the externSheet index for a sheet from an external workbook - * @param workbookName e.g. "Budget.xls" - * @param sheetName a name of a sheet in that workbook - */ - int getExternalSheetIndex(String workbookName, String sheetName); + /** + * gets the externSheet index for a sheet from this workbook + */ + int getExternalSheetIndex(String sheetName); + /** + * gets the externSheet index for a sheet from an external workbook + * @param workbookName e.g. "Budget.xls" + * @param sheetName a name of a sheet in that workbook + */ + int getExternalSheetIndex(String workbookName, String sheetName); - /** - * Returns an enum holding spreadhseet properties specific to an Excel version ( - * max column and row numbers, max arguments to a function, etc.) - */ - SpreadsheetVersion getSpreadsheetVersion(); + /** + * Returns an enum holding spreadhseet properties specific to an Excel version ( + * max column and row numbers, max arguments to a function, etc.) + */ + SpreadsheetVersion getSpreadsheetVersion(); } diff --git a/src/java/org/apache/poi/ss/formula/OperationEvaluationContext.java b/src/java/org/apache/poi/ss/formula/OperationEvaluationContext.java index cc590772d..d7345f7cb 100644 --- a/src/java/org/apache/poi/ss/formula/OperationEvaluationContext.java +++ b/src/java/org/apache/poi/ss/formula/OperationEvaluationContext.java @@ -46,199 +46,199 @@ import org.apache.poi.ss.util.CellReference.NameType; * For POI internal use only */ public final class OperationEvaluationContext { - public static final FreeRefFunction UDF = UserDefinedFunction.instance; - private final EvaluationWorkbook _workbook; - private final int _sheetIndex; - private final int _rowIndex; - private final int _columnIndex; - private final EvaluationTracker _tracker; - private final WorkbookEvaluator _bookEvaluator; + public static final FreeRefFunction UDF = UserDefinedFunction.instance; + private final EvaluationWorkbook _workbook; + private final int _sheetIndex; + private final int _rowIndex; + private final int _columnIndex; + private final EvaluationTracker _tracker; + private final WorkbookEvaluator _bookEvaluator; - public OperationEvaluationContext(WorkbookEvaluator bookEvaluator, EvaluationWorkbook workbook, int sheetIndex, int srcRowNum, - int srcColNum, EvaluationTracker tracker) { - _bookEvaluator = bookEvaluator; - _workbook = workbook; - _sheetIndex = sheetIndex; - _rowIndex = srcRowNum; - _columnIndex = srcColNum; - _tracker = tracker; - } + public OperationEvaluationContext(WorkbookEvaluator bookEvaluator, EvaluationWorkbook workbook, int sheetIndex, int srcRowNum, + int srcColNum, EvaluationTracker tracker) { + _bookEvaluator = bookEvaluator; + _workbook = workbook; + _sheetIndex = sheetIndex; + _rowIndex = srcRowNum; + _columnIndex = srcColNum; + _tracker = tracker; + } - public EvaluationWorkbook getWorkbook() { - return _workbook; - } + public EvaluationWorkbook getWorkbook() { + return _workbook; + } - public int getRowIndex() { - return _rowIndex; - } + public int getRowIndex() { + return _rowIndex; + } - public int getColumnIndex() { - return _columnIndex; - } + public int getColumnIndex() { + return _columnIndex; + } - SheetRangeEvaluator createExternSheetRefEvaluator(ExternSheetReferenceToken ptg) { - return createExternSheetRefEvaluator(ptg.getExternSheetIndex()); - } - SheetRangeEvaluator createExternSheetRefEvaluator(String firstSheetName, String lastSheetName, int externalWorkbookNumber) { + SheetRangeEvaluator createExternSheetRefEvaluator(ExternSheetReferenceToken ptg) { + return createExternSheetRefEvaluator(ptg.getExternSheetIndex()); + } + SheetRangeEvaluator createExternSheetRefEvaluator(String firstSheetName, String lastSheetName, int externalWorkbookNumber) { ExternalSheet externalSheet = _workbook.getExternalSheet(firstSheetName, lastSheetName, externalWorkbookNumber); return createExternSheetRefEvaluator(externalSheet); } - SheetRangeEvaluator createExternSheetRefEvaluator(int externSheetIndex) { - ExternalSheet externalSheet = _workbook.getExternalSheet(externSheetIndex); + SheetRangeEvaluator createExternSheetRefEvaluator(int externSheetIndex) { + ExternalSheet externalSheet = _workbook.getExternalSheet(externSheetIndex); return createExternSheetRefEvaluator(externalSheet); - } - SheetRangeEvaluator createExternSheetRefEvaluator(ExternalSheet externalSheet) { - WorkbookEvaluator targetEvaluator; - int otherFirstSheetIndex; - int otherLastSheetIndex = -1; - if (externalSheet == null || externalSheet.getWorkbookName() == null) { - // sheet is in same workbook - targetEvaluator = _bookEvaluator; - if(externalSheet == null) { - otherFirstSheetIndex = 0; - } else { - otherFirstSheetIndex = _workbook.getSheetIndex(externalSheet.getSheetName()); - } - - if (externalSheet instanceof ExternalSheetRange) { - String lastSheetName = ((ExternalSheetRange)externalSheet).getLastSheetName(); - otherLastSheetIndex = _workbook.getSheetIndex(lastSheetName); - } - } else { - // look up sheet by name from external workbook - String workbookName = externalSheet.getWorkbookName(); - try { - targetEvaluator = _bookEvaluator.getOtherWorkbookEvaluator(workbookName); - } catch (WorkbookNotFoundException e) { - throw new RuntimeException(e.getMessage(), e); - } - - otherFirstSheetIndex = targetEvaluator.getSheetIndex(externalSheet.getSheetName()); + } + SheetRangeEvaluator createExternSheetRefEvaluator(ExternalSheet externalSheet) { + WorkbookEvaluator targetEvaluator; + int otherFirstSheetIndex; + int otherLastSheetIndex = -1; + if (externalSheet == null || externalSheet.getWorkbookName() == null) { + // sheet is in same workbook + targetEvaluator = _bookEvaluator; + if(externalSheet == null) { + otherFirstSheetIndex = 0; + } else { + otherFirstSheetIndex = _workbook.getSheetIndex(externalSheet.getSheetName()); + } + + if (externalSheet instanceof ExternalSheetRange) { + String lastSheetName = ((ExternalSheetRange)externalSheet).getLastSheetName(); + otherLastSheetIndex = _workbook.getSheetIndex(lastSheetName); + } + } else { + // look up sheet by name from external workbook + String workbookName = externalSheet.getWorkbookName(); + try { + targetEvaluator = _bookEvaluator.getOtherWorkbookEvaluator(workbookName); + } catch (WorkbookNotFoundException e) { + throw new RuntimeException(e.getMessage(), e); + } + + otherFirstSheetIndex = targetEvaluator.getSheetIndex(externalSheet.getSheetName()); if (externalSheet instanceof ExternalSheetRange) { String lastSheetName = ((ExternalSheetRange)externalSheet).getLastSheetName(); otherLastSheetIndex = targetEvaluator.getSheetIndex(lastSheetName); } - - if (otherFirstSheetIndex < 0) { - throw new RuntimeException("Invalid sheet name '" + externalSheet.getSheetName() - + "' in bool '" + workbookName + "'."); - } - } - - if (otherLastSheetIndex == -1) { - // Reference to just one sheet - otherLastSheetIndex = otherFirstSheetIndex; - } - - SheetRefEvaluator[] evals = new SheetRefEvaluator[otherLastSheetIndex-otherFirstSheetIndex+1]; - for (int i=0; inull if either workbook or sheet is not found - */ - private SheetRefEvaluator createExternSheetRefEvaluator(String workbookName, String sheetName) { - WorkbookEvaluator targetEvaluator; - if (workbookName == null) { - targetEvaluator = _bookEvaluator; - } else { - if (sheetName == null) { - throw new IllegalArgumentException("sheetName must not be null if workbookName is provided"); - } - try { - targetEvaluator = _bookEvaluator.getOtherWorkbookEvaluator(workbookName); - } catch (WorkbookNotFoundException e) { - return null; - } - } - int otherSheetIndex = sheetName == null ? _sheetIndex : targetEvaluator.getSheetIndex(sheetName); - if (otherSheetIndex < 0) { - return null; - } - return new SheetRefEvaluator(targetEvaluator, _tracker, otherSheetIndex); - } + /** + * @return null if either workbook or sheet is not found + */ + private SheetRefEvaluator createExternSheetRefEvaluator(String workbookName, String sheetName) { + WorkbookEvaluator targetEvaluator; + if (workbookName == null) { + targetEvaluator = _bookEvaluator; + } else { + if (sheetName == null) { + throw new IllegalArgumentException("sheetName must not be null if workbookName is provided"); + } + try { + targetEvaluator = _bookEvaluator.getOtherWorkbookEvaluator(workbookName); + } catch (WorkbookNotFoundException e) { + return null; + } + } + int otherSheetIndex = sheetName == null ? _sheetIndex : targetEvaluator.getSheetIndex(sheetName); + if (otherSheetIndex < 0) { + return null; + } + return new SheetRefEvaluator(targetEvaluator, _tracker, otherSheetIndex); + } - public SheetRangeEvaluator getRefEvaluatorForCurrentSheet() { - SheetRefEvaluator sre = new SheetRefEvaluator(_bookEvaluator, _tracker, _sheetIndex); - return new SheetRangeEvaluator(_sheetIndex, sre); - } + public SheetRangeEvaluator getRefEvaluatorForCurrentSheet() { + SheetRefEvaluator sre = new SheetRefEvaluator(_bookEvaluator, _tracker, _sheetIndex); + return new SheetRangeEvaluator(_sheetIndex, sre); + } - /** - * Resolves a cell or area reference dynamically. - * @param workbookName the name of the workbook containing the reference. If null - * the current workbook is assumed. Note - to evaluate formulas which use multiple workbooks, - * a {@link CollaboratingWorkbooksEnvironment} must be set up. - * @param sheetName the name of the sheet containing the reference. May be null - * (when workbookName is also null) in which case the current workbook and sheet is - * assumed. - * @param refStrPart1 the single cell reference or first part of the area reference. Must not - * be null. - * @param refStrPart2 the second part of the area reference. For single cell references this - * parameter must be null - * @param isA1Style specifies the format for refStrPart1 and refStrPart2. - * Pass true for 'A1' style and false for 'R1C1' style. - * TODO - currently POI only supports 'A1' reference style - * @return a {@link RefEval} or {@link AreaEval} - */ - public ValueEval getDynamicReference(String workbookName, String sheetName, String refStrPart1, - String refStrPart2, boolean isA1Style) { - if (!isA1Style) { - throw new RuntimeException("R1C1 style not supported yet"); - } - SheetRefEvaluator se = createExternSheetRefEvaluator(workbookName, sheetName); - if (se == null) { - return ErrorEval.REF_INVALID; - } - SheetRangeEvaluator sre = new SheetRangeEvaluator(_sheetIndex, se); - - // ugly typecast - TODO - make spreadsheet version more easily accessible - SpreadsheetVersion ssVersion = ((FormulaParsingWorkbook)_workbook).getSpreadsheetVersion(); + /** + * Resolves a cell or area reference dynamically. + * @param workbookName the name of the workbook containing the reference. If null + * the current workbook is assumed. Note - to evaluate formulas which use multiple workbooks, + * a {@link CollaboratingWorkbooksEnvironment} must be set up. + * @param sheetName the name of the sheet containing the reference. May be null + * (when workbookName is also null) in which case the current workbook and sheet is + * assumed. + * @param refStrPart1 the single cell reference or first part of the area reference. Must not + * be null. + * @param refStrPart2 the second part of the area reference. For single cell references this + * parameter must be null + * @param isA1Style specifies the format for refStrPart1 and refStrPart2. + * Pass true for 'A1' style and false for 'R1C1' style. + * TODO - currently POI only supports 'A1' reference style + * @return a {@link RefEval} or {@link AreaEval} + */ + public ValueEval getDynamicReference(String workbookName, String sheetName, String refStrPart1, + String refStrPart2, boolean isA1Style) { + if (!isA1Style) { + throw new RuntimeException("R1C1 style not supported yet"); + } + SheetRefEvaluator se = createExternSheetRefEvaluator(workbookName, sheetName); + if (se == null) { + return ErrorEval.REF_INVALID; + } + SheetRangeEvaluator sre = new SheetRangeEvaluator(_sheetIndex, se); + + // ugly typecast - TODO - make spreadsheet version more easily accessible + SpreadsheetVersion ssVersion = ((FormulaParsingWorkbook)_workbook).getSpreadsheetVersion(); - NameType part1refType = classifyCellReference(refStrPart1, ssVersion); - switch (part1refType) { - case BAD_CELL_OR_NAMED_RANGE: - return ErrorEval.REF_INVALID; - case NAMED_RANGE: + NameType part1refType = classifyCellReference(refStrPart1, ssVersion); + switch (part1refType) { + case BAD_CELL_OR_NAMED_RANGE: + return ErrorEval.REF_INVALID; + case NAMED_RANGE: EvaluationName nm = ((FormulaParsingWorkbook)_workbook).getName(refStrPart1, _sheetIndex); if(!nm.isRange()){ throw new RuntimeException("Specified name '" + refStrPart1 + "' is not a range as expected."); } return _bookEvaluator.evaluateNameFormula(nm.getNameDefinition(), this); - } - if (refStrPart2 == null) { - // no ':' - switch (part1refType) { - case COLUMN: - case ROW: - return ErrorEval.REF_INVALID; - case CELL: - CellReference cr = new CellReference(refStrPart1); - return new LazyRefEval(cr.getRow(), cr.getCol(), sre); - } - throw new IllegalStateException("Unexpected reference classification of '" + refStrPart1 + "'."); - } - NameType part2refType = classifyCellReference(refStrPart1, ssVersion); - switch (part2refType) { - case BAD_CELL_OR_NAMED_RANGE: - return ErrorEval.REF_INVALID; - case NAMED_RANGE: - throw new RuntimeException("Cannot evaluate '" + refStrPart1 - + "'. Indirect evaluation of defined names not supported yet"); - } + } + if (refStrPart2 == null) { + // no ':' + switch (part1refType) { + case COLUMN: + case ROW: + return ErrorEval.REF_INVALID; + case CELL: + CellReference cr = new CellReference(refStrPart1); + return new LazyRefEval(cr.getRow(), cr.getCol(), sre); + } + throw new IllegalStateException("Unexpected reference classification of '" + refStrPart1 + "'."); + } + NameType part2refType = classifyCellReference(refStrPart1, ssVersion); + switch (part2refType) { + case BAD_CELL_OR_NAMED_RANGE: + return ErrorEval.REF_INVALID; + case NAMED_RANGE: + throw new RuntimeException("Cannot evaluate '" + refStrPart1 + + "'. Indirect evaluation of defined names not supported yet"); + } - if (part2refType != part1refType) { - // LHS and RHS of ':' must be compatible - return ErrorEval.REF_INVALID; - } - int firstRow, firstCol, lastRow, lastCol; - switch (part1refType) { - case COLUMN: + if (part2refType != part1refType) { + // LHS and RHS of ':' must be compatible + return ErrorEval.REF_INVALID; + } + int firstRow, firstCol, lastRow, lastCol; + switch (part1refType) { + case COLUMN: firstRow =0; if (part2refType.equals(NameType.COLUMN)) { @@ -252,7 +252,7 @@ public final class OperationEvaluationContext { lastCol = parseColRef(refStrPart2); } break; - case ROW: + case ROW: // support of cell range in the form of integer:integer firstCol = 0; if (part2refType.equals(NameType.ROW)) @@ -265,61 +265,61 @@ public final class OperationEvaluationContext { firstRow = parseRowRef(refStrPart1); lastRow = parseRowRef(refStrPart2); } - break; - case CELL: - CellReference cr; - cr = new CellReference(refStrPart1); - firstRow = cr.getRow(); - firstCol = cr.getCol(); - cr = new CellReference(refStrPart2); - lastRow = cr.getRow(); - lastCol = cr.getCol(); - break; - default: - throw new IllegalStateException("Unexpected reference classification of '" + refStrPart1 + "'."); - } - return new LazyAreaEval(firstRow, firstCol, lastRow, lastCol, sre); - } + break; + case CELL: + CellReference cr; + cr = new CellReference(refStrPart1); + firstRow = cr.getRow(); + firstCol = cr.getCol(); + cr = new CellReference(refStrPart2); + lastRow = cr.getRow(); + lastCol = cr.getCol(); + break; + default: + throw new IllegalStateException("Unexpected reference classification of '" + refStrPart1 + "'."); + } + return new LazyAreaEval(firstRow, firstCol, lastRow, lastCol, sre); + } - private static int parseRowRef(String refStrPart) { - return CellReference.convertColStringToIndex(refStrPart); - } + private static int parseRowRef(String refStrPart) { + return CellReference.convertColStringToIndex(refStrPart); + } - private static int parseColRef(String refStrPart) { - return Integer.parseInt(refStrPart) - 1; - } + private static int parseColRef(String refStrPart) { + return Integer.parseInt(refStrPart) - 1; + } - private static NameType classifyCellReference(String str, SpreadsheetVersion ssVersion) { - int len = str.length(); - if (len < 1) { - return CellReference.NameType.BAD_CELL_OR_NAMED_RANGE; - } - return CellReference.classifyCellReference(str, ssVersion); - } + private static NameType classifyCellReference(String str, SpreadsheetVersion ssVersion) { + int len = str.length(); + if (len < 1) { + return CellReference.NameType.BAD_CELL_OR_NAMED_RANGE; + } + return CellReference.classifyCellReference(str, ssVersion); + } - public FreeRefFunction findUserDefinedFunction(String functionName) { - return _bookEvaluator.findUserDefinedFunction(functionName); - } + public FreeRefFunction findUserDefinedFunction(String functionName) { + return _bookEvaluator.findUserDefinedFunction(functionName); + } - public ValueEval getRefEval(int rowIndex, int columnIndex) { - SheetRangeEvaluator sre = getRefEvaluatorForCurrentSheet(); - return new LazyRefEval(rowIndex, columnIndex, sre); - } - public ValueEval getRef3DEval(Ref3DPtg rptg) { - SheetRangeEvaluator sre = createExternSheetRefEvaluator(rptg.getExternSheetIndex()); - return new LazyRefEval(rptg.getRow(), rptg.getColumn(), sre); - } + public ValueEval getRefEval(int rowIndex, int columnIndex) { + SheetRangeEvaluator sre = getRefEvaluatorForCurrentSheet(); + return new LazyRefEval(rowIndex, columnIndex, sre); + } + public ValueEval getRef3DEval(Ref3DPtg rptg) { + SheetRangeEvaluator sre = createExternSheetRefEvaluator(rptg.getExternSheetIndex()); + return new LazyRefEval(rptg.getRow(), rptg.getColumn(), sre); + } public ValueEval getRef3DEval(Ref3DPxg rptg) { SheetRangeEvaluator sre = createExternSheetRefEvaluator( rptg.getSheetName(), rptg.getLastSheetName(), rptg.getExternalWorkbookNumber()); return new LazyRefEval(rptg.getRow(), rptg.getColumn(), sre); } - public ValueEval getAreaEval(int firstRowIndex, int firstColumnIndex, - int lastRowIndex, int lastColumnIndex) { - SheetRangeEvaluator sre = getRefEvaluatorForCurrentSheet(); - return new LazyAreaEval(firstRowIndex, firstColumnIndex, lastRowIndex, lastColumnIndex, sre); - } + public ValueEval getAreaEval(int firstRowIndex, int firstColumnIndex, + int lastRowIndex, int lastColumnIndex) { + SheetRangeEvaluator sre = getRefEvaluatorForCurrentSheet(); + return new LazyAreaEval(firstRowIndex, firstColumnIndex, lastRowIndex, lastColumnIndex, sre); + } public ValueEval getArea3DEval(Area3DPtg aptg) { SheetRangeEvaluator sre = createExternSheetRefEvaluator(aptg.getExternSheetIndex()); return new LazyAreaEval(aptg.getFirstRow(), aptg.getFirstColumn(), @@ -348,8 +348,8 @@ public final class OperationEvaluationContext { ); return getExternalNameXEval(externName, workbookName); } - public ValueEval getNameXEval(NameXPxg nameXPxg) { - ExternalSheet externSheet = _workbook.getExternalSheet(nameXPxg.getSheetName(), null, nameXPxg.getExternalWorkbookNumber()); + public ValueEval getNameXEval(NameXPxg nameXPxg) { + ExternalSheet externSheet = _workbook.getExternalSheet(nameXPxg.getSheetName(), null, nameXPxg.getExternalWorkbookNumber()); if(externSheet == null || externSheet.getWorkbookName() == null) { // External reference to our own workbook's name return getLocalNameXEval(nameXPxg); @@ -363,8 +363,8 @@ public final class OperationEvaluationContext { nameXPxg.getExternalWorkbookNumber() ); return getExternalNameXEval(externName, workbookName); - } - + } + private ValueEval getLocalNameXEval(NameXPxg nameXPxg) { // Look up the sheet, if present int sIdx = -1; @@ -383,7 +383,7 @@ public final class OperationEvaluationContext { return new FunctionNameEval(name); } } - private ValueEval getLocalNameXEval(NameXPtg nameXPtg) { + private ValueEval getLocalNameXEval(NameXPtg nameXPtg) { String name = _workbook.resolveNameXText(nameXPtg); // Try to parse it as a name @@ -406,8 +406,11 @@ public final class OperationEvaluationContext { // Must be an external function return new FunctionNameEval(name); } - } - + } + public int getSheetIndex() { + return _sheetIndex; + } + private ValueEval getExternalNameXEval(ExternalName externName, String workbookName) { try { // Fetch the workbook this refers to, and the name as defined with that diff --git a/src/java/org/apache/poi/ss/formula/WorkbookEvaluator.java b/src/java/org/apache/poi/ss/formula/WorkbookEvaluator.java index f4b30c4f0..7a964912a 100644 --- a/src/java/org/apache/poi/ss/formula/WorkbookEvaluator.java +++ b/src/java/org/apache/poi/ss/formula/WorkbookEvaluator.java @@ -26,20 +26,60 @@ import java.util.TreeSet; import org.apache.poi.ss.formula.CollaboratingWorkbooksEnvironment.WorkbookNotFoundException; import org.apache.poi.ss.formula.atp.AnalysisToolPak; -import org.apache.poi.ss.formula.eval.*; +import org.apache.poi.ss.formula.eval.BlankEval; +import org.apache.poi.ss.formula.eval.BoolEval; +import org.apache.poi.ss.formula.eval.ErrorEval; +import org.apache.poi.ss.formula.eval.EvaluationException; +import org.apache.poi.ss.formula.eval.ExternalNameEval; +import org.apache.poi.ss.formula.eval.FunctionEval; +import org.apache.poi.ss.formula.eval.FunctionNameEval; +import org.apache.poi.ss.formula.eval.MissingArgEval; +import org.apache.poi.ss.formula.eval.NotImplementedException; +import org.apache.poi.ss.formula.eval.NumberEval; +import org.apache.poi.ss.formula.eval.OperandResolver; +import org.apache.poi.ss.formula.eval.StringEval; +import org.apache.poi.ss.formula.eval.ValueEval; import org.apache.poi.ss.formula.function.FunctionMetadataRegistry; import org.apache.poi.ss.formula.functions.Choose; import org.apache.poi.ss.formula.functions.FreeRefFunction; import org.apache.poi.ss.formula.functions.Function; import org.apache.poi.ss.formula.functions.IfFunc; -import org.apache.poi.ss.formula.ptg.*; +import org.apache.poi.ss.formula.ptg.Area3DPtg; +import org.apache.poi.ss.formula.ptg.Area3DPxg; +import org.apache.poi.ss.formula.ptg.AreaErrPtg; +import org.apache.poi.ss.formula.ptg.AreaPtg; +import org.apache.poi.ss.formula.ptg.AttrPtg; +import org.apache.poi.ss.formula.ptg.BoolPtg; +import org.apache.poi.ss.formula.ptg.ControlPtg; +import org.apache.poi.ss.formula.ptg.DeletedArea3DPtg; +import org.apache.poi.ss.formula.ptg.DeletedRef3DPtg; +import org.apache.poi.ss.formula.ptg.ErrPtg; +import org.apache.poi.ss.formula.ptg.ExpPtg; +import org.apache.poi.ss.formula.ptg.FuncVarPtg; +import org.apache.poi.ss.formula.ptg.IntPtg; +import org.apache.poi.ss.formula.ptg.MemAreaPtg; +import org.apache.poi.ss.formula.ptg.MemErrPtg; +import org.apache.poi.ss.formula.ptg.MemFuncPtg; +import org.apache.poi.ss.formula.ptg.MissingArgPtg; +import org.apache.poi.ss.formula.ptg.NamePtg; +import org.apache.poi.ss.formula.ptg.NameXPtg; +import org.apache.poi.ss.formula.ptg.NameXPxg; +import org.apache.poi.ss.formula.ptg.NumberPtg; +import org.apache.poi.ss.formula.ptg.OperationPtg; +import org.apache.poi.ss.formula.ptg.Ptg; +import org.apache.poi.ss.formula.ptg.Ref3DPtg; +import org.apache.poi.ss.formula.ptg.Ref3DPxg; +import org.apache.poi.ss.formula.ptg.RefErrorPtg; +import org.apache.poi.ss.formula.ptg.RefPtg; +import org.apache.poi.ss.formula.ptg.StringPtg; +import org.apache.poi.ss.formula.ptg.UnionPtg; +import org.apache.poi.ss.formula.ptg.UnknownPtg; import org.apache.poi.ss.formula.udf.AggregatingUDFFinder; import org.apache.poi.ss.formula.udf.UDFFinder; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.util.CellReference; import org.apache.poi.util.POILogFactory; import org.apache.poi.util.POILogger; - /** * Evaluates formula cells.

* @@ -53,39 +93,39 @@ import org.apache.poi.util.POILogger; * @author Thies Wellpott (debug output enhancements) */ public final class WorkbookEvaluator { - - private static final POILogger LOG = POILogFactory.getLogger(WorkbookEvaluator.class); + + private static final POILogger LOG = POILogFactory.getLogger(WorkbookEvaluator.class); private final EvaluationWorkbook _workbook; - private EvaluationCache _cache; - /** part of cache entry key (useful when evaluating multiple workbooks) */ - private int _workbookIx; + private EvaluationCache _cache; + /** part of cache entry key (useful when evaluating multiple workbooks) */ + private int _workbookIx; - private final IEvaluationListener _evaluationListener; - private final Map _sheetIndexesBySheet; - private final Map _sheetIndexesByName; - private CollaboratingWorkbooksEnvironment _collaboratingWorkbookEnvironment; - private final IStabilityClassifier _stabilityClassifier; - private final AggregatingUDFFinder _udfFinder; + private final IEvaluationListener _evaluationListener; + private final Map _sheetIndexesBySheet; + private final Map _sheetIndexesByName; + private CollaboratingWorkbooksEnvironment _collaboratingWorkbookEnvironment; + private final IStabilityClassifier _stabilityClassifier; + private final AggregatingUDFFinder _udfFinder; private boolean _ignoreMissingWorkbooks = false; - /** - * @param udfFinder pass null for default (AnalysisToolPak only) - */ - public WorkbookEvaluator(EvaluationWorkbook workbook, IStabilityClassifier stabilityClassifier, UDFFinder udfFinder) { - this (workbook, null, stabilityClassifier, udfFinder); - } - /* package */ WorkbookEvaluator(EvaluationWorkbook workbook, IEvaluationListener evaluationListener, - IStabilityClassifier stabilityClassifier, UDFFinder udfFinder) { - _workbook = workbook; - _evaluationListener = evaluationListener; - _cache = new EvaluationCache(evaluationListener); - _sheetIndexesBySheet = new IdentityHashMap(); - _sheetIndexesByName = new IdentityHashMap(); - _collaboratingWorkbookEnvironment = CollaboratingWorkbooksEnvironment.EMPTY; - _workbookIx = 0; - _stabilityClassifier = stabilityClassifier; + /** + * @param udfFinder pass null for default (AnalysisToolPak only) + */ + public WorkbookEvaluator(EvaluationWorkbook workbook, IStabilityClassifier stabilityClassifier, UDFFinder udfFinder) { + this (workbook, null, stabilityClassifier, udfFinder); + } + /* package */ WorkbookEvaluator(EvaluationWorkbook workbook, IEvaluationListener evaluationListener, + IStabilityClassifier stabilityClassifier, UDFFinder udfFinder) { + _workbook = workbook; + _evaluationListener = evaluationListener; + _cache = new EvaluationCache(evaluationListener); + _sheetIndexesBySheet = new IdentityHashMap(); + _sheetIndexesByName = new IdentityHashMap(); + _collaboratingWorkbookEnvironment = CollaboratingWorkbooksEnvironment.EMPTY; + _workbookIx = 0; + _stabilityClassifier = stabilityClassifier; AggregatingUDFFinder defaultToolkit = // workbook can be null in unit tests workbook == null ? null : (AggregatingUDFFinder)workbook.getUDFFinder(); @@ -93,492 +133,492 @@ public final class WorkbookEvaluator { defaultToolkit.add(udfFinder); } _udfFinder = defaultToolkit; - } + } - /** - * also for debug use. Used in toString methods - */ - /* package */ String getSheetName(int sheetIndex) { - return _workbook.getSheetName(sheetIndex); - } + /** + * also for debug use. Used in toString methods + */ + /* package */ String getSheetName(int sheetIndex) { + return _workbook.getSheetName(sheetIndex); + } - /* package */ EvaluationSheet getSheet(int sheetIndex) { - return _workbook.getSheet(sheetIndex); - } - - /* package */ EvaluationWorkbook getWorkbook() { - return _workbook; - } + /* package */ EvaluationSheet getSheet(int sheetIndex) { + return _workbook.getSheet(sheetIndex); + } + + /* package */ EvaluationWorkbook getWorkbook() { + return _workbook; + } - /* package */ EvaluationName getName(String name, int sheetIndex) { - EvaluationName evalName = _workbook.getName(name, sheetIndex); - return evalName; - } + /* package */ EvaluationName getName(String name, int sheetIndex) { + EvaluationName evalName = _workbook.getName(name, sheetIndex); + return evalName; + } - private static boolean isDebugLogEnabled() { - return LOG.check(POILogger.DEBUG); - } - private static boolean isInfoLogEnabled() { - return LOG.check(POILogger.INFO); - } - private static void logDebug(String s) { - if (isDebugLogEnabled()) { - LOG.log(POILogger.DEBUG, s); - } - } - private static void logInfo(String s) { - if (isInfoLogEnabled()) { - LOG.log(POILogger.INFO, s); - } - } - /* package */ void attachToEnvironment(CollaboratingWorkbooksEnvironment collaboratingWorkbooksEnvironment, EvaluationCache cache, int workbookIx) { - _collaboratingWorkbookEnvironment = collaboratingWorkbooksEnvironment; - _cache = cache; - _workbookIx = workbookIx; - } - /* package */ CollaboratingWorkbooksEnvironment getEnvironment() { - return _collaboratingWorkbookEnvironment; - } + private static boolean isDebugLogEnabled() { + return LOG.check(POILogger.DEBUG); + } + private static boolean isInfoLogEnabled() { + return LOG.check(POILogger.INFO); + } + private static void logDebug(String s) { + if (isDebugLogEnabled()) { + LOG.log(POILogger.DEBUG, s); + } + } + private static void logInfo(String s) { + if (isInfoLogEnabled()) { + LOG.log(POILogger.INFO, s); + } + } + /* package */ void attachToEnvironment(CollaboratingWorkbooksEnvironment collaboratingWorkbooksEnvironment, EvaluationCache cache, int workbookIx) { + _collaboratingWorkbookEnvironment = collaboratingWorkbooksEnvironment; + _cache = cache; + _workbookIx = workbookIx; + } + /* package */ CollaboratingWorkbooksEnvironment getEnvironment() { + return _collaboratingWorkbookEnvironment; + } - /** - * Discards the current workbook environment and attaches to the default 'empty' environment. - * Also resets evaluation cache. - */ - /* package */ void detachFromEnvironment() { - _collaboratingWorkbookEnvironment = CollaboratingWorkbooksEnvironment.EMPTY; - _cache = new EvaluationCache(_evaluationListener); - _workbookIx = 0; - } - /** - * @return the evaluator for another workbook which is part of the same {@link CollaboratingWorkbooksEnvironment} - */ - /* package */ WorkbookEvaluator getOtherWorkbookEvaluator(String workbookName) throws WorkbookNotFoundException { - return _collaboratingWorkbookEnvironment.getWorkbookEvaluator(workbookName); - } + /** + * Discards the current workbook environment and attaches to the default 'empty' environment. + * Also resets evaluation cache. + */ + /* package */ void detachFromEnvironment() { + _collaboratingWorkbookEnvironment = CollaboratingWorkbooksEnvironment.EMPTY; + _cache = new EvaluationCache(_evaluationListener); + _workbookIx = 0; + } + /** + * @return the evaluator for another workbook which is part of the same {@link CollaboratingWorkbooksEnvironment} + */ + /* package */ WorkbookEvaluator getOtherWorkbookEvaluator(String workbookName) throws WorkbookNotFoundException { + return _collaboratingWorkbookEnvironment.getWorkbookEvaluator(workbookName); + } - /* package */ IEvaluationListener getEvaluationListener() { - return _evaluationListener; - } + /* package */ IEvaluationListener getEvaluationListener() { + return _evaluationListener; + } - /** - * Should be called whenever there are changes to input cells in the evaluated workbook. - * Failure to call this method after changing cell values will cause incorrect behaviour - * of the evaluate~ methods of this class - */ - public void clearAllCachedResultValues() { - _cache.clear(); - _sheetIndexesBySheet.clear(); - } + /** + * Should be called whenever there are changes to input cells in the evaluated workbook. + * Failure to call this method after changing cell values will cause incorrect behaviour + * of the evaluate~ methods of this class + */ + public void clearAllCachedResultValues() { + _cache.clear(); + _sheetIndexesBySheet.clear(); + } - /** - * Should be called to tell the cell value cache that the specified (value or formula) cell - * has changed. - */ - public void notifyUpdateCell(EvaluationCell cell) { - int sheetIndex = getSheetIndex(cell.getSheet()); - _cache.notifyUpdateCell(_workbookIx, sheetIndex, cell); - } - /** - * Should be called to tell the cell value cache that the specified cell has just been - * deleted. - */ - public void notifyDeleteCell(EvaluationCell cell) { - int sheetIndex = getSheetIndex(cell.getSheet()); - _cache.notifyDeleteCell(_workbookIx, sheetIndex, cell); - } - - private int getSheetIndex(EvaluationSheet sheet) { - Integer result = _sheetIndexesBySheet.get(sheet); - if (result == null) { - int sheetIndex = _workbook.getSheetIndex(sheet); - if (sheetIndex < 0) { - throw new RuntimeException("Specified sheet from a different book"); - } - result = Integer.valueOf(sheetIndex); - _sheetIndexesBySheet.put(sheet, result); - } - return result.intValue(); - } + /** + * Should be called to tell the cell value cache that the specified (value or formula) cell + * has changed. + */ + public void notifyUpdateCell(EvaluationCell cell) { + int sheetIndex = getSheetIndex(cell.getSheet()); + _cache.notifyUpdateCell(_workbookIx, sheetIndex, cell); + } + /** + * Should be called to tell the cell value cache that the specified cell has just been + * deleted. + */ + public void notifyDeleteCell(EvaluationCell cell) { + int sheetIndex = getSheetIndex(cell.getSheet()); + _cache.notifyDeleteCell(_workbookIx, sheetIndex, cell); + } + + private int getSheetIndex(EvaluationSheet sheet) { + Integer result = _sheetIndexesBySheet.get(sheet); + if (result == null) { + int sheetIndex = _workbook.getSheetIndex(sheet); + if (sheetIndex < 0) { + throw new RuntimeException("Specified sheet from a different book"); + } + result = Integer.valueOf(sheetIndex); + _sheetIndexesBySheet.put(sheet, result); + } + return result.intValue(); + } - public ValueEval evaluate(EvaluationCell srcCell) { - int sheetIndex = getSheetIndex(srcCell.getSheet()); - return evaluateAny(srcCell, sheetIndex, srcCell.getRowIndex(), srcCell.getColumnIndex(), new EvaluationTracker(_cache)); - } + public ValueEval evaluate(EvaluationCell srcCell) { + int sheetIndex = getSheetIndex(srcCell.getSheet()); + return evaluateAny(srcCell, sheetIndex, srcCell.getRowIndex(), srcCell.getColumnIndex(), new EvaluationTracker(_cache)); + } - /** - * Case-insensitive. - * @return -1 if sheet with specified name does not exist - */ - /* package */ int getSheetIndex(String sheetName) { - Integer result = _sheetIndexesByName.get(sheetName); - if (result == null) { - int sheetIndex = _workbook.getSheetIndex(sheetName); - if (sheetIndex < 0) { - return -1; - } - result = Integer.valueOf(sheetIndex); - _sheetIndexesByName.put(sheetName, result); - } - return result.intValue(); - } - - /* package */ int getSheetIndexByExternIndex(int externSheetIndex) { - return _workbook.convertFromExternSheetIndex(externSheetIndex); - } + /** + * Case-insensitive. + * @return -1 if sheet with specified name does not exist + */ + /* package */ int getSheetIndex(String sheetName) { + Integer result = _sheetIndexesByName.get(sheetName); + if (result == null) { + int sheetIndex = _workbook.getSheetIndex(sheetName); + if (sheetIndex < 0) { + return -1; + } + result = Integer.valueOf(sheetIndex); + _sheetIndexesByName.put(sheetName, result); + } + return result.intValue(); + } + + /* package */ int getSheetIndexByExternIndex(int externSheetIndex) { + return _workbook.convertFromExternSheetIndex(externSheetIndex); + } - /** - * @return never null, never {@link BlankEval} - */ - private ValueEval evaluateAny(EvaluationCell srcCell, int sheetIndex, - int rowIndex, int columnIndex, EvaluationTracker tracker) { + /** + * @return never null, never {@link BlankEval} + */ + private ValueEval evaluateAny(EvaluationCell srcCell, int sheetIndex, + int rowIndex, int columnIndex, EvaluationTracker tracker) { - // avoid tracking dependencies to cells that have constant definition - boolean shouldCellDependencyBeRecorded = _stabilityClassifier == null ? true - : !_stabilityClassifier.isCellFinal(sheetIndex, rowIndex, columnIndex); - if (srcCell == null || srcCell.getCellType() != Cell.CELL_TYPE_FORMULA) { - ValueEval result = getValueFromNonFormulaCell(srcCell); - if (shouldCellDependencyBeRecorded) { - tracker.acceptPlainValueDependency(_workbookIx, sheetIndex, rowIndex, columnIndex, result); - } - return result; - } + // avoid tracking dependencies to cells that have constant definition + boolean shouldCellDependencyBeRecorded = _stabilityClassifier == null ? true + : !_stabilityClassifier.isCellFinal(sheetIndex, rowIndex, columnIndex); + if (srcCell == null || srcCell.getCellType() != Cell.CELL_TYPE_FORMULA) { + ValueEval result = getValueFromNonFormulaCell(srcCell); + if (shouldCellDependencyBeRecorded) { + tracker.acceptPlainValueDependency(_workbookIx, sheetIndex, rowIndex, columnIndex, result); + } + return result; + } - FormulaCellCacheEntry cce = _cache.getOrCreateFormulaCellEntry(srcCell); - if (shouldCellDependencyBeRecorded || cce.isInputSensitive()) { - tracker.acceptFormulaDependency(cce); - } - IEvaluationListener evalListener = _evaluationListener; - ValueEval result; - if (cce.getValue() == null) { - if (!tracker.startEvaluate(cce)) { - return ErrorEval.CIRCULAR_REF_ERROR; - } - OperationEvaluationContext ec = new OperationEvaluationContext(this, _workbook, sheetIndex, rowIndex, columnIndex, tracker); + FormulaCellCacheEntry cce = _cache.getOrCreateFormulaCellEntry(srcCell); + if (shouldCellDependencyBeRecorded || cce.isInputSensitive()) { + tracker.acceptFormulaDependency(cce); + } + IEvaluationListener evalListener = _evaluationListener; + ValueEval result; + if (cce.getValue() == null) { + if (!tracker.startEvaluate(cce)) { + return ErrorEval.CIRCULAR_REF_ERROR; + } + OperationEvaluationContext ec = new OperationEvaluationContext(this, _workbook, sheetIndex, rowIndex, columnIndex, tracker); - try { + try { - Ptg[] ptgs = _workbook.getFormulaTokens(srcCell); - if (evalListener == null) { - result = evaluateFormula(ec, ptgs); - } else { - evalListener.onStartEvaluate(srcCell, cce); - result = evaluateFormula(ec, ptgs); - evalListener.onEndEvaluate(cce, result); - } + Ptg[] ptgs = _workbook.getFormulaTokens(srcCell); + if (evalListener == null) { + result = evaluateFormula(ec, ptgs); + } else { + evalListener.onStartEvaluate(srcCell, cce); + result = evaluateFormula(ec, ptgs); + evalListener.onEndEvaluate(cce, result); + } - tracker.updateCacheResult(result); - } - catch (NotImplementedException e) { - throw addExceptionInfo(e, sheetIndex, rowIndex, columnIndex); - } catch (RuntimeException re) { - if (re.getCause() instanceof WorkbookNotFoundException && _ignoreMissingWorkbooks) { - logInfo(re.getCause().getMessage() + " - Continuing with cached value!"); - switch(srcCell.getCachedFormulaResultType()) { - case Cell.CELL_TYPE_NUMERIC: - result = new NumberEval(srcCell.getNumericCellValue()); - break; - case Cell.CELL_TYPE_STRING: - result = new StringEval(srcCell.getStringCellValue()); - break; - case Cell.CELL_TYPE_BLANK: - result = BlankEval.instance; - break; - case Cell.CELL_TYPE_BOOLEAN: - result = BoolEval.valueOf(srcCell.getBooleanCellValue()); - break; - case Cell.CELL_TYPE_ERROR: - result = ErrorEval.valueOf(srcCell.getErrorCellValue()); - break; - case Cell.CELL_TYPE_FORMULA: - default: - throw new RuntimeException("Unexpected cell type '" + srcCell.getCellType()+"' found!"); - } - } else { - throw re; - } - } finally { - tracker.endEvaluate(cce); - } - } else { - if(evalListener != null) { - evalListener.onCacheHit(sheetIndex, rowIndex, columnIndex, cce.getValue()); - } - return cce.getValue(); - } - if (isDebugLogEnabled()) { - String sheetName = getSheetName(sheetIndex); - CellReference cr = new CellReference(rowIndex, columnIndex); - logDebug("Evaluated " + sheetName + "!" + cr.formatAsString() + " to " + result.toString()); - } - // Usually (result === cce.getValue()) - // But sometimes: (result==ErrorEval.CIRCULAR_REF_ERROR, cce.getValue()==null) - // When circular references are detected, the cache entry is only updated for - // the top evaluation frame - return result; - } + tracker.updateCacheResult(result); + } + catch (NotImplementedException e) { + throw addExceptionInfo(e, sheetIndex, rowIndex, columnIndex); + } catch (RuntimeException re) { + if (re.getCause() instanceof WorkbookNotFoundException && _ignoreMissingWorkbooks) { + logInfo(re.getCause().getMessage() + " - Continuing with cached value!"); + switch(srcCell.getCachedFormulaResultType()) { + case Cell.CELL_TYPE_NUMERIC: + result = new NumberEval(srcCell.getNumericCellValue()); + break; + case Cell.CELL_TYPE_STRING: + result = new StringEval(srcCell.getStringCellValue()); + break; + case Cell.CELL_TYPE_BLANK: + result = BlankEval.instance; + break; + case Cell.CELL_TYPE_BOOLEAN: + result = BoolEval.valueOf(srcCell.getBooleanCellValue()); + break; + case Cell.CELL_TYPE_ERROR: + result = ErrorEval.valueOf(srcCell.getErrorCellValue()); + break; + case Cell.CELL_TYPE_FORMULA: + default: + throw new RuntimeException("Unexpected cell type '" + srcCell.getCellType()+"' found!"); + } + } else { + throw re; + } + } finally { + tracker.endEvaluate(cce); + } + } else { + if(evalListener != null) { + evalListener.onCacheHit(sheetIndex, rowIndex, columnIndex, cce.getValue()); + } + return cce.getValue(); + } + if (isDebugLogEnabled()) { + String sheetName = getSheetName(sheetIndex); + CellReference cr = new CellReference(rowIndex, columnIndex); + logDebug("Evaluated " + sheetName + "!" + cr.formatAsString() + " to " + result.toString()); + } + // Usually (result === cce.getValue()) + // But sometimes: (result==ErrorEval.CIRCULAR_REF_ERROR, cce.getValue()==null) + // When circular references are detected, the cache entry is only updated for + // the top evaluation frame + return result; + } - /** - * Adds the current cell reference to the exception for easier debugging. - * Would be nice to get the formula text as well, but that seems to require - * too much digging around and casting to get the FormulaRenderingWorkbook. - */ - private NotImplementedException addExceptionInfo(NotImplementedException inner, int sheetIndex, int rowIndex, int columnIndex) { + /** + * Adds the current cell reference to the exception for easier debugging. + * Would be nice to get the formula text as well, but that seems to require + * too much digging around and casting to get the FormulaRenderingWorkbook. + */ + private NotImplementedException addExceptionInfo(NotImplementedException inner, int sheetIndex, int rowIndex, int columnIndex) { - try { - String sheetName = _workbook.getSheetName(sheetIndex); - CellReference cr = new CellReference(sheetName, rowIndex, columnIndex, false, false); - String msg = "Error evaluating cell " + cr.formatAsString(); - return new NotImplementedException(msg, inner); - } catch (Exception e) { - // avoid bombing out during exception handling - LOG.log(POILogger.ERROR, "Can't add exception info", e); - return inner; // preserve original exception - } - } - /** - * Gets the value from a non-formula cell. - * @param cell may be null - * @return {@link BlankEval} if cell is null or blank, never null - */ - /* package */ static ValueEval getValueFromNonFormulaCell(EvaluationCell cell) { - if (cell == null) { - return BlankEval.instance; - } - int cellType = cell.getCellType(); - switch (cellType) { - case Cell.CELL_TYPE_NUMERIC: - return new NumberEval(cell.getNumericCellValue()); - case Cell.CELL_TYPE_STRING: - return new StringEval(cell.getStringCellValue()); - case Cell.CELL_TYPE_BOOLEAN: - return BoolEval.valueOf(cell.getBooleanCellValue()); - case Cell.CELL_TYPE_BLANK: - return BlankEval.instance; - case Cell.CELL_TYPE_ERROR: - return ErrorEval.valueOf(cell.getErrorCellValue()); - } - throw new RuntimeException("Unexpected cell type (" + cellType + ")"); - } + try { + String sheetName = _workbook.getSheetName(sheetIndex); + CellReference cr = new CellReference(sheetName, rowIndex, columnIndex, false, false); + String msg = "Error evaluating cell " + cr.formatAsString(); + return new NotImplementedException(msg, inner); + } catch (Exception e) { + // avoid bombing out during exception handling + LOG.log(POILogger.ERROR, "Can't add exception info", e); + return inner; // preserve original exception + } + } + /** + * Gets the value from a non-formula cell. + * @param cell may be null + * @return {@link BlankEval} if cell is null or blank, never null + */ + /* package */ static ValueEval getValueFromNonFormulaCell(EvaluationCell cell) { + if (cell == null) { + return BlankEval.instance; + } + int cellType = cell.getCellType(); + switch (cellType) { + case Cell.CELL_TYPE_NUMERIC: + return new NumberEval(cell.getNumericCellValue()); + case Cell.CELL_TYPE_STRING: + return new StringEval(cell.getStringCellValue()); + case Cell.CELL_TYPE_BOOLEAN: + return BoolEval.valueOf(cell.getBooleanCellValue()); + case Cell.CELL_TYPE_BLANK: + return BlankEval.instance; + case Cell.CELL_TYPE_ERROR: + return ErrorEval.valueOf(cell.getErrorCellValue()); + } + throw new RuntimeException("Unexpected cell type (" + cellType + ")"); + } /** * whether print detailed messages about the next formula evaluation */ - private boolean dbgEvaluationOutputForNextEval = false; + private boolean dbgEvaluationOutputForNextEval = false; - // special logger for formula evaluation output (because of possibly very large output) - private final POILogger EVAL_LOG = POILogFactory.getLogger("POI.FormulaEval"); - // current indent level for evalution; negative value for no output - private int dbgEvaluationOutputIndent = -1; + // special logger for formula evaluation output (because of possibly very large output) + private final POILogger EVAL_LOG = POILogFactory.getLogger("POI.FormulaEval"); + // current indent level for evalution; negative value for no output + private int dbgEvaluationOutputIndent = -1; - // visibility raised for testing - /* package */ ValueEval evaluateFormula(OperationEvaluationContext ec, Ptg[] ptgs) { + // visibility raised for testing + /* package */ ValueEval evaluateFormula(OperationEvaluationContext ec, Ptg[] ptgs) { - String dbgIndentStr = ""; // always init. to non-null just for defensive avoiding NPE - if (dbgEvaluationOutputForNextEval) { - // first evaluation call when ouput is desired, so iit. this evaluator instance - dbgEvaluationOutputIndent = 1; - dbgEvaluationOutputForNextEval = false; - } - if (dbgEvaluationOutputIndent > 0) { - // init. indent string to needed spaces (create as substring vom very long space-only string; - // limit indendation for deep recursions) - dbgIndentStr = " "; - dbgIndentStr = dbgIndentStr.substring(0, Math.min(dbgIndentStr.length(), dbgEvaluationOutputIndent*2)); - EVAL_LOG.log(POILogger.WARN, dbgIndentStr - + "- evaluateFormula('" + ec.getRefEvaluatorForCurrentSheet().getSheetNameRange() - + "'/" + new CellReference(ec.getRowIndex(), ec.getColumnIndex()).formatAsString() - + "): " + Arrays.toString(ptgs).replaceAll("\\Qorg.apache.poi.ss.formula.ptg.\\E", "")); - dbgEvaluationOutputIndent++; - } + String dbgIndentStr = ""; // always init. to non-null just for defensive avoiding NPE + if (dbgEvaluationOutputForNextEval) { + // first evaluation call when ouput is desired, so iit. this evaluator instance + dbgEvaluationOutputIndent = 1; + dbgEvaluationOutputForNextEval = false; + } + if (dbgEvaluationOutputIndent > 0) { + // init. indent string to needed spaces (create as substring vom very long space-only string; + // limit indendation for deep recursions) + dbgIndentStr = " "; + dbgIndentStr = dbgIndentStr.substring(0, Math.min(dbgIndentStr.length(), dbgEvaluationOutputIndent*2)); + EVAL_LOG.log(POILogger.WARN, dbgIndentStr + + "- evaluateFormula('" + ec.getRefEvaluatorForCurrentSheet().getSheetNameRange() + + "'/" + new CellReference(ec.getRowIndex(), ec.getColumnIndex()).formatAsString() + + "): " + Arrays.toString(ptgs).replaceAll("\\Qorg.apache.poi.ss.formula.ptg.\\E", "")); + dbgEvaluationOutputIndent++; + } - Stack stack = new Stack(); - for (int i = 0, iSize = ptgs.length; i < iSize; i++) { + Stack stack = new Stack(); + for (int i = 0, iSize = ptgs.length; i < iSize; i++) { - // since we don't know how to handle these yet :( - Ptg ptg = ptgs[i]; - if (dbgEvaluationOutputIndent > 0) { - EVAL_LOG.log(POILogger.INFO, dbgIndentStr + " * ptg " + i + ": " + ptg); - } - if (ptg instanceof AttrPtg) { - AttrPtg attrPtg = (AttrPtg) ptg; - if (attrPtg.isSum()) { - // Excel prefers to encode 'SUM()' as a tAttr token, but this evaluator - // expects the equivalent function token - ptg = FuncVarPtg.SUM; - } - if (attrPtg.isOptimizedChoose()) { - ValueEval arg0 = stack.pop(); - int[] jumpTable = attrPtg.getJumpTable(); - int dist; - int nChoices = jumpTable.length; - try { - int switchIndex = Choose.evaluateFirstArg(arg0, ec.getRowIndex(), ec.getColumnIndex()); - if (switchIndex<1 || switchIndex > nChoices) { - stack.push(ErrorEval.VALUE_INVALID); - dist = attrPtg.getChooseFuncOffset() + 4; // +4 for tFuncFar(CHOOSE) - } else { - dist = jumpTable[switchIndex-1]; - } - } catch (EvaluationException e) { - stack.push(e.getErrorEval()); - dist = attrPtg.getChooseFuncOffset() + 4; // +4 for tFuncFar(CHOOSE) - } - // Encoded dist for tAttrChoose includes size of jump table, but - // countTokensToBeSkipped() does not (it counts whole tokens). - dist -= nChoices*2+2; // subtract jump table size - i+= countTokensToBeSkipped(ptgs, i, dist); - continue; - } - if (attrPtg.isOptimizedIf()) { - ValueEval arg0 = stack.pop(); - boolean evaluatedPredicate; - try { - evaluatedPredicate = IfFunc.evaluateFirstArg(arg0, ec.getRowIndex(), ec.getColumnIndex()); - } catch (EvaluationException e) { - stack.push(e.getErrorEval()); - int dist = attrPtg.getData(); - i+= countTokensToBeSkipped(ptgs, i, dist); - attrPtg = (AttrPtg) ptgs[i]; - dist = attrPtg.getData()+1; - i+= countTokensToBeSkipped(ptgs, i, dist); - continue; - } - if (evaluatedPredicate) { - // nothing to skip - true param follows - } else { - int dist = attrPtg.getData(); - i+= countTokensToBeSkipped(ptgs, i, dist); - Ptg nextPtg = ptgs[i+1]; - if (ptgs[i] instanceof AttrPtg && nextPtg instanceof FuncVarPtg && - // in order to verify that there is no third param, we need to check - // if we really have the IF next or some other FuncVarPtg as third param, e.g. ROW()/COLUMN()! - ((FuncVarPtg)nextPtg).getFunctionIndex() == FunctionMetadataRegistry.FUNCTION_INDEX_IF) { - // this is an if statement without a false param (as opposed to MissingArgPtg as the false param) - i++; - stack.push(BoolEval.FALSE); - } - } - continue; - } - if (attrPtg.isSkip()) { - int dist = attrPtg.getData()+1; - i+= countTokensToBeSkipped(ptgs, i, dist); - if (stack.peek() == MissingArgEval.instance) { - stack.pop(); - stack.push(BlankEval.instance); - } - continue; - } - } - if (ptg instanceof ControlPtg) { - // skip Parentheses, Attr, etc - continue; - } - if (ptg instanceof MemFuncPtg || ptg instanceof MemAreaPtg) { - // can ignore, rest of tokens for this expression are in OK RPN order - continue; - } - if (ptg instanceof MemErrPtg) { - continue; - } + // since we don't know how to handle these yet :( + Ptg ptg = ptgs[i]; + if (dbgEvaluationOutputIndent > 0) { + EVAL_LOG.log(POILogger.INFO, dbgIndentStr + " * ptg " + i + ": " + ptg); + } + if (ptg instanceof AttrPtg) { + AttrPtg attrPtg = (AttrPtg) ptg; + if (attrPtg.isSum()) { + // Excel prefers to encode 'SUM()' as a tAttr token, but this evaluator + // expects the equivalent function token + ptg = FuncVarPtg.SUM; + } + if (attrPtg.isOptimizedChoose()) { + ValueEval arg0 = stack.pop(); + int[] jumpTable = attrPtg.getJumpTable(); + int dist; + int nChoices = jumpTable.length; + try { + int switchIndex = Choose.evaluateFirstArg(arg0, ec.getRowIndex(), ec.getColumnIndex()); + if (switchIndex<1 || switchIndex > nChoices) { + stack.push(ErrorEval.VALUE_INVALID); + dist = attrPtg.getChooseFuncOffset() + 4; // +4 for tFuncFar(CHOOSE) + } else { + dist = jumpTable[switchIndex-1]; + } + } catch (EvaluationException e) { + stack.push(e.getErrorEval()); + dist = attrPtg.getChooseFuncOffset() + 4; // +4 for tFuncFar(CHOOSE) + } + // Encoded dist for tAttrChoose includes size of jump table, but + // countTokensToBeSkipped() does not (it counts whole tokens). + dist -= nChoices*2+2; // subtract jump table size + i+= countTokensToBeSkipped(ptgs, i, dist); + continue; + } + if (attrPtg.isOptimizedIf()) { + ValueEval arg0 = stack.pop(); + boolean evaluatedPredicate; + try { + evaluatedPredicate = IfFunc.evaluateFirstArg(arg0, ec.getRowIndex(), ec.getColumnIndex()); + } catch (EvaluationException e) { + stack.push(e.getErrorEval()); + int dist = attrPtg.getData(); + i+= countTokensToBeSkipped(ptgs, i, dist); + attrPtg = (AttrPtg) ptgs[i]; + dist = attrPtg.getData()+1; + i+= countTokensToBeSkipped(ptgs, i, dist); + continue; + } + if (evaluatedPredicate) { + // nothing to skip - true param follows + } else { + int dist = attrPtg.getData(); + i+= countTokensToBeSkipped(ptgs, i, dist); + Ptg nextPtg = ptgs[i+1]; + if (ptgs[i] instanceof AttrPtg && nextPtg instanceof FuncVarPtg && + // in order to verify that there is no third param, we need to check + // if we really have the IF next or some other FuncVarPtg as third param, e.g. ROW()/COLUMN()! + ((FuncVarPtg)nextPtg).getFunctionIndex() == FunctionMetadataRegistry.FUNCTION_INDEX_IF) { + // this is an if statement without a false param (as opposed to MissingArgPtg as the false param) + i++; + stack.push(BoolEval.FALSE); + } + } + continue; + } + if (attrPtg.isSkip()) { + int dist = attrPtg.getData()+1; + i+= countTokensToBeSkipped(ptgs, i, dist); + if (stack.peek() == MissingArgEval.instance) { + stack.pop(); + stack.push(BlankEval.instance); + } + continue; + } + } + if (ptg instanceof ControlPtg) { + // skip Parentheses, Attr, etc + continue; + } + if (ptg instanceof MemFuncPtg || ptg instanceof MemAreaPtg) { + // can ignore, rest of tokens for this expression are in OK RPN order + continue; + } + if (ptg instanceof MemErrPtg) { + continue; + } - ValueEval opResult; - if (ptg instanceof OperationPtg) { - OperationPtg optg = (OperationPtg) ptg; + ValueEval opResult; + if (ptg instanceof OperationPtg) { + OperationPtg optg = (OperationPtg) ptg; - if (optg instanceof UnionPtg) { continue; } + if (optg instanceof UnionPtg) { continue; } - int numops = optg.getNumberOfOperands(); - ValueEval[] ops = new ValueEval[numops]; + int numops = optg.getNumberOfOperands(); + ValueEval[] ops = new ValueEval[numops]; - // storing the ops in reverse order since they are popping - for (int j = numops - 1; j >= 0; j--) { - ValueEval p = stack.pop(); - ops[j] = p; - } -// logDebug("invoke " + operation + " (nAgs=" + numops + ")"); - opResult = OperationEvaluatorFactory.evaluate(optg, ops, ec); - } else { - opResult = getEvalForPtg(ptg, ec); - } - if (opResult == null) { - throw new RuntimeException("Evaluation result must not be null"); - } -// logDebug("push " + opResult); - stack.push(opResult); - if (dbgEvaluationOutputIndent > 0) { - EVAL_LOG.log(POILogger.INFO, dbgIndentStr + " = " + opResult); - } - } + // storing the ops in reverse order since they are popping + for (int j = numops - 1; j >= 0; j--) { + ValueEval p = stack.pop(); + ops[j] = p; + } +// logDebug("invoke " + operation + " (nAgs=" + numops + ")"); + opResult = OperationEvaluatorFactory.evaluate(optg, ops, ec); + } else { + opResult = getEvalForPtg(ptg, ec); + } + if (opResult == null) { + throw new RuntimeException("Evaluation result must not be null"); + } +// logDebug("push " + opResult); + stack.push(opResult); + if (dbgEvaluationOutputIndent > 0) { + EVAL_LOG.log(POILogger.INFO, dbgIndentStr + " = " + opResult); + } + } - ValueEval value = stack.pop(); - if (!stack.isEmpty()) { - throw new IllegalStateException("evaluation stack not empty"); - } - ValueEval result = dereferenceResult(value, ec.getRowIndex(), ec.getColumnIndex()); - if (dbgEvaluationOutputIndent > 0) { - EVAL_LOG.log(POILogger.INFO, dbgIndentStr + "finshed eval of " - + new CellReference(ec.getRowIndex(), ec.getColumnIndex()).formatAsString() - + ": " + result); - dbgEvaluationOutputIndent--; - if (dbgEvaluationOutputIndent == 1) { - // this evaluation is done, reset indent to stop logging - dbgEvaluationOutputIndent = -1; - } - } // if - return result; + ValueEval value = stack.pop(); + if (!stack.isEmpty()) { + throw new IllegalStateException("evaluation stack not empty"); + } + ValueEval result = dereferenceResult(value, ec.getRowIndex(), ec.getColumnIndex()); + if (dbgEvaluationOutputIndent > 0) { + EVAL_LOG.log(POILogger.INFO, dbgIndentStr + "finshed eval of " + + new CellReference(ec.getRowIndex(), ec.getColumnIndex()).formatAsString() + + ": " + result); + dbgEvaluationOutputIndent--; + if (dbgEvaluationOutputIndent == 1) { + // this evaluation is done, reset indent to stop logging + dbgEvaluationOutputIndent = -1; + } + } // if + return result; - } + } - /** - * Calculates the number of tokens that the evaluator should skip upon reaching a tAttrSkip. - * - * @return the number of tokens (starting from startIndex+1) that need to be skipped - * to achieve the specified distInBytes skip distance. - */ - private static int countTokensToBeSkipped(Ptg[] ptgs, int startIndex, int distInBytes) { - int remBytes = distInBytes; - int index = startIndex; - while (remBytes != 0) { - index++; - remBytes -= ptgs[index].getSize(); - if (remBytes < 0) { - throw new RuntimeException("Bad skip distance (wrong token size calculation)."); - } - if (index >= ptgs.length) { - throw new RuntimeException("Skip distance too far (ran out of formula tokens)."); - } - } - return index-startIndex; - } + /** + * Calculates the number of tokens that the evaluator should skip upon reaching a tAttrSkip. + * + * @return the number of tokens (starting from startIndex+1) that need to be skipped + * to achieve the specified distInBytes skip distance. + */ + private static int countTokensToBeSkipped(Ptg[] ptgs, int startIndex, int distInBytes) { + int remBytes = distInBytes; + int index = startIndex; + while (remBytes != 0) { + index++; + remBytes -= ptgs[index].getSize(); + if (remBytes < 0) { + throw new RuntimeException("Bad skip distance (wrong token size calculation)."); + } + if (index >= ptgs.length) { + throw new RuntimeException("Skip distance too far (ran out of formula tokens)."); + } + } + return index-startIndex; + } - /** - * Dereferences a single value from any AreaEval or RefEval evaluation - * result. If the supplied evaluationResult is just a plain value, it is - * returned as-is. - * - * @return a {@link NumberEval}, {@link StringEval}, {@link BoolEval}, or - * {@link ErrorEval}. Never null. {@link BlankEval} is - * converted to {@link NumberEval#ZERO} - */ - public static ValueEval dereferenceResult(ValueEval evaluationResult, int srcRowNum, int srcColNum) { - ValueEval value; - try { - value = OperandResolver.getSingleValue(evaluationResult, srcRowNum, srcColNum); - } catch (EvaluationException e) { - return e.getErrorEval(); - } - if (value == BlankEval.instance) { - // Note Excel behaviour here. A blank final final value is converted to zero. - return NumberEval.ZERO; - // Formulas _never_ evaluate to blank. If a formula appears to have evaluated to - // blank, the actual value is empty string. This can be verified with ISBLANK(). - } - return value; - } + /** + * Dereferences a single value from any AreaEval or RefEval evaluation + * result. If the supplied evaluationResult is just a plain value, it is + * returned as-is. + * + * @return a {@link NumberEval}, {@link StringEval}, {@link BoolEval}, or + * {@link ErrorEval}. Never null. {@link BlankEval} is + * converted to {@link NumberEval#ZERO} + */ + public static ValueEval dereferenceResult(ValueEval evaluationResult, int srcRowNum, int srcColNum) { + ValueEval value; + try { + value = OperandResolver.getSingleValue(evaluationResult, srcRowNum, srcColNum); + } catch (EvaluationException e) { + return e.getErrorEval(); + } + if (value == BlankEval.instance) { + // Note Excel behaviour here. A blank final final value is converted to zero. + return NumberEval.ZERO; + // Formulas _never_ evaluate to blank. If a formula appears to have evaluated to + // blank, the actual value is empty string. This can be verified with ISBLANK(). + } + return value; + } /** @@ -670,7 +710,7 @@ public final class WorkbookEvaluator { } return eval; } - + private ValueEval getEvalForNameRecord(EvaluationName nameRecord, OperationEvaluationContext ec) { if (nameRecord.isFunctionName()) { return new FunctionNameEval(nameRecord.getNameText()); @@ -685,25 +725,25 @@ public final class WorkbookEvaluator { /** * YK: Used by OperationEvaluationContext to resolve indirect names. */ - /*package*/ ValueEval evaluateNameFormula(Ptg[] ptgs, OperationEvaluationContext ec) { + /*package*/ ValueEval evaluateNameFormula(Ptg[] ptgs, OperationEvaluationContext ec) { if (ptgs.length == 1) { return getEvalForPtg(ptgs[0], ec); } - return evaluateFormula(ec, ptgs); - } + return evaluateFormula(ec, ptgs); + } - /** - * Used by the lazy ref evals whenever they need to get the value of a contained cell. - */ - /* package */ ValueEval evaluateReference(EvaluationSheet sheet, int sheetIndex, int rowIndex, - int columnIndex, EvaluationTracker tracker) { + /** + * Used by the lazy ref evals whenever they need to get the value of a contained cell. + */ + /* package */ ValueEval evaluateReference(EvaluationSheet sheet, int sheetIndex, int rowIndex, + int columnIndex, EvaluationTracker tracker) { - EvaluationCell cell = sheet.getCell(rowIndex, columnIndex); - return evaluateAny(cell, sheetIndex, rowIndex, columnIndex, tracker); - } - public FreeRefFunction findUserDefinedFunction(String functionName) { - return _udfFinder.findFunction(functionName); - } + EvaluationCell cell = sheet.getCell(rowIndex, columnIndex); + return evaluateAny(cell, sheetIndex, rowIndex, columnIndex, tracker); + } + public FreeRefFunction findUserDefinedFunction(String functionName) { + return _udfFinder.findFunction(functionName); + } /** * Whether to ignore missing references to external workbooks and diff --git a/src/java/org/apache/poi/ss/formula/functions/Indirect.java b/src/java/org/apache/poi/ss/formula/functions/Indirect.java index 7774af506..1ea3dcd38 100644 --- a/src/java/org/apache/poi/ss/formula/functions/Indirect.java +++ b/src/java/org/apache/poi/ss/formula/functions/Indirect.java @@ -17,13 +17,18 @@ 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.ErrorEval; import org.apache.poi.ss.formula.eval.EvaluationException; import org.apache.poi.ss.formula.eval.MissingArgEval; import org.apache.poi.ss.formula.eval.OperandResolver; 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

@@ -42,198 +47,210 @@ import org.apache.poi.ss.formula.OperationEvaluationContext; */ public final class Indirect implements FreeRefFunction { - public static final FreeRefFunction instance = new Indirect(); + public static final FreeRefFunction instance = new Indirect(); - private Indirect() { - // enforce singleton - } + private Indirect() { + // enforce singleton + } - public ValueEval evaluate(ValueEval[] args, OperationEvaluationContext ec) { - if (args.length < 1) { - return ErrorEval.VALUE_INVALID; - } + public ValueEval evaluate(ValueEval[] args, OperationEvaluationContext ec) { + if (args.length < 1) { + return ErrorEval.VALUE_INVALID; + } - boolean isA1style; - String text; - try { - ValueEval ve = OperandResolver.getSingleValue(args[0], ec.getRowIndex(), ec - .getColumnIndex()); - text = OperandResolver.coerceValueToString(ve); - switch (args.length) { - case 1: - isA1style = true; - break; - case 2: - isA1style = evaluateBooleanArg(args[1], ec); - break; - default: - return ErrorEval.VALUE_INVALID; - } - } catch (EvaluationException e) { - return e.getErrorEval(); - } + boolean isA1style; + String text; + try { + ValueEval ve = OperandResolver.getSingleValue(args[0], ec.getRowIndex(), ec + .getColumnIndex()); + text = OperandResolver.coerceValueToString(ve); + switch (args.length) { + case 1: + isA1style = true; + break; + case 2: + isA1style = evaluateBooleanArg(args[1], ec); + break; + default: + return ErrorEval.VALUE_INVALID; + } + } catch (EvaluationException e) { + return e.getErrorEval(); + } - return evaluateIndirect(ec, text, isA1style); - } + return evaluateIndirect(ec, text, isA1style); + } - private static boolean evaluateBooleanArg(ValueEval arg, OperationEvaluationContext ec) - throws EvaluationException { - ValueEval ve = OperandResolver.getSingleValue(arg, ec.getRowIndex(), ec.getColumnIndex()); + private static boolean evaluateBooleanArg(ValueEval arg, OperationEvaluationContext ec) + throws EvaluationException { + ValueEval ve = OperandResolver.getSingleValue(arg, ec.getRowIndex(), ec.getColumnIndex()); - if (ve == BlankEval.instance || ve == MissingArgEval.instance) { - return false; - } - // numeric quantities follow standard boolean conversion rules - // for strings, only "TRUE" and "FALSE" (case insensitive) are valid - return OperandResolver.coerceValueToBoolean(ve, false).booleanValue(); - } + if (ve == BlankEval.instance || ve == MissingArgEval.instance) { + return false; + } + // numeric quantities follow standard boolean conversion rules + // for strings, only "TRUE" and "FALSE" (case insensitive) are valid + return OperandResolver.coerceValueToBoolean(ve, false).booleanValue(); + } - private static ValueEval evaluateIndirect(OperationEvaluationContext ec, String text, - boolean isA1style) { - // Search backwards for '!' because sheet names can contain '!' - int plingPos = text.lastIndexOf('!'); + private static ValueEval evaluateIndirect(final OperationEvaluationContext ec, String text, + boolean isA1style) { + + // Search backwards for '!' because sheet names can contain '!' + int plingPos = text.lastIndexOf('!'); - String workbookName; - String sheetName; - String refText; // whitespace around this gets trimmed OK - if (plingPos < 0) { - workbookName = null; - sheetName = null; - refText = text; - } else { - String[] parts = parseWorkbookAndSheetName(text.subSequence(0, plingPos)); - if (parts == null) { - return ErrorEval.REF_INVALID; - } - workbookName = parts[0]; - sheetName = parts[1]; - refText = text.substring(plingPos + 1); - } + String workbookName; + String sheetName; + String refText; // whitespace around this gets trimmed OK + if (plingPos < 0) { + workbookName = null; + sheetName = null; + refText = text; + } else { + String[] parts = parseWorkbookAndSheetName(text.subSequence(0, plingPos)); + if (parts == null) { + return ErrorEval.REF_INVALID; + } + workbookName = parts[0]; + sheetName = parts[1]; + refText = text.substring(plingPos + 1); + } - String refStrPart1; - String refStrPart2; + if (Table.isStructuredReference.matcher(refText).matches()) { + // 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) { - 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); - } + /** + * @return array of length 2: {workbookName, sheetName,}. Second element will always be + * present. First element may be null if sheetName is unqualified. + * Returns null if text cannot be parsed. + */ + private static String[] parseWorkbookAndSheetName(CharSequence text) { + int lastIx = text.length() - 1; + 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; + } - /** - * @return array of length 2: {workbookName, sheetName,}. Second element will always be - * present. First element may be null if sheetName is unqualified. - * Returns null if text cannot be parsed. - */ - private static String[] parseWorkbookAndSheetName(CharSequence text) { - int lastIx = text.length() - 1; - 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 + String sheetName = unescapeString(text.subSequence(sheetStartPos, lastIx)); + if (sheetName == null) { // note - when quoted, sheetName can + // start/end with whitespace + return null; + } + return new String[] { wbName, sheetName, }; + } - // else - just sheet name - String sheetName = unescapeString(text.subSequence(sheetStartPos, lastIx)); - if (sheetName == null) { // note - when quoted, sheetName can - // start/end with whitespace - return null; - } - return new String[] { wbName, sheetName, }; - } + if (firstChar == '[') { + int rbPos = text.toString().lastIndexOf(']'); + if (rbPos < 0) { + return null; + } + CharSequence wbName = text.subSequence(1, rbPos); + 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(']'); - if (rbPos < 0) { - return null; - } - CharSequence wbName = text.subSequence(1, rbPos); - 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(), }; - } + /** + * @return null if there is a syntax error in any escape sequence + * (the typical syntax error is a single quote character not followed by another). + */ + private static String unescapeString(CharSequence text) { + int len = text.length(); + StringBuilder sb = new StringBuilder(len); + int i = 0; + while (i < len) { + char ch = text.charAt(i); + if (ch == '\'') { + // every quote must be followed by another + i++; + if (i >= len) { + return null; + } + ch = text.charAt(i); + if (ch != '\'') { + return null; + } + } + sb.append(ch); + i++; + } + return sb.toString(); + } - /** - * @return null if there is a syntax error in any escape sequence - * (the typical syntax error is a single quote character not followed by another). - */ - private static String unescapeString(CharSequence text) { - int len = text.length(); - StringBuilder sb = new StringBuilder(len); - int i = 0; - while (i < len) { - char ch = text.charAt(i); - if (ch == '\'') { - // every quote must be followed by another - 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; - } + 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; + } } diff --git a/src/java/org/apache/poi/ss/formula/ptg/Area3DPxg.java b/src/java/org/apache/poi/ss/formula/ptg/Area3DPxg.java index 665ac98cf..2916211b6 100644 --- a/src/java/org/apache/poi/ss/formula/ptg/Area3DPxg.java +++ b/src/java/org/apache/poi/ss/formula/ptg/Area3DPxg.java @@ -36,9 +36,9 @@ public final class Area3DPxg extends AreaPtgBase implements Pxg3D { private String firstSheetName; private String lastSheetName; - public Area3DPxg(int externalWorkbookNumber, SheetIdentifier sheetName, String arearef) { - this(externalWorkbookNumber, sheetName, new AreaReference(arearef)); - } + public Area3DPxg(int externalWorkbookNumber, SheetIdentifier sheetName, String arearef) { + this(externalWorkbookNumber, sheetName, new AreaReference(arearef)); + } public Area3DPxg(int externalWorkbookNumber, SheetIdentifier sheetName, AreaReference arearef) { super(arearef); this.externalWorkbookNumber = externalWorkbookNumber; @@ -57,8 +57,8 @@ public final class Area3DPxg extends AreaPtgBase implements Pxg3D { this(-1, sheetName, arearef); } - @Override - public String toString() { + @Override + public String toString() { StringBuffer sb = new StringBuffer(); sb.append(getClass().getName()); sb.append(" ["); @@ -76,8 +76,8 @@ public final class Area3DPxg extends AreaPtgBase implements Pxg3D { sb.append(formatReferenceAsString()); sb.append("]"); return sb.toString(); - } - + } + public int getExternalWorkbookNumber() { return externalWorkbookNumber; } diff --git a/src/java/org/apache/poi/ss/formula/ptg/AreaPtgBase.java b/src/java/org/apache/poi/ss/formula/ptg/AreaPtgBase.java index bc808b0f0..816287620 100644 --- a/src/java/org/apache/poi/ss/formula/ptg/AreaPtgBase.java +++ b/src/java/org/apache/poi/ss/formula/ptg/AreaPtgBase.java @@ -31,272 +31,272 @@ import org.apache.poi.util.LittleEndianOutput; * @author Jason Height (jheight at chariot dot net dot au) */ public abstract class AreaPtgBase extends OperandPtg implements AreaI { - /** - * TODO - (May-2008) fix subclasses of AreaPtg 'AreaN~' which are used in shared formulas. - * see similar comment in ReferencePtg - */ - protected final RuntimeException notImplemented() { - return new RuntimeException("Coding Error: This method should never be called. This ptg should be converted"); - } + /** + * TODO - (May-2008) fix subclasses of AreaPtg 'AreaN~' which are used in shared formulas. + * see similar comment in ReferencePtg + */ + protected final RuntimeException notImplemented() { + return new RuntimeException("Coding Error: This method should never be called. This ptg should be converted"); + } - /** zero based, unsigned 16 bit */ - private int field_1_first_row; - /** zero based, unsigned 16 bit */ - private int field_2_last_row; - /** zero based, unsigned 8 bit */ - private int field_3_first_column; //BitFields: (first row relative, first col relative, first column number) - /** zero based, unsigned 8 bit */ - private int field_4_last_column; //BitFields: (last row relative, last col relative, last column number) + /** zero based, unsigned 16 bit */ + private int field_1_first_row; + /** zero based, unsigned 16 bit */ + private int field_2_last_row; + /** zero based, unsigned 8 bit */ + private int field_3_first_column; //BitFields: (first row relative, first col relative, first column number) + /** zero based, unsigned 8 bit */ + 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 colRelative = BitFieldFactory.getInstance(0x4000); - private final static BitField columnMask = BitFieldFactory.getInstance(0x3FFF); + private final static BitField rowRelative = BitFieldFactory.getInstance(0x8000); + private final static BitField colRelative = BitFieldFactory.getInstance(0x4000); + private final static BitField columnMask = BitFieldFactory.getInstance(0x3FFF); - protected AreaPtgBase() { - // do nothing - } + protected AreaPtgBase() { + // do nothing + } - protected AreaPtgBase(AreaReference ar) { - CellReference firstCell = ar.getFirstCell(); - CellReference lastCell = ar.getLastCell(); - setFirstRow(firstCell.getRow()); - setFirstColumn(firstCell.getCol() == -1 ? 0 : firstCell.getCol()); - setLastRow(lastCell.getRow()); - setLastColumn(lastCell.getCol() == -1 ? 0xFF : lastCell.getCol()); - setFirstColRelative(!firstCell.isColAbsolute()); - setLastColRelative(!lastCell.isColAbsolute()); - setFirstRowRelative(!firstCell.isRowAbsolute()); - setLastRowRelative(!lastCell.isRowAbsolute()); - } + protected AreaPtgBase(AreaReference ar) { + CellReference firstCell = ar.getFirstCell(); + CellReference lastCell = ar.getLastCell(); + setFirstRow(firstCell.getRow()); + setFirstColumn(firstCell.getCol() == -1 ? 0 : firstCell.getCol()); + setLastRow(lastCell.getRow()); + setLastColumn(lastCell.getCol() == -1 ? 0xFF : lastCell.getCol()); + setFirstColRelative(!firstCell.isColAbsolute()); + setLastColRelative(!lastCell.isColAbsolute()); + setFirstRowRelative(!firstCell.isRowAbsolute()); + setLastRowRelative(!lastCell.isRowAbsolute()); + } - protected AreaPtgBase(int firstRow, int lastRow, int firstColumn, int lastColumn, - boolean firstRowRelative, boolean lastRowRelative, boolean firstColRelative, boolean lastColRelative) { + protected AreaPtgBase(int firstRow, int lastRow, int firstColumn, int lastColumn, + boolean firstRowRelative, boolean lastRowRelative, boolean firstColRelative, boolean lastColRelative) { - if (lastRow >= firstRow) { - setFirstRow(firstRow); - setLastRow(lastRow); - setFirstRowRelative(firstRowRelative); - setLastRowRelative(lastRowRelative); - } else { - setFirstRow(lastRow); - setLastRow(firstRow); - setFirstRowRelative(lastRowRelative); - setLastRowRelative(firstRowRelative); - } + if (lastRow >= firstRow) { + setFirstRow(firstRow); + setLastRow(lastRow); + setFirstRowRelative(firstRowRelative); + setLastRowRelative(lastRowRelative); + } else { + setFirstRow(lastRow); + setLastRow(firstRow); + setFirstRowRelative(lastRowRelative); + setLastRowRelative(firstRowRelative); + } - if (lastColumn >= firstColumn) { - setFirstColumn(firstColumn); - setLastColumn(lastColumn); - setFirstColRelative(firstColRelative); - setLastColRelative(lastColRelative); - } else { - setFirstColumn(lastColumn); - setLastColumn(firstColumn); - setFirstColRelative(lastColRelative); - setLastColRelative(firstColRelative); - } - } - - /** - * 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. - * - *

For example, $E5:B$10 becomes B5:$E$10

- */ - public void sortTopLeftToBottomRight() { - if (getFirstRow() > getLastRow()) { - //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 - final int firstRow = getFirstRow(); - final boolean firstRowRel = isFirstRowRelative(); - setFirstRow(getLastRow()); - setFirstRowRelative(isLastRowRelative()); - setLastRow(firstRow); - setLastRowRelative(firstRowRel); - } - if (getFirstColumn() > getLastColumn()) { - //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 - final int firstCol = getFirstColumn(); - final boolean firstColRel = isFirstColRelative(); - setFirstColumn(getLastColumn()); - setFirstColRelative(isLastColRelative()); - setLastColumn(firstCol); - setLastColRelative(firstColRel); - } - } + if (lastColumn >= firstColumn) { + setFirstColumn(firstColumn); + setLastColumn(lastColumn); + setFirstColRelative(firstColRelative); + setLastColRelative(lastColRelative); + } else { + setFirstColumn(lastColumn); + setLastColumn(firstColumn); + setFirstColRelative(lastColRelative); + setLastColRelative(firstColRelative); + } + } + + /** + * 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. + * + *

For example, $E5:B$10 becomes B5:$E$10

+ */ + public void sortTopLeftToBottomRight() { + if (getFirstRow() > getLastRow()) { + //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 + final int firstRow = getFirstRow(); + final boolean firstRowRel = isFirstRowRelative(); + setFirstRow(getLastRow()); + setFirstRowRelative(isLastRowRelative()); + setLastRow(firstRow); + setLastRowRelative(firstRowRel); + } + if (getFirstColumn() > getLastColumn()) { + //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 + final int firstCol = getFirstColumn(); + final boolean firstColRel = isFirstColRelative(); + setFirstColumn(getLastColumn()); + setFirstColRelative(isLastColRelative()); + setLastColumn(firstCol); + setLastColRelative(firstColRel); + } + } - protected final void readCoordinates(LittleEndianInput in) { - field_1_first_row = in.readUShort(); - field_2_last_row = in.readUShort(); - field_3_first_column = in.readUShort(); - field_4_last_column = in.readUShort(); - } - protected final void writeCoordinates(LittleEndianOutput out) { - out.writeShort(field_1_first_row); - out.writeShort(field_2_last_row); - out.writeShort(field_3_first_column); - out.writeShort(field_4_last_column); - } + protected final void readCoordinates(LittleEndianInput in) { + field_1_first_row = in.readUShort(); + field_2_last_row = in.readUShort(); + field_3_first_column = in.readUShort(); + field_4_last_column = in.readUShort(); + } + protected final void writeCoordinates(LittleEndianOutput out) { + out.writeShort(field_1_first_row); + out.writeShort(field_2_last_row); + out.writeShort(field_3_first_column); + out.writeShort(field_4_last_column); + } - /** - * @return the first row in the area - */ - public final int getFirstRow() { - return field_1_first_row; - } + /** + * @return the first row in the area + */ + public final int getFirstRow() { + return field_1_first_row; + } - /** - * sets the first row - * @param rowIx number (0-based) - */ - public final void setFirstRow(int rowIx) { - field_1_first_row = rowIx; - } + /** + * sets the first row + * @param rowIx number (0-based) + */ + public final void setFirstRow(int rowIx) { + field_1_first_row = rowIx; + } - /** - * @return last row in the range (x2 in x1,y1-x2,y2) - */ - public final int getLastRow() { - return field_2_last_row; - } + /** + * @return last row in the range (x2 in x1,y1-x2,y2) + */ + public final int getLastRow() { + return field_2_last_row; + } - /** - * @param rowIx last row number in the area - */ - public final void setLastRow(int rowIx) { - field_2_last_row = rowIx; - } + /** + * @param rowIx last row number in the area + */ + public final void setLastRow(int rowIx) { + field_2_last_row = rowIx; + } - /** - * @return the first column number in the area. - */ - public final int getFirstColumn() { - return columnMask.getValue(field_3_first_column); - } + /** + * @return the first column number in the area. + */ + public final int getFirstColumn() { + return columnMask.getValue(field_3_first_column); + } - /** - * @return the first column number + the options bit settings unstripped - */ - public final short getFirstColumnRaw() { - return (short) field_3_first_column; // TODO - } + /** + * @return the first column number + the options bit settings unstripped + */ + public final short getFirstColumnRaw() { + return (short) field_3_first_column; // TODO + } - /** - * @return whether or not the first row is a relative reference or not. - */ - public final boolean isFirstRowRelative() { - return rowRelative.isSet(field_3_first_column); - } + /** + * @return whether or not the first row is a relative reference or not. + */ + public final boolean isFirstRowRelative() { + return rowRelative.isSet(field_3_first_column); + } - /** - * sets the first row to relative or not - * @param rel is relative or not. - */ - public final void setFirstRowRelative(boolean rel) { - field_3_first_column=rowRelative.setBoolean(field_3_first_column,rel); - } + /** + * sets the first row to relative or not + * @param rel is relative or not. + */ + public final void setFirstRowRelative(boolean rel) { + field_3_first_column=rowRelative.setBoolean(field_3_first_column,rel); + } - /** - * @return isrelative first column to relative or not - */ - public final boolean isFirstColRelative() { - return colRelative.isSet(field_3_first_column); - } + /** + * @return isrelative first column to relative or not + */ + public final boolean isFirstColRelative() { + return colRelative.isSet(field_3_first_column); + } - /** - * set whether the first column is relative - */ - public final void setFirstColRelative(boolean rel) { - field_3_first_column=colRelative.setBoolean(field_3_first_column,rel); - } + /** + * set whether the first column is relative + */ + public final void setFirstColRelative(boolean rel) { + field_3_first_column=colRelative.setBoolean(field_3_first_column,rel); + } - /** - * set the first column in the area - */ - public final void setFirstColumn(int colIx) { - field_3_first_column=columnMask.setValue(field_3_first_column, colIx); - } + /** + * set the first column in the area + */ + public final void setFirstColumn(int colIx) { + field_3_first_column=columnMask.setValue(field_3_first_column, colIx); + } - /** - * set the first column irrespective of the bitmasks - */ - public final void setFirstColumnRaw(int column) { - field_3_first_column = column; - } + /** + * set the first column irrespective of the bitmasks + */ + public final void setFirstColumnRaw(int column) { + field_3_first_column = column; + } - /** - * @return lastcolumn in the area - */ - public final int getLastColumn() { - return columnMask.getValue(field_4_last_column); - } + /** + * @return lastcolumn in the area + */ + public final int getLastColumn() { + return columnMask.getValue(field_4_last_column); + } - /** - * @return last column and bitmask (the raw field) - */ - public final short getLastColumnRaw() { - return (short) field_4_last_column; - } + /** + * @return last column and bitmask (the raw field) + */ + public final short getLastColumnRaw() { + return (short) field_4_last_column; + } - /** - * @return last row relative or not - */ - public final boolean isLastRowRelative() { - return rowRelative.isSet(field_4_last_column); - } + /** + * @return last row relative or not + */ + public final boolean isLastRowRelative() { + return rowRelative.isSet(field_4_last_column); + } - /** - * set whether the last row is relative or not - * @param rel true if the last row relative, else - * false - */ - public final void setLastRowRelative(boolean rel) { - field_4_last_column=rowRelative.setBoolean(field_4_last_column,rel); - } + /** + * set whether the last row is relative or not + * @param rel true if the last row relative, else + * false + */ + public final void setLastRowRelative(boolean rel) { + field_4_last_column=rowRelative.setBoolean(field_4_last_column,rel); + } - /** - * @return lastcol relative or not - */ - public final boolean isLastColRelative() { - return colRelative.isSet(field_4_last_column); - } + /** + * @return lastcol relative or not + */ + public final boolean isLastColRelative() { + return colRelative.isSet(field_4_last_column); + } - /** - * set whether the last column should be relative or not - */ - public final void setLastColRelative(boolean rel) { - field_4_last_column=colRelative.setBoolean(field_4_last_column,rel); - } + /** + * set whether the last column should be relative or not + */ + public final void setLastColRelative(boolean rel) { + field_4_last_column=colRelative.setBoolean(field_4_last_column,rel); + } - /** - * set the last column in the area - */ - public final void setLastColumn(int colIx) { - field_4_last_column=columnMask.setValue(field_4_last_column, colIx); - } + /** + * set the last column in the area + */ + public final void setLastColumn(int colIx) { + field_4_last_column=columnMask.setValue(field_4_last_column, colIx); + } - /** - * set the last column irrespective of the bitmasks - */ - public final void setLastColumnRaw(short column) { - field_4_last_column = column; - } - protected final String formatReferenceAsString() { - CellReference topLeft = new CellReference(getFirstRow(),getFirstColumn(),!isFirstRowRelative(),!isFirstColRelative()); - CellReference botRight = new CellReference(getLastRow(),getLastColumn(),!isLastRowRelative(),!isLastColRelative()); + /** + * set the last column irrespective of the bitmasks + */ + public final void setLastColumnRaw(short column) { + field_4_last_column = column; + } + protected final String formatReferenceAsString() { + CellReference topLeft = new CellReference(getFirstRow(),getFirstColumn(),!isFirstRowRelative(),!isFirstColRelative()); + CellReference botRight = new CellReference(getLastRow(),getLastColumn(),!isLastRowRelative(),!isLastColRelative()); - if(AreaReference.isWholeColumnReference(SpreadsheetVersion.EXCEL97, topLeft, botRight)) { - return (new AreaReference(topLeft, botRight)).formatAsString(); - } - return topLeft.formatAsString() + ":" + botRight.formatAsString(); - } + if(AreaReference.isWholeColumnReference(SpreadsheetVersion.EXCEL97, topLeft, botRight)) { + return (new AreaReference(topLeft, botRight)).formatAsString(); + } + return topLeft.formatAsString() + ":" + botRight.formatAsString(); + } - public String toFormulaString() { - return formatReferenceAsString(); - } + public String toFormulaString() { + return formatReferenceAsString(); + } - public byte getDefaultOperandClass() { - return Ptg.CLASS_REF; - } + public byte getDefaultOperandClass() { + return Ptg.CLASS_REF; + } } diff --git a/src/ooxml/java/org/apache/poi/xssf/streaming/SXSSFEvaluationWorkbook.java b/src/ooxml/java/org/apache/poi/xssf/streaming/SXSSFEvaluationWorkbook.java index b873588db..3582daefc 100644 --- a/src/ooxml/java/org/apache/poi/xssf/streaming/SXSSFEvaluationWorkbook.java +++ b/src/ooxml/java/org/apache/poi/xssf/streaming/SXSSFEvaluationWorkbook.java @@ -28,7 +28,7 @@ import org.apache.poi.xssf.usermodel.BaseXSSFEvaluationWorkbook; * SXSSF wrapper around the SXSSF and XSSF workbooks */ public final class SXSSFEvaluationWorkbook extends BaseXSSFEvaluationWorkbook { - private SXSSFWorkbook _uBook; + private final SXSSFWorkbook _uBook; public static SXSSFEvaluationWorkbook create(SXSSFWorkbook book) { if (book == null) { @@ -41,16 +41,19 @@ public final class SXSSFEvaluationWorkbook extends BaseXSSFEvaluationWorkbook { super(book.getXSSFWorkbook()); _uBook = book; } - + + @Override public int getSheetIndex(EvaluationSheet evalSheet) { SXSSFSheet sheet = ((SXSSFEvaluationSheet)evalSheet).getSXSSFSheet(); return _uBook.getSheetIndex(sheet); } - + + @Override public EvaluationSheet getSheet(int sheetIndex) { return new SXSSFEvaluationSheet(_uBook.getSheetAt(sheetIndex)); } - + + @Override public Ptg[] getFormulaTokens(EvaluationCell evalCell) { SXSSFCell cell = ((SXSSFEvaluationCell)evalCell).getSXSSFCell(); SXSSFEvaluationWorkbook frBook = SXSSFEvaluationWorkbook.create(_uBook); diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/BaseXSSFEvaluationWorkbook.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/BaseXSSFEvaluationWorkbook.java index 4a6882ac4..28a9ecf7d 100644 --- a/src/ooxml/java/org/apache/poi/xssf/usermodel/BaseXSSFEvaluationWorkbook.java +++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/BaseXSSFEvaluationWorkbook.java @@ -17,7 +17,10 @@ package org.apache.poi.xssf.usermodel; +import java.util.HashMap; import java.util.List; +import java.util.Locale; +import java.util.Map; import org.apache.poi.ss.SpreadsheetVersion; 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.udf.IndexedUDFFinder; 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.CellReference; 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 */ public abstract class BaseXSSFEvaluationWorkbook implements FormulaRenderingWorkbook, EvaluationWorkbook, FormulaParsingWorkbook { - protected final XSSFWorkbook _uBook; + protected final XSSFWorkbook _uBook; - protected BaseXSSFEvaluationWorkbook(XSSFWorkbook book) { - _uBook = book; - } + protected BaseXSSFEvaluationWorkbook(XSSFWorkbook book) { + _uBook = book; + } - private int convertFromExternalSheetIndex(int externSheetIndex) { - return externSheetIndex; - } - /** - * XSSF doesn't use external sheet indexes, so when asked treat - * it just as a local index - */ - public int convertFromExternSheetIndex(int externSheetIndex) { - return externSheetIndex; - } - /** - * @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. - * Fairly easy on XSSF (we think...) since the internal and external - * indices are the same - */ - private int convertToExternalSheetIndex(int sheetIndex) { - return sheetIndex; - } + private int convertFromExternalSheetIndex(int externSheetIndex) { + return externSheetIndex; + } + /** + * XSSF doesn't use external sheet indexes, so when asked treat + * it just as a local index + */ + public int convertFromExternSheetIndex(int externSheetIndex) { + return externSheetIndex; + } + /** + * @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. + * Fairly easy on XSSF (we think...) since the internal and external + * indices are the same + */ + private int convertToExternalSheetIndex(int sheetIndex) { + return sheetIndex; + } public int getExternalSheetIndex(String 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) - * @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 - * if named range scope is global to the workbook, sheetIndex is -1. - * @return If name is a named range in the workbook, returns - * EvaluationName corresponding to that named range - * Returns null if there is no named range with the same name and scope in the workbook - */ - public EvaluationName getName(String name, int sheetIndex) { - for (int i = 0; i < _uBook.getNumberOfNames(); i++) { - XSSFName nm = _uBook.getNameAt(i); - String nameText = nm.getNameName(); - int nameSheetindex = nm.getSheetIndex(); - if (name.equalsIgnoreCase(nameText) && - (nameSheetindex == -1 || nameSheetindex == sheetIndex)) { - return new Name(nm, i, this); - } - } - return sheetIndex == -1 ? null : getName(name, -1); - } + /** + * Return EvaluationName wrapper around the matching XSSFName (named range) + * @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 + * if named range scope is global to the workbook, sheetIndex is -1. + * @return If name is a named range in the workbook, returns + * EvaluationName corresponding to that named range + * Returns null if there is no named range with the same name and scope in the workbook + */ + public EvaluationName getName(String name, int sheetIndex) { + for (int i = 0; i < _uBook.getNumberOfNames(); i++) { + XSSFName nm = _uBook.getNameAt(i); + String nameText = nm.getNameName(); + int nameSheetindex = nm.getSheetIndex(); + if (name.equalsIgnoreCase(nameText) && + (nameSheetindex == -1 || nameSheetindex == sheetIndex)) { + return new Name(nm, i, this); + } + } + return sheetIndex == -1 ? null : getName(name, -1); + } - public String getSheetName(int sheetIndex) { - return _uBook.getSheetName(sheetIndex); - } - - public ExternalName getExternalName(int externSheetIndex, int externNameIndex) { + public String getSheetName(int sheetIndex) { + return _uBook.getSheetName(sheetIndex); + } + + public ExternalName getExternalName(int externSheetIndex, int externNameIndex) { 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) { // External reference - reference is 1 based, link table is 0 based int linkNumber = externalWorkbookNumber - 1; @@ -186,7 +191,7 @@ public abstract class BaseXSSFEvaluationWorkbook implements FormulaRenderingWork int nameIdx = _uBook.getNameIndex(nameName); return new ExternalName(nameName, nameIdx, 0); // TODO Is this right? } - + } /** @@ -194,7 +199,7 @@ public abstract class BaseXSSFEvaluationWorkbook implements FormulaRenderingWork */ @Override 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(); FreeRefFunction func = udfFinder.findFunction(name); if (func != null) { @@ -223,7 +228,7 @@ public abstract class BaseXSSFEvaluationWorkbook implements FormulaRenderingWork } else { return new NameXPxg(sheetName, name); } - } + } public Ptg get3DReferencePtg(CellReference cell, SheetIdentifier sheet) { if (sheet._bookName != null) { int bookIndex = resolveBookIndex(sheet._bookName); @@ -259,102 +264,151 @@ public abstract class BaseXSSFEvaluationWorkbook implements FormulaRenderingWork return name; } - public ExternalSheet getExternalSheet(int externSheetIndex) { - throw new IllegalStateException("HSSF-style external references are not supported for XSSF"); - } - public ExternalSheet getExternalSheet(String firstSheetName, String lastSheetName, int externalWorkbookNumber) { - String workbookName; - if (externalWorkbookNumber > 0) { - // External reference - reference is 1 based, link table is 0 based - int linkNumber = externalWorkbookNumber - 1; - ExternalLinksTable linkTable = _uBook.getExternalLinksTable().get(linkNumber); - workbookName = linkTable.getLinkedFileName(); - } else { - // Internal reference - workbookName = null; - } - - if (lastSheetName == null || firstSheetName.equals(lastSheetName)) { - return new ExternalSheet(workbookName, firstSheetName); - } else { - return new ExternalSheetRange(workbookName, firstSheetName, lastSheetName); - } + public ExternalSheet getExternalSheet(int externSheetIndex) { + throw new IllegalStateException("HSSF-style external references are not supported for XSSF"); + } + public ExternalSheet getExternalSheet(String firstSheetName, String lastSheetName, int externalWorkbookNumber) { + String workbookName; + if (externalWorkbookNumber > 0) { + // External reference - reference is 1 based, link table is 0 based + int linkNumber = externalWorkbookNumber - 1; + ExternalLinksTable linkTable = _uBook.getExternalLinksTable().get(linkNumber); + workbookName = linkTable.getLinkedFileName(); + } else { + // Internal reference + workbookName = null; + } + + if (lastSheetName == null || firstSheetName.equals(lastSheetName)) { + return new ExternalSheet(workbookName, firstSheetName); + } else { + return new ExternalSheetRange(workbookName, firstSheetName, lastSheetName); + } } @NotImplemented public int getExternalSheetIndex(String workbookName, String sheetName) { - throw new RuntimeException("not implemented yet"); - } - public int getSheetIndex(String sheetName) { - return _uBook.getSheetIndex(sheetName); - } + throw new RuntimeException("not implemented yet"); + } + public int getSheetIndex(String sheetName) { + return _uBook.getSheetIndex(sheetName); + } - public String getSheetFirstNameByExternSheet(int externSheetIndex) { - int sheetIndex = convertFromExternalSheetIndex(externSheetIndex); - return _uBook.getSheetName(sheetIndex); - } + public String getSheetFirstNameByExternSheet(int externSheetIndex) { + int sheetIndex = convertFromExternalSheetIndex(externSheetIndex); + return _uBook.getSheetName(sheetIndex); + } public String getSheetLastNameByExternSheet(int externSheetIndex) { // XSSF does multi-sheet references differently, so this is the same as the first return getSheetFirstNameByExternSheet(externSheetIndex); } - public String getNameText(NamePtg namePtg) { - return _uBook.getNameAt(namePtg.getIndex()).getNameName(); - } - public EvaluationName getName(NamePtg namePtg) { - int ix = namePtg.getIndex(); - return new Name(_uBook.getNameAt(ix), ix, this); - } - @Override - public XSSFName createName() { - return _uBook.createName(); - } - + public String getNameText(NamePtg namePtg) { + return _uBook.getNameAt(namePtg.getIndex()).getNameName(); + } + public EvaluationName getName(NamePtg namePtg) { + int ix = namePtg.getIndex(); + return new Name(_uBook.getNameAt(ix), ix, this); + } + @Override + public XSSFName 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 _tableCache = null; + private Map getTableCache() { + if ( _tableCache != null ) { + return _tableCache; + } + // FIXME: use org.apache.commons.collections.map.CaseInsensitiveMap + _tableCache = new HashMap(); + + 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 name, or null if no table is named name. + * @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(){ return _uBook.getUDFFinder(); } - private static final class Name implements EvaluationName { + private static final class Name implements EvaluationName { - private final XSSFName _nameRecord; - private final int _index; - private final FormulaParsingWorkbook _fpBook; + private final XSSFName _nameRecord; + private final int _index; + private final FormulaParsingWorkbook _fpBook; - public Name(XSSFName name, int index, FormulaParsingWorkbook fpBook) { - _nameRecord = name; - _index = index; - _fpBook = fpBook; - } + public Name(XSSFName name, int index, FormulaParsingWorkbook fpBook) { + _nameRecord = name; + _index = index; + _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() { - return _nameRecord.getNameName(); - } + public String getNameText() { + return _nameRecord.getNameName(); + } - public boolean hasFormula() { - // TODO - no idea if this is right - CTDefinedName ctn = _nameRecord.getCTName(); - String strVal = ctn.getStringValue(); - return !ctn.getFunction() && strVal != null && strVal.length() > 0; - } + public boolean hasFormula() { + // TODO - no idea if this is right + CTDefinedName ctn = _nameRecord.getCTName(); + String strVal = ctn.getStringValue(); + return !ctn.getFunction() && strVal != null && strVal.length() > 0; + } - public boolean isFunctionName() { - return _nameRecord.isFunctionName(); - } + public boolean isFunctionName() { + return _nameRecord.isFunctionName(); + } - public boolean isRange() { - return hasFormula(); // TODO - is this right? - } - public NamePtg createPtg() { - return new NamePtg(_index); - } - } + public boolean isRange() { + return hasFormula(); // TODO - is this right? + } + public NamePtg createPtg() { + return new NamePtg(_index); + } + } - public SpreadsheetVersion getSpreadsheetVersion(){ - return SpreadsheetVersion.EXCEL2007; - } + public SpreadsheetVersion getSpreadsheetVersion(){ + return SpreadsheetVersion.EXCEL2007; + } } diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFCell.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFCell.java index 29aaa867e..ef2a7d94e 100644 --- a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFCell.java +++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFCell.java @@ -406,9 +406,9 @@ public final class XSSFCell implements Cell { if (cachedValueType != expectedTypeCode) { throw typeMismatch(expectedTypeCode, cachedValueType, true); } - } + } - /** + /** * Set a string value for the cell. * * @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()); 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, getRowIndex() - ref.getFirstRow(), getColumnIndex() - ref.getFirstColumn()); return FormulaRenderer.toFormulaString(fpb, fmla); @@ -550,7 +550,7 @@ public final class XSSFCell implements Cell { XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(wb); //validate through the FormulaParser - FormulaParser.parse(formula, fpb, formulaType, wb.getSheetIndex(getSheet())); + FormulaParser.parse(formula, fpb, formulaType, wb.getSheetIndex(getSheet()), getRowIndex()); CTCellFormula f = CTCellFormula.Factory.newInstance(); f.setStringValue(formula); @@ -925,8 +925,8 @@ public final class XSSFCell implements Cell { throw new IllegalArgumentException("Illegal cell type: " + cellType); } if (cellType != CELL_TYPE_FORMULA && _cell.isSetF()) { - _cell.unsetF(); - } + _cell.unsetF(); + } } /** diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFEvaluationWorkbook.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFEvaluationWorkbook.java index 70fc72067..5fead42f1 100644 --- a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFEvaluationWorkbook.java +++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFEvaluationWorkbook.java @@ -27,29 +27,32 @@ import org.apache.poi.ss.formula.ptg.Ptg; * Internal POI use only */ public final class XSSFEvaluationWorkbook extends BaseXSSFEvaluationWorkbook { - public static XSSFEvaluationWorkbook create(XSSFWorkbook book) { - if (book == null) { - return null; - } - return new XSSFEvaluationWorkbook(book); - } + public static XSSFEvaluationWorkbook create(XSSFWorkbook book) { + if (book == null) { + return null; + } + return new XSSFEvaluationWorkbook(book); + } - private XSSFEvaluationWorkbook(XSSFWorkbook book) { - super(book); - } + private XSSFEvaluationWorkbook(XSSFWorkbook book) { + super(book); + } - public int getSheetIndex(EvaluationSheet evalSheet) { - XSSFSheet sheet = ((XSSFEvaluationSheet)evalSheet).getXSSFSheet(); - return _uBook.getSheetIndex(sheet); - } + @Override + public int getSheetIndex(EvaluationSheet evalSheet) { + XSSFSheet sheet = ((XSSFEvaluationSheet)evalSheet).getXSSFSheet(); + return _uBook.getSheetIndex(sheet); + } - public EvaluationSheet getSheet(int sheetIndex) { - return new XSSFEvaluationSheet(_uBook.getSheetAt(sheetIndex)); - } - + @Override + public EvaluationSheet getSheet(int sheetIndex) { + return new XSSFEvaluationSheet(_uBook.getSheetAt(sheetIndex)); + } + + @Override public Ptg[] getFormulaTokens(EvaluationCell evalCell) { XSSFCell cell = ((XSSFEvaluationCell)evalCell).getXSSFCell(); 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()); } } diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFName.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFName.java index ecbec23b5..be68c3050 100644 --- a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFName.java +++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFName.java @@ -192,7 +192,7 @@ public final class XSSFName implements Name { public void setRefersToFormula(String formulaText) { XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(_workbook); //validate through the FormulaParser - FormulaParser.parse(formulaText, fpb, FormulaType.NAMEDRANGE, getSheetIndex()); + FormulaParser.parse(formulaText, fpb, FormulaType.NAMEDRANGE, getSheetIndex(), -1); _ctName.setStringValue(formulaText); } @@ -203,7 +203,7 @@ public final class XSSFName implements Name { return false; } 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); } diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFTable.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFTable.java index c8a932bb9..03e461422 100644 --- a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFTable.java +++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFTable.java @@ -24,13 +24,17 @@ import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; import java.util.List; +import java.util.Locale; import org.apache.poi.POIXMLDocumentPart; import org.apache.poi.openxml4j.opc.PackagePart; import org.apache.poi.openxml4j.opc.PackageRelationship; +import org.apache.poi.ss.usermodel.Table; import org.apache.poi.ss.util.CellReference; import org.apache.poi.xssf.usermodel.helpers.XSSFXmlColumnPr; +import org.apache.poi.util.StringUtil; import org.apache.xmlbeans.XmlException; import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTTable; import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTTableColumn; @@ -48,10 +52,12 @@ import org.openxmlformats.schemas.spreadsheetml.x2006.main.TableDocument; * * @author Roberto Manicardi */ -public class XSSFTable extends POIXMLDocumentPart { +public class XSSFTable extends POIXMLDocumentPart implements Table { private CTTable ctTable; private List xmlColumnPr; + private CTTableColumn[] ctColumns; + private HashMap columnMap; private CellReference startCellReference; private CellReference endCellReference; private String commonXPath; @@ -107,7 +113,7 @@ public class XSSFTable extends POIXMLDocumentPart { out.close(); } - public CTTable getCTTable(){ + public CTTable getCTTable() { return ctTable; } @@ -117,32 +123,41 @@ public class XSSFTable extends POIXMLDocumentPart { * @return true if the Table element contain mappings */ public boolean mapsTo(long id){ - boolean maps =false; - List pointers = getXmlColumnPrs(); for (XSSFXmlColumnPr pointer: pointers) { if (pointer.getMapId()==id) { - maps=true; - break; + return true; } } - return maps; + return false; } + /** + * caches table columns for performance. + * Updated via updateHeaders + * @since 3.15 beta 2 + */ + private CTTableColumn[] getTableColumns() { + if (ctColumns == null) { + ctColumns = ctTable.getTableColumns().getTableColumnArray(); + } + return ctColumns; + } /** * * Calculates the xpath of the root element for the table. This will be the common part * of all the mapping's xpaths + * Note: this function caches the result for performance. To flush the cache {@link #updateHeaders()} must be called. * * @return the xpath of the table's root element */ public String getCommonXpath() { if (commonXPath == null) { String[] commonTokens = {}; - for (CTTableColumn column :ctTable.getTableColumns().getTableColumnArray()) { + for (CTTableColumn column : getTableColumns()) { if (column.getXmlColumnPr()!=null) { String xpath = column.getXmlColumnPr().getXpath(); String[] tokens = xpath.split("/"); @@ -166,21 +181,24 @@ public class XSSFTable extends POIXMLDocumentPart { } } - commonXPath = ""; - for (int i = 1 ; i< commonTokens.length;i++) { - commonXPath +="/"+commonTokens[i]; - } + commonTokens[0] = ""; + commonXPath = StringUtil.join(commonTokens, "/"); } return commonXPath; } + /** + * Note this list is static - once read, it does not notice later changes to the underlying column structures + * To clear the cache, call {@link #updateHeaders} + * @return List of XSSFXmlColumnPr + */ public List getXmlColumnPrs() { if (xmlColumnPr==null) { xmlColumnPr = new ArrayList(); - for (CTTableColumn column:ctTable.getTableColumns().getTableColumnArray()) { + for (CTTableColumn column: getTableColumns()) { if (column.getXmlColumnPr()!=null) { XSSFXmlColumnPr columnPr = new XSSFXmlColumnPr(this,column,column.getXmlColumnPr()); xmlColumnPr.add(columnPr); @@ -283,7 +301,7 @@ public class XSSFTable extends POIXMLDocumentPart { * Headers must be in sync, otherwise Excel will display a * "Found unreadable content" message on startup. */ - public void updateHeaders(){ + public void updateHeaders() { XSSFSheet sheet = (XSSFSheet)getParent(); CellReference ref = getStartCellReference(); if(ref == null) return; @@ -301,6 +319,83 @@ public class XSSFTable extends POIXMLDocumentPart { } 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 column. + * The column index is relative to the left-most column in the table, 0-indexed. + * Returns -1 if column 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(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(); + } } diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFWorkbook.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFWorkbook.java index 100c2b95b..742720f78 100644 --- a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFWorkbook.java +++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFWorkbook.java @@ -33,6 +33,7 @@ import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.NoSuchElementException; import java.util.regex.Pattern; @@ -1761,7 +1762,7 @@ public class XSSFWorkbook extends POIXMLDocument implements Workbook { * Get the document's embedded files. */ @Override - public List getAllEmbedds() throws OpenXML4JException { + public List getAllEmbedds() throws OpenXML4JException { List embedds = new LinkedList(); for(XSSFSheet sheet : sheets){ @@ -1928,7 +1929,7 @@ public class XSSFWorkbook extends POIXMLDocument implements Workbook { */ @Internal 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"); } - /** - * Specifies a boolean value that indicates whether structure of workbook is locked.
- * 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.
- * A value of false indicates the structure of the workbook is not locked.
- * - * @return true if structure of workbook is locked - */ - public boolean isStructureLocked() { - return workbookProtectionPresent() && workbook.getWorkbookProtection().getLockStructure(); - } + /** + * Specifies a boolean value that indicates whether structure of workbook is locked.
+ * 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.
+ * A value of false indicates the structure of the workbook is not locked.
+ * + * @return true if structure of workbook is locked + */ + public boolean isStructureLocked() { + return workbookProtectionPresent() && workbook.getWorkbookProtection().getLockStructure(); + } - /** - * Specifies a boolean value that indicates whether the windows that comprise the workbook are locked.
- * A value of true indicates the workbook windows are locked. Windows are the same size and position each time the - * workbook is opened.
- * A value of false indicates the workbook windows are not locked. - * - * @return true if windows that comprise the workbook are locked - */ - public boolean isWindowsLocked() { - return workbookProtectionPresent() && workbook.getWorkbookProtection().getLockWindows(); - } + /** + * Specifies a boolean value that indicates whether the windows that comprise the workbook are locked.
+ * A value of true indicates the workbook windows are locked. Windows are the same size and position each time the + * workbook is opened.
+ * A value of false indicates the workbook windows are not locked. + * + * @return true if windows that comprise the workbook are locked + */ + public boolean isWindowsLocked() { + return workbookProtectionPresent() && workbook.getWorkbookProtection().getLockWindows(); + } - /** - * Specifies a boolean value that indicates whether the workbook is locked for revisions. - * - * @return true if the workbook is locked for revisions. - */ - public boolean isRevisionLocked() { - return workbookProtectionPresent() && workbook.getWorkbookProtection().getLockRevision(); - } + /** + * Specifies a boolean value that indicates whether the workbook is locked for revisions. + * + * @return true if the workbook is locked for revisions. + */ + public boolean isRevisionLocked() { + return workbookProtectionPresent() && workbook.getWorkbookProtection().getLockRevision(); + } - /** - * Locks the structure of workbook. - */ - public void lockStructure() { - safeGetWorkbookProtection().setLockStructure(true); - } + /** + * Locks the structure of workbook. + */ + public void lockStructure() { + safeGetWorkbookProtection().setLockStructure(true); + } - /** - * Unlocks the structure of workbook. - */ - public void unLockStructure() { - safeGetWorkbookProtection().setLockStructure(false); - } + /** + * Unlocks the structure of workbook. + */ + public void unLockStructure() { + safeGetWorkbookProtection().setLockStructure(false); + } - /** - * Locks the windows that comprise the workbook. - */ - public void lockWindows() { - safeGetWorkbookProtection().setLockWindows(true); - } + /** + * Locks the windows that comprise the workbook. + */ + public void lockWindows() { + safeGetWorkbookProtection().setLockWindows(true); + } - /** - * Unlocks the windows that comprise the workbook. - */ - public void unLockWindows() { - safeGetWorkbookProtection().setLockWindows(false); - } + /** + * Unlocks the windows that comprise the workbook. + */ + public void unLockWindows() { + safeGetWorkbookProtection().setLockWindows(false); + } - /** - * Locks the workbook for revisions. - */ - public void lockRevision() { - safeGetWorkbookProtection().setLockRevision(true); - } + /** + * Locks the workbook for revisions. + */ + public void lockRevision() { + safeGetWorkbookProtection().setLockRevision(true); + } - /** - * Unlocks the workbook for revisions. - */ - public void unLockRevision() { - safeGetWorkbookProtection().setLockRevision(false); - } + /** + * Unlocks the workbook for revisions. + */ + public void unLockRevision() { + safeGetWorkbookProtection().setLockRevision(false); + } - /** - * Sets the workbook password. - * - * @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) - * otherwise the given algorithm is used for calculating the hash password (Excel 2013) - */ - public void setWorkbookPassword(String password, HashAlgorithm hashAlgo) { + /** + * Sets the workbook password. + * + * @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) + * otherwise the given algorithm is used for calculating the hash password (Excel 2013) + */ + public void setWorkbookPassword(String password, HashAlgorithm hashAlgo) { if (password == null && !workbookProtectionPresent()) return; setPassword(safeGetWorkbookProtection(), password, hashAlgo, "workbook"); - } + } /** * 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() { - return workbook.isSetWorkbookProtection(); - } + private boolean workbookProtectionPresent() { + return workbook.isSetWorkbookProtection(); + } private CTWorkbookProtection safeGetWorkbookProtection() { if (!workbookProtectionPresent()){ @@ -2083,7 +2084,7 @@ public class XSSFWorkbook extends POIXMLDocument implements Workbook { } return workbook.getWorkbookProtection(); } - + /** * * Returns the locator of user-defined functions. @@ -2261,4 +2262,24 @@ public class XSSFWorkbook extends POIXMLDocument implements Workbook { public SpreadsheetVersion getSpreadsheetVersion() { 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 name, or null if no table is named name. + * @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; + } } diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/helpers/XSSFFormulaUtils.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/helpers/XSSFFormulaUtils.java index a9d995fb7..8aa3de601 100644 --- a/src/ooxml/java/org/apache/poi/xssf/usermodel/helpers/XSSFFormulaUtils.java +++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/helpers/XSSFFormulaUtils.java @@ -94,7 +94,7 @@ public final class XSSFFormulaUtils { String formula = f.getStringValue(); if (formula != null && formula.length() > 0) { 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) { updatePtg(ptg, oldName, newName); } @@ -113,7 +113,8 @@ public final class XSSFFormulaUtils { String formula = name.getRefersToFormula(); if (formula != null) { int sheetIndex = name.getSheetIndex(); - Ptg[] ptgs = FormulaParser.parse(formula, _fpwb, FormulaType.NAMEDRANGE, sheetIndex); + int rowIndex = -1; //don't care + Ptg[] ptgs = FormulaParser.parse(formula, _fpwb, FormulaType.NAMEDRANGE, sheetIndex, rowIndex); for (Ptg ptg : ptgs) { updatePtg(ptg, oldName, newName); } diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/helpers/XSSFRowShifter.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/helpers/XSSFRowShifter.java index 80462ac4c..a1436cc64 100644 --- a/src/ooxml/java/org/apache/poi/xssf/usermodel/helpers/XSSFRowShifter.java +++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/helpers/XSSFRowShifter.java @@ -134,8 +134,9 @@ public final class XSSFRowShifter { XSSFName name = wb.getNameAt(i); String formula = name.getRefersToFormula(); int sheetIndex = name.getSheetIndex(); + final int rowIndex = -1; //don't care, named ranges are not allowed to include structured references - Ptg[] ptgs = FormulaParser.parse(formula, fpb, FormulaType.NAMEDRANGE, sheetIndex); + Ptg[] ptgs = FormulaParser.parse(formula, fpb, FormulaType.NAMEDRANGE, sheetIndex, rowIndex); if (shifter.adjustFormula(ptgs, sheetIndex)) { String shiftedFmla = FormulaRenderer.toFormulaString(fpb, ptgs); name.setRefersToFormula(shiftedFmla); @@ -218,10 +219,11 @@ public final class XSSFRowShifter { XSSFSheet sheet = row.getSheet(); XSSFWorkbook wb = sheet.getWorkbook(); int sheetIndex = wb.getSheetIndex(sheet); + final int rowIndex = row.getRowNum(); XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(wb); try { - Ptg[] ptgs = FormulaParser.parse(formula, fpb, FormulaType.CELL, sheetIndex); + Ptg[] ptgs = FormulaParser.parse(formula, fpb, FormulaType.CELL, sheetIndex, rowIndex); String shiftedFmla = null; if (shifter.adjustFormula(ptgs, sheetIndex)) { shiftedFmla = FormulaRenderer.toFormulaString(fpb, ptgs); @@ -238,6 +240,7 @@ public final class XSSFRowShifter { public void updateConditionalFormatting(FormulaShifter shifter) { XSSFWorkbook wb = sheet.getWorkbook(); int sheetIndex = wb.getSheetIndex(sheet); + final int rowIndex = -1; //don't care, structured references not allowed in conditional formatting XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(wb); CTWorksheet ctWorksheet = sheet.getCTWorksheet(); @@ -283,7 +286,7 @@ public final class XSSFRowShifter { String[] formulaArray = cfRule.getFormulaArray(); for (int i = 0; i < formulaArray.length; i++) { String formula = formulaArray[i]; - Ptg[] ptgs = FormulaParser.parse(formula, fpb, FormulaType.CELL, sheetIndex); + Ptg[] ptgs = FormulaParser.parse(formula, fpb, FormulaType.CELL, sheetIndex, rowIndex); if (shifter.adjustFormula(ptgs, sheetIndex)) { String shiftedFmla = FormulaRenderer.toFormulaString(fpb, ptgs); cfRule.setFormulaArray(i, shiftedFmla); diff --git a/src/ooxml/testcases/org/apache/poi/ss/formula/TestFormulaParser.java b/src/ooxml/testcases/org/apache/poi/ss/formula/TestFormulaParser.java index 9987dcf42..9f40ca363 100644 --- a/src/ooxml/testcases/org/apache/poi/ss/formula/TestFormulaParser.java +++ b/src/ooxml/testcases/org/apache/poi/ss/formula/TestFormulaParser.java @@ -18,6 +18,11 @@ */ 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.HSSFWorkbook; 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.XSSFWorkbook; -import junit.framework.TestCase; +import org.junit.Test; /** * Test {@link FormulaParser}'s handling of row numbers at the edge of the @@ -36,8 +41,9 @@ import junit.framework.TestCase; * * @author David North */ -public class TestFormulaParser extends TestCase { +public class TestFormulaParser { + @Test public void testHSSFFailsForOver65536() { FormulaParsingWorkbook workbook = HSSFEvaluationWorkbook.create(new HSSFWorkbook()); try { @@ -49,16 +55,19 @@ public class TestFormulaParser extends TestCase { } } + @Test public void testHSSFPassCase() { FormulaParsingWorkbook workbook = HSSFEvaluationWorkbook.create(new HSSFWorkbook()); FormulaParser.parse("Sheet1!1:65536", workbook, FormulaType.CELL, 0); } + @Test public void testXSSFWorksForOver65536() { FormulaParsingWorkbook workbook = XSSFEvaluationWorkbook.create(new XSSFWorkbook()); FormulaParser.parse("Sheet1!1:65537", workbook, FormulaType.CELL, 0); } + @Test public void testXSSFFailCase() { FormulaParsingWorkbook workbook = XSSFEvaluationWorkbook.create(new XSSFWorkbook()); try { @@ -71,6 +80,7 @@ public class TestFormulaParser extends TestCase { } // copied from org.apache.poi.hssf.model.TestFormulaParser + @Test public void testMacroFunction() throws Exception { // testNames.xlsm contains a VB function called 'myFunc' final String testFile = "testNames.xlsm"; @@ -126,6 +136,7 @@ public class TestFormulaParser extends TestCase { } } + @Test public void testParserErrors() throws Exception { XSSFWorkbook wb = XSSFTestDataSamples.openSampleWorkbook("testNames.xlsm"); try { diff --git a/src/ooxml/testcases/org/apache/poi/ss/formula/TestStructuredReferences.java b/src/ooxml/testcases/org/apache/poi/ss/formula/TestStructuredReferences.java new file mode 100644 index 000000000..2fb7cb89f --- /dev/null +++ b/src/ooxml/testcases/org/apache/poi/ss/formula/TestStructuredReferences.java @@ -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 + * Excel Structured Reference Syntax + * + */ +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); + } +} diff --git a/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFFormulaParser.java b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFFormulaParser.java index 83dabacb9..08c4ed298 100644 --- a/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFFormulaParser.java +++ b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFFormulaParser.java @@ -22,6 +22,8 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import java.io.IOException; + import org.apache.poi.hssf.HSSFTestDataSamples; import org.apache.poi.hssf.usermodel.HSSFCell; import org.apache.poi.hssf.usermodel.HSSFEvaluationWorkbook; @@ -41,12 +43,15 @@ import org.junit.Test; import java.util.Arrays; public final class TestXSSFFormulaParser { - private static Ptg[] parse(FormulaParsingWorkbook fpb, String fmla) { - return FormulaParser.parse(fmla, fpb, FormulaType.CELL, -1); - } + private static Ptg[] parse(FormulaParsingWorkbook fpb, String fmla) { + 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 - public void basicParsing() { + @Test + public void basicParsing() throws IOException { XSSFWorkbook wb = new XSSFWorkbook(); XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(wb); Ptg[] ptgs; @@ -118,10 +123,12 @@ public final class TestXSSFFormulaParser { assertEquals(AttrPtg.class, ptgs[1].getClass()); assertEquals("Sheet1!A1:B3", ptgs[0].toFormulaString()); assertEquals("SUM", ptgs[1].toFormulaString()); + + wb.close(); } - @Test - public void builtInFormulas() { + @Test + public void builtInFormulas() throws IOException { XSSFWorkbook wb = new XSSFWorkbook(); XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(wb); Ptg[] ptgs; @@ -134,10 +141,12 @@ public final class TestXSSFFormulaParser { assertEquals(2, ptgs.length); assertTrue("Had " + Arrays.toString(ptgs), ptgs[0] instanceof IntPtg); assertTrue("Had " + Arrays.toString(ptgs), ptgs[1] instanceof FuncPtg); + + wb.close(); } @Test - public void formaulReferncesSameWorkbook() { + public void formulaReferencesSameWorkbook() throws IOException { // Use a test file with "other workbook" style references // to itself XSSFWorkbook wb = XSSFTestDataSamples.openSampleWorkbook("56737.xlsx"); @@ -153,10 +162,12 @@ public final class TestXSSFFormulaParser { assertEquals(null, ((NameXPxg)ptgs[0]).getSheetName()); assertEquals("NR_Global_B2",((NameXPxg)ptgs[0]).getNameName()); assertEquals("[0]!NR_Global_B2",((NameXPxg)ptgs[0]).toFormulaString()); + + wb.close(); } - @Test - public void formulaReferencesOtherSheets() { + @Test + public void formulaReferencesOtherSheets() throws IOException { // Use a test file with the named ranges in place XSSFWorkbook wb = XSSFTestDataSamples.openSampleWorkbook("56737.xlsx"); XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(wb); @@ -193,10 +204,12 @@ public final class TestXSSFFormulaParser { assertEquals(1, ptgs.length); assertEquals(NamePtg.class, ptgs[0].getClass()); assertEquals("NR_Global_B2",((NamePtg)ptgs[0]).toFormulaString(fpb)); + + wb.close(); } @Test - public void formulaReferencesOtherWorkbook() { + public void formulaReferencesOtherWorkbook() throws IOException { // Use a test file with the external linked table in place XSSFWorkbook wb = XSSFTestDataSamples.openSampleWorkbook("ref-56737.xlsx"); XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(wb); @@ -228,6 +241,8 @@ public final class TestXSSFFormulaParser { assertEquals(null, ((NameXPxg)ptgs[0]).getSheetName()); assertEquals("NR_Global_B2",((NameXPxg)ptgs[0]).getNameName()); 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) */ @Test - public void multiSheetReferencesHSSFandXSSF() throws Exception { + public void multiSheetReferencesHSSFandXSSF() throws IOException { Workbook[] wbs = new Workbook[] { HSSFTestDataSamples.openSampleWorkbook("55906-MultiSheetRefs.xls"), XSSFTestDataSamples.openSampleWorkbook("55906-MultiSheetRefs.xlsx") @@ -363,6 +378,8 @@ public final class TestXSSFFormulaParser { newF = s1.getRow(0).createCell(11, Cell.CELL_TYPE_FORMULA); newF.setCellFormula("MIN(Sheet1:Sheet2!A1:B2)"); assertEquals("MIN(Sheet1:Sheet2!A1:B2)", newF.getCellFormula()); + + wb.close(); } } @@ -374,7 +391,7 @@ public final class TestXSSFFormulaParser { } @Test - public void test58648Single() { + public void test58648Single() throws IOException { XSSFWorkbook wb = new XSSFWorkbook(); XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(wb); Ptg[] ptgs; @@ -384,10 +401,12 @@ public final class TestXSSFFormulaParser { 2, ptgs.length); assertTrue("Had " + Arrays.toString(ptgs), ptgs[0] instanceof RefPtg); assertTrue("Had " + Arrays.toString(ptgs), ptgs[1] instanceof ParenthesisPtg); + + wb.close(); } @Test - public void test58648Basic() { + public void test58648Basic() throws IOException { XSSFWorkbook wb = new XSSFWorkbook(); XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(wb); 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[1] instanceof ParenthesisPtg); assertTrue("Had " + Arrays.toString(ptgs), ptgs[2] instanceof ParenthesisPtg); + + wb.close(); } @Test - public void test58648FormulaParsing() { + public void test58648FormulaParsing() throws IOException { Workbook wb = XSSFTestDataSamples.openSampleWorkbook("58648.xlsx"); FormulaEvaluator evaluator = wb.getCreationHelper().createFormulaEvaluator(); @@ -460,10 +481,12 @@ public final class TestXSSFFormulaParser { Cell cell = sheet.getRow(1).getCell(4); assertEquals(5d, cell.getNumericCellValue(), 0d); + + wb.close(); } @Test - public void testWhitespaceInFormula() { + public void testWhitespaceInFormula() throws IOException { XSSFWorkbook wb = new XSSFWorkbook(); XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(wb); 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[2] instanceof AreaPtg); assertTrue("Had " + Arrays.toString(ptgs), ptgs[3] instanceof IntersectionPtg); + + wb.close(); } @Test - public void testWhitespaceInComplexFormula() { + public void testWhitespaceInComplexFormula() throws IOException { XSSFWorkbook wb = new XSSFWorkbook(); XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(wb); 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[2] instanceof AreaPtg); 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(); } } diff --git a/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFSheet.java b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFSheet.java index 6b1eebe65..868557ae1 100644 --- a/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFSheet.java +++ b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFSheet.java @@ -1137,7 +1137,7 @@ public final class TestXSSFSheet extends BaseTestXSheet { } /** - * See bug #50829 + * See bug #50829 test data tables */ @Test public void tables() throws IOException { diff --git a/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFWorkbook.java b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFWorkbook.java index 6e642ccca..81eed92ca 100644 --- a/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFWorkbook.java +++ b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFWorkbook.java @@ -79,432 +79,432 @@ import org.openxmlformats.schemas.spreadsheetml.x2006.main.STCalcMode; public final class TestXSSFWorkbook extends BaseTestXWorkbook { - public TestXSSFWorkbook() { - super(XSSFITestDataProvider.instance); - } + public TestXSSFWorkbook() { + super(XSSFITestDataProvider.instance); + } - /** - * Tests that we can save, and then re-load a new document - */ - @Test - public void saveLoadNew() throws IOException, InvalidFormatException { + /** + * Tests that we can save, and then re-load a new document + */ + @Test + public void saveLoadNew() throws IOException, InvalidFormatException { XSSFWorkbook wb1 = new XSSFWorkbook(); - //check that the default date system is set to 1900 - CTWorkbookPr pr = wb1.getCTWorkbook().getWorkbookPr(); - assertNotNull(pr); - assertTrue(pr.isSetDate1904()); - assertFalse("XSSF must use the 1900 date system", pr.getDate1904()); + //check that the default date system is set to 1900 + CTWorkbookPr pr = wb1.getCTWorkbook().getWorkbookPr(); + assertNotNull(pr); + assertTrue(pr.isSetDate1904()); + assertFalse("XSSF must use the 1900 date system", pr.getDate1904()); - Sheet sheet1 = wb1.createSheet("sheet1"); - Sheet sheet2 = wb1.createSheet("sheet2"); - wb1.createSheet("sheet3"); + Sheet sheet1 = wb1.createSheet("sheet1"); + Sheet sheet2 = wb1.createSheet("sheet2"); + wb1.createSheet("sheet3"); - RichTextString rts = wb1.getCreationHelper().createRichTextString("hello world"); + RichTextString rts = wb1.getCreationHelper().createRichTextString("hello world"); - sheet1.createRow(0).createCell((short)0).setCellValue(1.2); - sheet1.createRow(1).createCell((short)0).setCellValue(rts); - sheet2.createRow(0); + sheet1.createRow(0).createCell((short)0).setCellValue(1.2); + sheet1.createRow(1).createCell((short)0).setCellValue(rts); + sheet2.createRow(0); - assertEquals(0, wb1.getSheetAt(0).getFirstRowNum()); - assertEquals(1, wb1.getSheetAt(0).getLastRowNum()); - assertEquals(0, wb1.getSheetAt(1).getFirstRowNum()); - assertEquals(0, wb1.getSheetAt(1).getLastRowNum()); - assertEquals(0, wb1.getSheetAt(2).getFirstRowNum()); - assertEquals(0, wb1.getSheetAt(2).getLastRowNum()); + assertEquals(0, wb1.getSheetAt(0).getFirstRowNum()); + assertEquals(1, wb1.getSheetAt(0).getLastRowNum()); + assertEquals(0, wb1.getSheetAt(1).getFirstRowNum()); + assertEquals(0, wb1.getSheetAt(1).getLastRowNum()); + assertEquals(0, wb1.getSheetAt(2).getFirstRowNum()); + assertEquals(0, wb1.getSheetAt(2).getLastRowNum()); - File file = TempFile.createTempFile("poi-", ".xlsx"); - OutputStream out = new FileOutputStream(file); - wb1.write(out); - out.close(); + File file = TempFile.createTempFile("poi-", ".xlsx"); + OutputStream out = new FileOutputStream(file); + wb1.write(out); + out.close(); - // Check the package contains what we'd expect it to - OPCPackage pkg = OPCPackage.open(file.toString()); - PackagePart wbRelPart = - pkg.getPart(PackagingURIHelper.createPartName("/xl/_rels/workbook.xml.rels")); - assertNotNull(wbRelPart); - assertTrue(wbRelPart.isRelationshipPart()); - assertEquals(ContentTypes.RELATIONSHIPS_PART, wbRelPart.getContentType()); + // Check the package contains what we'd expect it to + OPCPackage pkg = OPCPackage.open(file.toString()); + PackagePart wbRelPart = + pkg.getPart(PackagingURIHelper.createPartName("/xl/_rels/workbook.xml.rels")); + assertNotNull(wbRelPart); + assertTrue(wbRelPart.isRelationshipPart()); + assertEquals(ContentTypes.RELATIONSHIPS_PART, wbRelPart.getContentType()); - PackagePart wbPart = - pkg.getPart(PackagingURIHelper.createPartName("/xl/workbook.xml")); - // Links to the three sheets, shared strings and styles - assertTrue(wbPart.hasRelationships()); - assertEquals(5, wbPart.getRelationships().size()); - wb1.close(); + PackagePart wbPart = + pkg.getPart(PackagingURIHelper.createPartName("/xl/workbook.xml")); + // Links to the three sheets, shared strings and styles + assertTrue(wbPart.hasRelationships()); + assertEquals(5, wbPart.getRelationships().size()); + wb1.close(); - // Load back the XSSFWorkbook - @SuppressWarnings("resource") + // Load back the XSSFWorkbook + @SuppressWarnings("resource") XSSFWorkbook wb2 = new XSSFWorkbook(pkg); - assertEquals(3, wb2.getNumberOfSheets()); - assertNotNull(wb2.getSheetAt(0)); - assertNotNull(wb2.getSheetAt(1)); - assertNotNull(wb2.getSheetAt(2)); + assertEquals(3, wb2.getNumberOfSheets()); + assertNotNull(wb2.getSheetAt(0)); + assertNotNull(wb2.getSheetAt(1)); + assertNotNull(wb2.getSheetAt(2)); - assertNotNull(wb2.getSharedStringSource()); - assertNotNull(wb2.getStylesSource()); + assertNotNull(wb2.getSharedStringSource()); + assertNotNull(wb2.getStylesSource()); - assertEquals(0, wb2.getSheetAt(0).getFirstRowNum()); - assertEquals(1, wb2.getSheetAt(0).getLastRowNum()); - assertEquals(0, wb2.getSheetAt(1).getFirstRowNum()); - assertEquals(0, wb2.getSheetAt(1).getLastRowNum()); - assertEquals(0, wb2.getSheetAt(2).getFirstRowNum()); - assertEquals(0, wb2.getSheetAt(2).getLastRowNum()); + assertEquals(0, wb2.getSheetAt(0).getFirstRowNum()); + assertEquals(1, wb2.getSheetAt(0).getLastRowNum()); + assertEquals(0, wb2.getSheetAt(1).getFirstRowNum()); + assertEquals(0, wb2.getSheetAt(1).getLastRowNum()); + assertEquals(0, wb2.getSheetAt(2).getFirstRowNum()); + assertEquals(0, wb2.getSheetAt(2).getLastRowNum()); - sheet1 = wb2.getSheetAt(0); - assertEquals(1.2, sheet1.getRow(0).getCell(0).getNumericCellValue(), 0.0001); - assertEquals("hello world", sheet1.getRow(1).getCell(0).getRichStringCellValue().getString()); + sheet1 = wb2.getSheetAt(0); + assertEquals(1.2, sheet1.getRow(0).getCell(0).getNumericCellValue(), 0.0001); + assertEquals("hello world", sheet1.getRow(1).getCell(0).getRichStringCellValue().getString()); - pkg.close(); - } + pkg.close(); + } @Test - public void existing() throws Exception { + public void existing() throws Exception { - XSSFWorkbook workbook = XSSFTestDataSamples.openSampleWorkbook("Formatting.xlsx"); - assertNotNull(workbook.getSharedStringSource()); - assertNotNull(workbook.getStylesSource()); + XSSFWorkbook workbook = XSSFTestDataSamples.openSampleWorkbook("Formatting.xlsx"); + assertNotNull(workbook.getSharedStringSource()); + assertNotNull(workbook.getStylesSource()); - // And check a few low level bits too - OPCPackage pkg = OPCPackage.open(HSSFTestDataSamples.openSampleFileStream("Formatting.xlsx")); - PackagePart wbPart = - pkg.getPart(PackagingURIHelper.createPartName("/xl/workbook.xml")); + // And check a few low level bits too + OPCPackage pkg = OPCPackage.open(HSSFTestDataSamples.openSampleFileStream("Formatting.xlsx")); + PackagePart wbPart = + pkg.getPart(PackagingURIHelper.createPartName("/xl/workbook.xml")); - // Links to the three sheets, shared, styles and themes - assertTrue(wbPart.hasRelationships()); - assertEquals(6, wbPart.getRelationships().size()); + // Links to the three sheets, shared, styles and themes + assertTrue(wbPart.hasRelationships()); + assertEquals(6, wbPart.getRelationships().size()); - pkg.close(); - workbook.close(); - } + pkg.close(); + workbook.close(); + } @Test - public void getCellStyleAt() throws IOException{ - XSSFWorkbook workbook = new XSSFWorkbook(); - try { - short i = 0; - //get default style - CellStyle cellStyleAt = workbook.getCellStyleAt(i); - assertNotNull(cellStyleAt); + public void getCellStyleAt() throws IOException{ + XSSFWorkbook workbook = new XSSFWorkbook(); + try { + short i = 0; + //get default style + CellStyle cellStyleAt = workbook.getCellStyleAt(i); + assertNotNull(cellStyleAt); - //get custom style - StylesTable styleSource = workbook.getStylesSource(); - XSSFCellStyle customStyle = new XSSFCellStyle(styleSource); - XSSFFont font = new XSSFFont(); - font.setFontName("Verdana"); - customStyle.setFont(font); - int x = styleSource.putStyle(customStyle); - cellStyleAt = workbook.getCellStyleAt((short)x); - assertNotNull(cellStyleAt); - } finally { - workbook.close(); - } - } + //get custom style + StylesTable styleSource = workbook.getStylesSource(); + XSSFCellStyle customStyle = new XSSFCellStyle(styleSource); + XSSFFont font = new XSSFFont(); + font.setFontName("Verdana"); + customStyle.setFont(font); + int x = styleSource.putStyle(customStyle); + cellStyleAt = workbook.getCellStyleAt((short)x); + assertNotNull(cellStyleAt); + } finally { + workbook.close(); + } + } @Test - public void getFontAt() throws IOException{ - XSSFWorkbook workbook = new XSSFWorkbook(); - try { - StylesTable styleSource = workbook.getStylesSource(); - short i = 0; - //get default font - Font fontAt = workbook.getFontAt(i); - assertNotNull(fontAt); + public void getFontAt() throws IOException{ + XSSFWorkbook workbook = new XSSFWorkbook(); + try { + StylesTable styleSource = workbook.getStylesSource(); + short i = 0; + //get default font + Font fontAt = workbook.getFontAt(i); + assertNotNull(fontAt); - //get customized font - XSSFFont customFont = new XSSFFont(); - customFont.setItalic(true); - int x = styleSource.putFont(customFont); - fontAt = workbook.getFontAt((short)x); - assertNotNull(fontAt); - } finally { - workbook.close(); - } - } + //get customized font + XSSFFont customFont = new XSSFFont(); + customFont.setItalic(true); + int x = styleSource.putFont(customFont); + fontAt = workbook.getFontAt((short)x); + assertNotNull(fontAt); + } finally { + workbook.close(); + } + } @Test - public void getNumCellStyles() throws IOException{ - XSSFWorkbook workbook = new XSSFWorkbook(); - try { - //get default cellStyles - assertEquals(1, workbook.getNumCellStyles()); - } finally { - workbook.close(); - } - } + public void getNumCellStyles() throws IOException{ + XSSFWorkbook workbook = new XSSFWorkbook(); + try { + //get default cellStyles + assertEquals(1, workbook.getNumCellStyles()); + } finally { + workbook.close(); + } + } @Test - public void loadSave() throws IOException { - XSSFWorkbook workbook = XSSFTestDataSamples.openSampleWorkbook("Formatting.xlsx"); - assertEquals(3, workbook.getNumberOfSheets()); - assertEquals("dd/mm/yyyy", workbook.getSheetAt(0).getRow(1).getCell(0).getRichStringCellValue().getString()); - assertNotNull(workbook.getSharedStringSource()); - assertNotNull(workbook.getStylesSource()); + public void loadSave() throws IOException { + XSSFWorkbook workbook = XSSFTestDataSamples.openSampleWorkbook("Formatting.xlsx"); + assertEquals(3, workbook.getNumberOfSheets()); + assertEquals("dd/mm/yyyy", workbook.getSheetAt(0).getRow(1).getCell(0).getRichStringCellValue().getString()); + assertNotNull(workbook.getSharedStringSource()); + assertNotNull(workbook.getStylesSource()); - // Write out, and check - // Load up again, check all still there - XSSFWorkbook wb2 = XSSFTestDataSamples.writeOutAndReadBack(workbook); - assertEquals(3, wb2.getNumberOfSheets()); - assertNotNull(wb2.getSheetAt(0)); - assertNotNull(wb2.getSheetAt(1)); - assertNotNull(wb2.getSheetAt(2)); + // Write out, and check + // Load up again, check all still there + XSSFWorkbook wb2 = XSSFTestDataSamples.writeOutAndReadBack(workbook); + assertEquals(3, wb2.getNumberOfSheets()); + assertNotNull(wb2.getSheetAt(0)); + assertNotNull(wb2.getSheetAt(1)); + assertNotNull(wb2.getSheetAt(2)); - assertEquals("dd/mm/yyyy", wb2.getSheetAt(0).getRow(1).getCell(0).getRichStringCellValue().getString()); - assertEquals("yyyy/mm/dd", wb2.getSheetAt(0).getRow(2).getCell(0).getRichStringCellValue().getString()); - assertEquals("yyyy-mm-dd", wb2.getSheetAt(0).getRow(3).getCell(0).getRichStringCellValue().getString()); - assertEquals("yy/mm/dd", wb2.getSheetAt(0).getRow(4).getCell(0).getRichStringCellValue().getString()); - assertNotNull(wb2.getSharedStringSource()); - assertNotNull(wb2.getStylesSource()); + assertEquals("dd/mm/yyyy", wb2.getSheetAt(0).getRow(1).getCell(0).getRichStringCellValue().getString()); + assertEquals("yyyy/mm/dd", wb2.getSheetAt(0).getRow(2).getCell(0).getRichStringCellValue().getString()); + assertEquals("yyyy-mm-dd", wb2.getSheetAt(0).getRow(3).getCell(0).getRichStringCellValue().getString()); + assertEquals("yy/mm/dd", wb2.getSheetAt(0).getRow(4).getCell(0).getRichStringCellValue().getString()); + assertNotNull(wb2.getSharedStringSource()); + assertNotNull(wb2.getStylesSource()); - workbook.close(); - wb2.close(); - } + workbook.close(); + wb2.close(); + } @Test - public void styles() throws IOException { - XSSFWorkbook wb1 = XSSFTestDataSamples.openSampleWorkbook("Formatting.xlsx"); + public void styles() throws IOException { + XSSFWorkbook wb1 = XSSFTestDataSamples.openSampleWorkbook("Formatting.xlsx"); - StylesTable ss = wb1.getStylesSource(); - assertNotNull(ss); - StylesTable st = ss; + StylesTable ss = wb1.getStylesSource(); + assertNotNull(ss); + StylesTable st = ss; - // Has 8 number formats - assertEquals(8, st.getNumDataFormats()); - // Has 2 fonts - assertEquals(2, st.getFonts().size()); - // Has 2 fills - assertEquals(2, st.getFills().size()); - // Has 1 border - assertEquals(1, st.getBorders().size()); + // Has 8 number formats + assertEquals(8, st.getNumDataFormats()); + // Has 2 fonts + assertEquals(2, st.getFonts().size()); + // Has 2 fills + assertEquals(2, st.getFills().size()); + // Has 1 border + assertEquals(1, st.getBorders().size()); - // Add two more styles - assertEquals(StylesTable.FIRST_CUSTOM_STYLE_ID + 8, - st.putNumberFormat("testFORMAT")); - assertEquals(StylesTable.FIRST_CUSTOM_STYLE_ID + 8, - st.putNumberFormat("testFORMAT")); - assertEquals(StylesTable.FIRST_CUSTOM_STYLE_ID + 9, - st.putNumberFormat("testFORMAT2")); - assertEquals(10, st.getNumDataFormats()); + // Add two more styles + assertEquals(StylesTable.FIRST_CUSTOM_STYLE_ID + 8, + st.putNumberFormat("testFORMAT")); + assertEquals(StylesTable.FIRST_CUSTOM_STYLE_ID + 8, + st.putNumberFormat("testFORMAT")); + assertEquals(StylesTable.FIRST_CUSTOM_STYLE_ID + 9, + st.putNumberFormat("testFORMAT2")); + assertEquals(10, st.getNumDataFormats()); - // Save, load back in again, and check - XSSFWorkbook wb2 = XSSFTestDataSamples.writeOutAndReadBack(wb1); - wb1.close(); + // Save, load back in again, and check + XSSFWorkbook wb2 = XSSFTestDataSamples.writeOutAndReadBack(wb1); + wb1.close(); - ss = wb2.getStylesSource(); - assertNotNull(ss); + ss = wb2.getStylesSource(); + assertNotNull(ss); - assertEquals(10, st.getNumDataFormats()); - assertEquals(2, st.getFonts().size()); - assertEquals(2, st.getFills().size()); - assertEquals(1, st.getBorders().size()); - wb2.close(); - } + assertEquals(10, st.getNumDataFormats()); + assertEquals(2, st.getFonts().size()); + assertEquals(2, st.getFills().size()); + assertEquals(1, st.getBorders().size()); + wb2.close(); + } @Test - public void incrementSheetId() throws IOException { - XSSFWorkbook wb = new XSSFWorkbook(); - try { - int sheetId = (int)wb.createSheet().sheet.getSheetId(); - assertEquals(1, sheetId); - sheetId = (int)wb.createSheet().sheet.getSheetId(); - assertEquals(2, sheetId); + public void incrementSheetId() throws IOException { + XSSFWorkbook wb = new XSSFWorkbook(); + try { + int sheetId = (int)wb.createSheet().sheet.getSheetId(); + assertEquals(1, sheetId); + sheetId = (int)wb.createSheet().sheet.getSheetId(); + assertEquals(2, sheetId); - //test file with gaps in the sheetId sequence - XSSFWorkbook wbBack = XSSFTestDataSamples.openSampleWorkbook("47089.xlsm"); - try { - int lastSheetId = (int)wbBack.getSheetAt(wbBack.getNumberOfSheets() - 1).sheet.getSheetId(); - sheetId = (int)wbBack.createSheet().sheet.getSheetId(); - assertEquals(lastSheetId+1, sheetId); - } finally { - wbBack.close(); - } - } finally { - wb.close(); - } - } + //test file with gaps in the sheetId sequence + XSSFWorkbook wbBack = XSSFTestDataSamples.openSampleWorkbook("47089.xlsm"); + try { + int lastSheetId = (int)wbBack.getSheetAt(wbBack.getNumberOfSheets() - 1).sheet.getSheetId(); + sheetId = (int)wbBack.createSheet().sheet.getSheetId(); + assertEquals(lastSheetId+1, sheetId); + } finally { + wbBack.close(); + } + } finally { + wb.close(); + } + } - /** - * Test setting of core properties such as Title and Author - * @throws IOException - */ + /** + * Test setting of core properties such as Title and Author + * @throws IOException + */ @Test - public void workbookProperties() throws IOException { - XSSFWorkbook workbook = new XSSFWorkbook(); - try { - POIXMLProperties props = workbook.getProperties(); - assertNotNull(props); - //the Application property must be set for new workbooks, see Bugzilla #47559 - assertEquals("Apache POI", props.getExtendedProperties().getUnderlyingProperties().getApplication()); + public void workbookProperties() throws IOException { + XSSFWorkbook workbook = new XSSFWorkbook(); + try { + POIXMLProperties props = workbook.getProperties(); + assertNotNull(props); + //the Application property must be set for new workbooks, see Bugzilla #47559 + assertEquals("Apache POI", props.getExtendedProperties().getUnderlyingProperties().getApplication()); - PackagePropertiesPart opcProps = props.getCoreProperties().getUnderlyingProperties(); - assertNotNull(opcProps); + PackagePropertiesPart opcProps = props.getCoreProperties().getUnderlyingProperties(); + assertNotNull(opcProps); - opcProps.setTitleProperty("Testing Bugzilla #47460"); - assertEquals("Apache POI", opcProps.getCreatorProperty().getValue()); - opcProps.setCreatorProperty("poi-dev@poi.apache.org"); + opcProps.setTitleProperty("Testing Bugzilla #47460"); + assertEquals("Apache POI", opcProps.getCreatorProperty().getValue()); + opcProps.setCreatorProperty("poi-dev@poi.apache.org"); - XSSFWorkbook wbBack = XSSFTestDataSamples.writeOutAndReadBack(workbook); - assertEquals("Apache POI", wbBack.getProperties().getExtendedProperties().getUnderlyingProperties().getApplication()); - opcProps = wbBack.getProperties().getCoreProperties().getUnderlyingProperties(); - assertEquals("Testing Bugzilla #47460", opcProps.getTitleProperty().getValue()); - assertEquals("poi-dev@poi.apache.org", opcProps.getCreatorProperty().getValue()); - wbBack.close(); - } finally { - workbook.close(); - } - } + XSSFWorkbook wbBack = XSSFTestDataSamples.writeOutAndReadBack(workbook); + assertEquals("Apache POI", wbBack.getProperties().getExtendedProperties().getUnderlyingProperties().getApplication()); + opcProps = wbBack.getProperties().getCoreProperties().getUnderlyingProperties(); + assertEquals("Testing Bugzilla #47460", opcProps.getTitleProperty().getValue()); + assertEquals("poi-dev@poi.apache.org", opcProps.getCreatorProperty().getValue()); + wbBack.close(); + } finally { + workbook.close(); + } + } - /** - * Verify that the attached test data was not modified. If this test method - * fails, the test data is not working properly. - */ + /** + * Verify that the attached test data was not modified. If this test method + * fails, the test data is not working properly. + */ @Test - public void bug47668() throws Exception { - XSSFWorkbook workbook = XSSFTestDataSamples.openSampleWorkbook("47668.xlsx"); - List allPictures = workbook.getAllPictures(); - assertEquals(1, allPictures.size()); + public void bug47668() throws Exception { + XSSFWorkbook workbook = XSSFTestDataSamples.openSampleWorkbook("47668.xlsx"); + List allPictures = workbook.getAllPictures(); + assertEquals(1, allPictures.size()); - PackagePartName imagePartName = PackagingURIHelper - .createPartName("/xl/media/image1.jpeg"); - PackagePart imagePart = workbook.getPackage().getPart(imagePartName); - assertNotNull(imagePart); + PackagePartName imagePartName = PackagingURIHelper + .createPartName("/xl/media/image1.jpeg"); + PackagePart imagePart = workbook.getPackage().getPart(imagePartName); + assertNotNull(imagePart); - for (XSSFPictureData pictureData : allPictures) { - PackagePart picturePart = pictureData.getPackagePart(); - assertSame(imagePart, picturePart); - } + for (XSSFPictureData pictureData : allPictures) { + PackagePart picturePart = pictureData.getPackagePart(); + assertSame(imagePart, picturePart); + } - XSSFSheet sheet0 = workbook.getSheetAt(0); - XSSFDrawing drawing0 = sheet0.createDrawingPatriarch(); - XSSFPictureData pictureData0 = (XSSFPictureData) drawing0.getRelations().get(0); - byte[] data0 = pictureData0.getData(); - CRC32 crc0 = new CRC32(); - crc0.update(data0); + XSSFSheet sheet0 = workbook.getSheetAt(0); + XSSFDrawing drawing0 = sheet0.createDrawingPatriarch(); + XSSFPictureData pictureData0 = (XSSFPictureData) drawing0.getRelations().get(0); + byte[] data0 = pictureData0.getData(); + CRC32 crc0 = new CRC32(); + crc0.update(data0); - XSSFSheet sheet1 = workbook.getSheetAt(1); - XSSFDrawing drawing1 = sheet1.createDrawingPatriarch(); - XSSFPictureData pictureData1 = (XSSFPictureData) drawing1.getRelations().get(0); - byte[] data1 = pictureData1.getData(); - CRC32 crc1 = new CRC32(); - crc1.update(data1); + XSSFSheet sheet1 = workbook.getSheetAt(1); + XSSFDrawing drawing1 = sheet1.createDrawingPatriarch(); + XSSFPictureData pictureData1 = (XSSFPictureData) drawing1.getRelations().get(0); + byte[] data1 = pictureData1.getData(); + CRC32 crc1 = new CRC32(); + crc1.update(data1); - assertEquals(crc0.getValue(), crc1.getValue()); - workbook.close(); - } + assertEquals(crc0.getValue(), crc1.getValue()); + workbook.close(); + } - /** - * When deleting a sheet make sure that we adjust sheet indices of named ranges - */ + /** + * When deleting a sheet make sure that we adjust sheet indices of named ranges + */ @Test - public void bug47737() throws IOException { - XSSFWorkbook wb = XSSFTestDataSamples.openSampleWorkbook("47737.xlsx"); - assertEquals(2, wb.getNumberOfNames()); - assertNotNull(wb.getCalculationChain()); + public void bug47737() throws IOException { + XSSFWorkbook wb = XSSFTestDataSamples.openSampleWorkbook("47737.xlsx"); + assertEquals(2, wb.getNumberOfNames()); + assertNotNull(wb.getCalculationChain()); - XSSFName nm0 = wb.getNameAt(0); - assertTrue(nm0.getCTName().isSetLocalSheetId()); - assertEquals(0, nm0.getCTName().getLocalSheetId()); + XSSFName nm0 = wb.getNameAt(0); + assertTrue(nm0.getCTName().isSetLocalSheetId()); + assertEquals(0, nm0.getCTName().getLocalSheetId()); - XSSFName nm1 = wb.getNameAt(1); - assertTrue(nm1.getCTName().isSetLocalSheetId()); - assertEquals(1, nm1.getCTName().getLocalSheetId()); + XSSFName nm1 = wb.getNameAt(1); + assertTrue(nm1.getCTName().isSetLocalSheetId()); + assertEquals(1, nm1.getCTName().getLocalSheetId()); - wb.removeSheetAt(0); - assertEquals(1, wb.getNumberOfNames()); - XSSFName nm2 = wb.getNameAt(0); - assertTrue(nm2.getCTName().isSetLocalSheetId()); - assertEquals(0, nm2.getCTName().getLocalSheetId()); - //calculation chain is removed as well - assertNull(wb.getCalculationChain()); - wb.close(); + wb.removeSheetAt(0); + assertEquals(1, wb.getNumberOfNames()); + XSSFName nm2 = wb.getNameAt(0); + assertTrue(nm2.getCTName().isSetLocalSheetId()); + assertEquals(0, nm2.getCTName().getLocalSheetId()); + //calculation chain is removed as well + assertNull(wb.getCalculationChain()); + wb.close(); - } + } - /** - * Problems with XSSFWorkbook.removeSheetAt when workbook contains charts - */ + /** + * Problems with XSSFWorkbook.removeSheetAt when workbook contains charts + */ @Test - public void bug47813() throws IOException { - XSSFWorkbook wb1 = XSSFTestDataSamples.openSampleWorkbook("47813.xlsx"); - assertEquals(3, wb1.getNumberOfSheets()); - assertNotNull(wb1.getCalculationChain()); + public void bug47813() throws IOException { + XSSFWorkbook wb1 = XSSFTestDataSamples.openSampleWorkbook("47813.xlsx"); + assertEquals(3, wb1.getNumberOfSheets()); + assertNotNull(wb1.getCalculationChain()); - assertEquals("Numbers", wb1.getSheetName(0)); - //the second sheet is of type 'chartsheet' - assertEquals("Chart", wb1.getSheetName(1)); - assertTrue(wb1.getSheetAt(1) instanceof XSSFChartSheet); - assertEquals("SomeJunk", wb1.getSheetName(2)); + assertEquals("Numbers", wb1.getSheetName(0)); + //the second sheet is of type 'chartsheet' + assertEquals("Chart", wb1.getSheetName(1)); + assertTrue(wb1.getSheetAt(1) instanceof XSSFChartSheet); + assertEquals("SomeJunk", wb1.getSheetName(2)); - wb1.removeSheetAt(2); - assertEquals(2, wb1.getNumberOfSheets()); - assertNull(wb1.getCalculationChain()); + wb1.removeSheetAt(2); + assertEquals(2, wb1.getNumberOfSheets()); + assertNull(wb1.getCalculationChain()); - XSSFWorkbook wb2 = XSSFTestDataSamples.writeOutAndReadBack(wb1); - assertEquals(2, wb2.getNumberOfSheets()); - assertNull(wb2.getCalculationChain()); + XSSFWorkbook wb2 = XSSFTestDataSamples.writeOutAndReadBack(wb1); + assertEquals(2, wb2.getNumberOfSheets()); + assertNull(wb2.getCalculationChain()); - assertEquals("Numbers", wb2.getSheetName(0)); - assertEquals("Chart", wb2.getSheetName(1)); - wb2.close(); - wb1.close(); - } + assertEquals("Numbers", wb2.getSheetName(0)); + assertEquals("Chart", wb2.getSheetName(1)); + wb2.close(); + wb1.close(); + } - /** - * Problems with the count of the number of styles - * coming out wrong - */ + /** + * Problems with the count of the number of styles + * coming out wrong + */ @Test - public void bug49702() throws IOException { - // First try with a new file - XSSFWorkbook wb1 = new XSSFWorkbook(); + public void bug49702() throws IOException { + // First try with a new file + XSSFWorkbook wb1 = new XSSFWorkbook(); - // Should have one style - assertEquals(1, wb1.getNumCellStyles()); - wb1.getCellStyleAt((short)0); - try { - wb1.getCellStyleAt((short)1); - fail("Shouldn't be able to get style at 1 that doesn't exist"); - } catch(IndexOutOfBoundsException e) {} + // Should have one style + assertEquals(1, wb1.getNumCellStyles()); + wb1.getCellStyleAt((short)0); + try { + wb1.getCellStyleAt((short)1); + fail("Shouldn't be able to get style at 1 that doesn't exist"); + } catch(IndexOutOfBoundsException e) {} - // Add another one - CellStyle cs = wb1.createCellStyle(); - cs.setDataFormat((short)11); + // Add another one + CellStyle cs = wb1.createCellStyle(); + cs.setDataFormat((short)11); - // Re-check - assertEquals(2, wb1.getNumCellStyles()); - wb1.getCellStyleAt((short)0); - wb1.getCellStyleAt((short)1); - try { - wb1.getCellStyleAt((short)2); - fail("Shouldn't be able to get style at 2 that doesn't exist"); - } catch(IndexOutOfBoundsException e) {} + // Re-check + assertEquals(2, wb1.getNumCellStyles()); + wb1.getCellStyleAt((short)0); + wb1.getCellStyleAt((short)1); + try { + wb1.getCellStyleAt((short)2); + fail("Shouldn't be able to get style at 2 that doesn't exist"); + } catch(IndexOutOfBoundsException e) {} - // Save and reload - XSSFWorkbook nwb = XSSFTestDataSamples.writeOutAndReadBack(wb1); - assertEquals(2, nwb.getNumCellStyles()); - nwb.getCellStyleAt((short)0); - nwb.getCellStyleAt((short)1); - try { - nwb.getCellStyleAt((short)2); - fail("Shouldn't be able to get style at 2 that doesn't exist"); - } catch(IndexOutOfBoundsException e) {} + // Save and reload + XSSFWorkbook nwb = XSSFTestDataSamples.writeOutAndReadBack(wb1); + assertEquals(2, nwb.getNumCellStyles()); + nwb.getCellStyleAt((short)0); + nwb.getCellStyleAt((short)1); + try { + nwb.getCellStyleAt((short)2); + fail("Shouldn't be able to get style at 2 that doesn't exist"); + } catch(IndexOutOfBoundsException e) {} - // Now with an existing file - XSSFWorkbook wb2 = XSSFTestDataSamples.openSampleWorkbook("sample.xlsx"); - assertEquals(3, wb2.getNumCellStyles()); - wb2.getCellStyleAt((short)0); - wb2.getCellStyleAt((short)1); - wb2.getCellStyleAt((short)2); - try { - wb2.getCellStyleAt((short)3); - fail("Shouldn't be able to get style at 3 that doesn't exist"); - } catch(IndexOutOfBoundsException e) {} + // Now with an existing file + XSSFWorkbook wb2 = XSSFTestDataSamples.openSampleWorkbook("sample.xlsx"); + assertEquals(3, wb2.getNumCellStyles()); + wb2.getCellStyleAt((short)0); + wb2.getCellStyleAt((short)1); + wb2.getCellStyleAt((short)2); + try { + wb2.getCellStyleAt((short)3); + fail("Shouldn't be able to get style at 3 that doesn't exist"); + } catch(IndexOutOfBoundsException e) {} - wb2.close(); - wb1.close(); - nwb.close(); - } + wb2.close(); + wb1.close(); + nwb.close(); + } @Test public void recalcId() throws IOException { @@ -542,14 +542,14 @@ public final class TestXSSFWorkbook extends BaseTestXWorkbook { } @Test - public void columnWidthPOI52233() throws Exception { - XSSFWorkbook workbook = new XSSFWorkbook(); - XSSFSheet sheet = workbook.createSheet(); - XSSFRow row = sheet.createRow(0); - XSSFCell cell = row.createCell(0); - cell.setCellValue("hello world"); + public void columnWidthPOI52233() throws Exception { + XSSFWorkbook workbook = new XSSFWorkbook(); + XSSFSheet sheet = workbook.createSheet(); + XSSFRow row = sheet.createRow(0); + XSSFCell cell = row.createCell(0); + cell.setCellValue("hello world"); - sheet = workbook.createSheet(); + sheet = workbook.createSheet(); sheet.setColumnWidth(4, 5000); sheet.setColumnWidth(5, 5000); @@ -557,114 +557,114 @@ public final class TestXSSFWorkbook extends BaseTestXWorkbook { accessWorkbook(workbook); - ByteArrayOutputStream stream = new ByteArrayOutputStream(); - try { - workbook.write(stream); - } finally { - stream.close(); - } + ByteArrayOutputStream stream = new ByteArrayOutputStream(); + try { + workbook.write(stream); + } finally { + stream.close(); + } - accessWorkbook(workbook); - workbook.close(); - } + accessWorkbook(workbook); + workbook.close(); + } - private void accessWorkbook(XSSFWorkbook workbook) { - workbook.getSheetAt(1).setColumnGroupCollapsed(4, true); - workbook.getSheetAt(1).setColumnGroupCollapsed(4, false); + private void accessWorkbook(XSSFWorkbook workbook) { + workbook.getSheetAt(1).setColumnGroupCollapsed(4, true); + workbook.getSheetAt(1).setColumnGroupCollapsed(4, false); - assertEquals("hello world", workbook.getSheetAt(0).getRow(0).getCell(0).getStringCellValue()); - assertEquals(2048, workbook.getSheetAt(0).getColumnWidth(0)); // <-works - } + assertEquals("hello world", workbook.getSheetAt(0).getRow(0).getCell(0).getStringCellValue()); + assertEquals(2048, workbook.getSheetAt(0).getColumnWidth(0)); // <-works + } @Test - public void bug48495() throws IOException { - Workbook wb = XSSFTestDataSamples.openSampleWorkbook("48495.xlsx"); + public void bug48495() throws IOException { + Workbook wb = XSSFTestDataSamples.openSampleWorkbook("48495.xlsx"); - assertSheetOrder(wb, "Sheet1"); + assertSheetOrder(wb, "Sheet1"); - Sheet sheet = wb.getSheetAt(0); - sheet.shiftRows(2, sheet.getLastRowNum(), 1, true, false); - Row newRow = sheet.getRow(2); - if (newRow == null) newRow = sheet.createRow(2); - newRow.createCell(0).setCellValue(" Another Header"); - wb.cloneSheet(0); + Sheet sheet = wb.getSheetAt(0); + sheet.shiftRows(2, sheet.getLastRowNum(), 1, true, false); + Row newRow = sheet.getRow(2); + if (newRow == null) newRow = sheet.createRow(2); + newRow.createCell(0).setCellValue(" Another Header"); + wb.cloneSheet(0); - assertSheetOrder(wb, "Sheet1", "Sheet1 (2)"); + assertSheetOrder(wb, "Sheet1", "Sheet1 (2)"); - // FileOutputStream fileOut = new FileOutputStream("/tmp/bug48495.xlsx"); -// try { -// wb.write(fileOut); -// } finally { -// fileOut.close(); -// } + // FileOutputStream fileOut = new FileOutputStream("/tmp/bug48495.xlsx"); +// try { +// wb.write(fileOut); +// } finally { +// fileOut.close(); +// } - Workbook read = XSSFTestDataSamples.writeOutAndReadBack(wb); - assertNotNull(read); - assertSheetOrder(read, "Sheet1", "Sheet1 (2)"); - read.close(); - wb.close(); - } + Workbook read = XSSFTestDataSamples.writeOutAndReadBack(wb); + assertNotNull(read); + assertSheetOrder(read, "Sheet1", "Sheet1 (2)"); + read.close(); + wb.close(); + } @Test - public void bug47090a() throws IOException { - Workbook workbook = XSSFTestDataSamples.openSampleWorkbook("47090.xlsx"); - assertSheetOrder(workbook, "Sheet1", "Sheet2"); - workbook.removeSheetAt(0); - assertSheetOrder(workbook, "Sheet2"); - workbook.createSheet(); - assertSheetOrder(workbook, "Sheet2", "Sheet1"); - Workbook read = XSSFTestDataSamples.writeOutAndReadBack(workbook); - assertSheetOrder(read, "Sheet2", "Sheet1"); - read.close(); - workbook.close(); - } + public void bug47090a() throws IOException { + Workbook workbook = XSSFTestDataSamples.openSampleWorkbook("47090.xlsx"); + assertSheetOrder(workbook, "Sheet1", "Sheet2"); + workbook.removeSheetAt(0); + assertSheetOrder(workbook, "Sheet2"); + workbook.createSheet(); + assertSheetOrder(workbook, "Sheet2", "Sheet1"); + Workbook read = XSSFTestDataSamples.writeOutAndReadBack(workbook); + assertSheetOrder(read, "Sheet2", "Sheet1"); + read.close(); + workbook.close(); + } @Test - public void bug47090b() throws IOException { - Workbook workbook = XSSFTestDataSamples.openSampleWorkbook("47090.xlsx"); - assertSheetOrder(workbook, "Sheet1", "Sheet2"); - workbook.removeSheetAt(1); - assertSheetOrder(workbook, "Sheet1"); - workbook.createSheet(); - assertSheetOrder(workbook, "Sheet1", "Sheet0"); // Sheet0 because it uses "Sheet" + sheets.size() as starting point! - Workbook read = XSSFTestDataSamples.writeOutAndReadBack(workbook); - assertSheetOrder(read, "Sheet1", "Sheet0"); - read.close(); - workbook.close(); - } + public void bug47090b() throws IOException { + Workbook workbook = XSSFTestDataSamples.openSampleWorkbook("47090.xlsx"); + assertSheetOrder(workbook, "Sheet1", "Sheet2"); + workbook.removeSheetAt(1); + assertSheetOrder(workbook, "Sheet1"); + workbook.createSheet(); + assertSheetOrder(workbook, "Sheet1", "Sheet0"); // Sheet0 because it uses "Sheet" + sheets.size() as starting point! + Workbook read = XSSFTestDataSamples.writeOutAndReadBack(workbook); + assertSheetOrder(read, "Sheet1", "Sheet0"); + read.close(); + workbook.close(); + } @Test - public void bug47090c() throws IOException { - Workbook workbook = XSSFTestDataSamples.openSampleWorkbook("47090.xlsx"); - assertSheetOrder(workbook, "Sheet1", "Sheet2"); - workbook.removeSheetAt(0); - assertSheetOrder(workbook, "Sheet2"); - workbook.cloneSheet(0); - assertSheetOrder(workbook, "Sheet2", "Sheet2 (2)"); - Workbook read = XSSFTestDataSamples.writeOutAndReadBack(workbook); - assertSheetOrder(read, "Sheet2", "Sheet2 (2)"); - read.close(); - workbook.close(); - } + public void bug47090c() throws IOException { + Workbook workbook = XSSFTestDataSamples.openSampleWorkbook("47090.xlsx"); + assertSheetOrder(workbook, "Sheet1", "Sheet2"); + workbook.removeSheetAt(0); + assertSheetOrder(workbook, "Sheet2"); + workbook.cloneSheet(0); + assertSheetOrder(workbook, "Sheet2", "Sheet2 (2)"); + Workbook read = XSSFTestDataSamples.writeOutAndReadBack(workbook); + assertSheetOrder(read, "Sheet2", "Sheet2 (2)"); + read.close(); + workbook.close(); + } @Test - public void bug47090d() throws IOException { - Workbook workbook = XSSFTestDataSamples.openSampleWorkbook("47090.xlsx"); - assertSheetOrder(workbook, "Sheet1", "Sheet2"); - workbook.createSheet(); - assertSheetOrder(workbook, "Sheet1", "Sheet2", "Sheet0"); - workbook.removeSheetAt(0); - assertSheetOrder(workbook, "Sheet2", "Sheet0"); - workbook.createSheet(); - assertSheetOrder(workbook, "Sheet2", "Sheet0", "Sheet1"); - Workbook read = XSSFTestDataSamples.writeOutAndReadBack(workbook); - assertSheetOrder(read, "Sheet2", "Sheet0", "Sheet1"); - read.close(); - workbook.close(); - } + public void bug47090d() throws IOException { + Workbook workbook = XSSFTestDataSamples.openSampleWorkbook("47090.xlsx"); + assertSheetOrder(workbook, "Sheet1", "Sheet2"); + workbook.createSheet(); + assertSheetOrder(workbook, "Sheet1", "Sheet2", "Sheet0"); + workbook.removeSheetAt(0); + assertSheetOrder(workbook, "Sheet2", "Sheet0"); + workbook.createSheet(); + assertSheetOrder(workbook, "Sheet2", "Sheet0", "Sheet1"); + Workbook read = XSSFTestDataSamples.writeOutAndReadBack(workbook); + assertSheetOrder(read, "Sheet2", "Sheet0", "Sheet1"); + read.close(); + workbook.close(); + } @Test - public void bug51158() throws IOException { + public void bug51158() throws IOException { // create a workbook final XSSFWorkbook wb1 = new XSSFWorkbook(); XSSFSheet sheet = wb1.createSheet("Test Sheet"); @@ -706,10 +706,10 @@ public final class TestXSSFWorkbook extends BaseTestXWorkbook { wb3.close(); wb2.close(); wb1.close(); - } + } @Test - public void bug51158a() throws IOException { + public void bug51158a() throws IOException { // create a workbook final XSSFWorkbook workbook = new XSSFWorkbook(); try { @@ -1091,20 +1091,52 @@ public final class TestXSSFWorkbook extends BaseTestXWorkbook { //assertCloseDoesNotModifyFile(filename, wb); } - @Test - public void testCloseBeforeWrite() throws IOException { - Workbook wb = new XSSFWorkbook(); - wb.createSheet("somesheet"); + @Test + public void testCloseBeforeWrite() throws IOException { + Workbook wb = new XSSFWorkbook(); + wb.createSheet("somesheet"); - // test what happens if we close the Workbook before we write it out - wb.close(); + // test what happens if we close the Workbook before we write it out + wb.close(); - try { - XSSFTestDataSamples.writeOutAndReadBack(wb); - fail("Expecting IOException here"); - } catch (RuntimeException e) { - // expected here - assertTrue("Had: " + e.getCause(), e.getCause() instanceof IOException); - } - } + try { + XSSFTestDataSamples.writeOutAndReadBack(wb); + fail("Expecting IOException here"); + } catch (RuntimeException e) { + // expected here + assertTrue("Had: " + e.getCause(), e.getCause() instanceof IOException); + } + } + + /** + * See bug #57840 test data tables + */ + @Test + public void getTable() throws IOException { + XSSFWorkbook wb = XSSFTestDataSamples.openSampleWorkbook("WithTable.xlsx"); + XSSFTable table1 = wb.getTable("Tabella1"); + assertNotNull("Tabella1 was not found in workbook", table1); + assertEquals("Table name", "Tabella1", table1.getName()); + assertEquals("Sheet name", "Foglio1", table1.getSheetName()); + + // Table lookup should be case-insensitive + assertSame("Case insensitive table name lookup", table1, wb.getTable("TABELLA1")); + + // If workbook does not contain any data tables matching the provided name, getTable should return null + assertNull("Null table name should not throw NPE", wb.getTable(null)); + assertNull("Should not be able to find non-existent table", wb.getTable("Foglio1")); + + // If a table is added after getTable is called it should still be reachable by XSSFWorkbook.getTable + // This test makes sure that if any caching is done that getTable never uses a stale cache + XSSFTable table2 = wb.getSheet("Foglio2").createTable(); + table2.setName("Table2"); + assertSame("Did not find Table2", table2, wb.getTable("Table2")); + + // If table name is modified after getTable is called, the table can only be found by its new name + // This test makes sure that if any caching is done that getTable never uses a stale cache + table1.setName("Table1"); + assertSame("Did not find Tabella1 renamed to Table1", table1, wb.getTable("TABLE1")); + + wb.close(); + } } diff --git a/src/testcases/org/apache/poi/hssf/model/AllModelTests.java b/src/testcases/org/apache/poi/hssf/model/AllModelTests.java index 0d140ab7d..bca29c74f 100644 --- a/src/testcases/org/apache/poi/hssf/model/AllModelTests.java +++ b/src/testcases/org/apache/poi/hssf/model/AllModelTests.java @@ -27,7 +27,7 @@ import org.junit.runners.Suite; @Suite.SuiteClasses({ TestDrawingManager.class, TestDrawingManager2.class, - TestFormulaParser.class, + //TestFormulaParser.class, //converted to junit4 TestFormulaParserEval.class, TestFormulaParserIf.class, TestLinkTable.class, diff --git a/src/testcases/org/apache/poi/hssf/model/TestFormulaParser.java b/src/testcases/org/apache/poi/hssf/model/TestFormulaParser.java index f39d07b94..82153fe09 100644 --- a/src/testcases/org/apache/poi/hssf/model/TestFormulaParser.java +++ b/src/testcases/org/apache/poi/hssf/model/TestFormulaParser.java @@ -87,271 +87,271 @@ import org.junit.Test; */ public final class TestFormulaParser { - /** - * @return parsed token array already confirmed not null - */ - /* package */ static Ptg[] parseFormula(String formula) { - Ptg[] result = HSSFFormulaParser.parse(formula, (HSSFWorkbook)null); - assertNotNull("Ptg array should not be null", result); - return result; - } - private static String toFormulaString(Ptg[] ptgs) { - return HSSFFormulaParser.toFormulaString((HSSFWorkbook)null, ptgs); - } + /** + * @return parsed token array already confirmed not null + */ + /* package */ static Ptg[] parseFormula(String formula) { + Ptg[] result = HSSFFormulaParser.parse(formula, (HSSFWorkbook)null); + assertNotNull("Ptg array should not be null", result); + return result; + } + private static String toFormulaString(Ptg[] ptgs) { + return HSSFFormulaParser.toFormulaString((HSSFWorkbook)null, ptgs); + } - @Test - public void testSimpleFormula() { - confirmTokenClasses("2+2",IntPtg.class, IntPtg.class, AddPtg.class); - } + @Test + public void testSimpleFormula() { + confirmTokenClasses("2+2",IntPtg.class, IntPtg.class, AddPtg.class); + } - @Test - public void testFormulaWithSpace1() { - confirmTokenClasses(" 2 + 2 ",IntPtg.class, IntPtg.class, AddPtg.class); - } + @Test + public void testFormulaWithSpace1() { + confirmTokenClasses(" 2 + 2 ",IntPtg.class, IntPtg.class, AddPtg.class); + } - @Test - public void testFormulaWithSpace2() { - Ptg[] ptgs = parseFormula("2+ sum( 3 , 4) "); - assertEquals(5, ptgs.length); - } + @Test + public void testFormulaWithSpace2() { + Ptg[] ptgs = parseFormula("2+ sum( 3 , 4) "); + assertEquals(5, ptgs.length); + } - @Test - public void testFormulaWithSpaceNRef() { - Ptg[] ptgs = parseFormula("sum( A2:A3 )"); - assertEquals(2, ptgs.length); - } + @Test + public void testFormulaWithSpaceNRef() { + Ptg[] ptgs = parseFormula("sum( A2:A3 )"); + assertEquals(2, ptgs.length); + } - @Test - public void testFormulaWithString() { - Ptg[] ptgs = parseFormula("\"hello\" & \"world\" "); - assertEquals(3, ptgs.length); - } + @Test + public void testFormulaWithString() { + Ptg[] ptgs = parseFormula("\"hello\" & \"world\" "); + assertEquals(3, ptgs.length); + } - @Test - public void testTRUE() { - Ptg[] ptgs = parseFormula("TRUE"); - assertEquals(1, ptgs.length); - BoolPtg flag = (BoolPtg) ptgs[0]; - assertEquals(true, flag.getValue()); - } + @Test + public void testTRUE() { + Ptg[] ptgs = parseFormula("TRUE"); + assertEquals(1, ptgs.length); + BoolPtg flag = (BoolPtg) ptgs[0]; + assertEquals(true, flag.getValue()); + } - @Test - public void testSumIf() { - Ptg[] ptgs = parseFormula("SUMIF(A1:A5,\">4000\",B1:B5)"); - assertEquals(4, ptgs.length); - } + @Test + public void testSumIf() { + Ptg[] ptgs = parseFormula("SUMIF(A1:A5,\">4000\",B1:B5)"); + assertEquals(4, ptgs.length); + } - /** - * Bug Reported by xt-jens.riis@nokia.com (Jens Riis) - * Refers to Bug #17582 - * - */ - @Test - public void testNonAlphaFormula() { - Ptg[] ptgs = parseFormula("\"TOTAL[\"&F3&\"]\""); - confirmTokenClasses(ptgs, StringPtg.class, RefPtg.class, ConcatPtg.class, StringPtg.class, ConcatPtg.class); - assertEquals("TOTAL[", ((StringPtg)ptgs[0]).getValue()); - } + /** + * Bug Reported by xt-jens.riis@nokia.com (Jens Riis) + * Refers to Bug #17582 + * + */ + @Test + public void testNonAlphaFormula() { + Ptg[] ptgs = parseFormula("\"TOTAL[\"&F3&\"]\""); + confirmTokenClasses(ptgs, StringPtg.class, RefPtg.class, ConcatPtg.class, StringPtg.class, ConcatPtg.class); + assertEquals("TOTAL[", ((StringPtg)ptgs[0]).getValue()); + } - @Test - public void testMacroFunction() throws IOException { - // testNames.xls contains a VB function called 'myFunc' - final String testFile = "testNames.xls"; - HSSFWorkbook wb = HSSFTestDataSamples.openSampleWorkbook(testFile); - try { - HSSFEvaluationWorkbook book = HSSFEvaluationWorkbook.create(wb); + @Test + public void testMacroFunction() throws IOException { + // testNames.xls contains a VB function called 'myFunc' + final String testFile = "testNames.xls"; + HSSFWorkbook wb = HSSFTestDataSamples.openSampleWorkbook(testFile); + try { + HSSFEvaluationWorkbook book = HSSFEvaluationWorkbook.create(wb); - //Expected ptg stack: [NamePtg(myFunc), StringPtg(arg), (additional operands go here...), FunctionPtg(myFunc)] - Ptg[] ptg = FormulaParser.parse("myFunc(\"arg\")", book, FormulaType.CELL, -1); - assertEquals(3, ptg.length); + //Expected ptg stack: [NamePtg(myFunc), StringPtg(arg), (additional operands go here...), FunctionPtg(myFunc)] + Ptg[] ptg = FormulaParser.parse("myFunc(\"arg\")", book, FormulaType.CELL, -1); + assertEquals(3, ptg.length); - // the name gets encoded as the first operand on the stack - NamePtg tname = (NamePtg) ptg[0]; - assertEquals("myFunc", tname.toFormulaString(book)); + // the name gets encoded as the first operand on the stack + NamePtg tname = (NamePtg) ptg[0]; + assertEquals("myFunc", tname.toFormulaString(book)); - // the function's arguments are pushed onto the stack from left-to-right as OperandPtgs - StringPtg arg = (StringPtg) ptg[1]; - assertEquals("arg", arg.getValue()); + // the function's arguments are pushed onto the stack from left-to-right as OperandPtgs + StringPtg arg = (StringPtg) ptg[1]; + assertEquals("arg", arg.getValue()); - // The external FunctionPtg is the last Ptg added to the stack - // During formula evaluation, this Ptg pops off the the appropriate number of - // arguments (getNumberOfOperands()) and pushes the result on the stack - AbstractFunctionPtg tfunc = (AbstractFunctionPtg) ptg[2]; //FuncVarPtg - assertTrue(tfunc.isExternalFunction()); + // The external FunctionPtg is the last Ptg added to the stack + // During formula evaluation, this Ptg pops off the the appropriate number of + // arguments (getNumberOfOperands()) and pushes the result on the stack + AbstractFunctionPtg tfunc = (AbstractFunctionPtg) ptg[2]; //FuncVarPtg + assertTrue(tfunc.isExternalFunction()); - // confirm formula parsing is case-insensitive - FormulaParser.parse("mYfUnC(\"arg\")", book, FormulaType.CELL, -1); + // confirm formula parsing is case-insensitive + FormulaParser.parse("mYfUnC(\"arg\")", book, FormulaType.CELL, -1); - // confirm formula parsing doesn't care about argument count or type - // this should only throw an error when evaluating the formula. - FormulaParser.parse("myFunc()", book, FormulaType.CELL, -1); - FormulaParser.parse("myFunc(\"arg\", 0, TRUE)", book, FormulaType.CELL, -1); + // confirm formula parsing doesn't care about argument count or type + // this should only throw an error when evaluating the formula. + FormulaParser.parse("myFunc()", book, FormulaType.CELL, -1); + FormulaParser.parse("myFunc(\"arg\", 0, TRUE)", book, FormulaType.CELL, -1); - // A completely unknown formula name (not saved in workbook) should still be parseable and renderable - // but will throw an NotImplementedFunctionException or return a #NAME? error value if evaluated. - FormulaParser.parse("yourFunc(\"arg\")", book, FormulaType.CELL, -1); + // A completely unknown formula name (not saved in workbook) should still be parseable and renderable + // but will throw an NotImplementedFunctionException or return a #NAME? error value if evaluated. + FormulaParser.parse("yourFunc(\"arg\")", book, FormulaType.CELL, -1); - // Verify that myFunc and yourFunc were successfully added to Workbook names - HSSFWorkbook wb2 = HSSFTestDataSamples.writeOutAndReadBack(wb); - try { - // HSSFWorkbook/EXCEL97-specific side-effects user-defined function names must be added to Workbook's defined names in order to be saved. - assertNotNull(wb2.getName("myFunc")); - assertEqualsIgnoreCase("myFunc", wb2.getName("myFunc").getNameName()); - assertNotNull(wb2.getName("yourFunc")); - assertEqualsIgnoreCase("yourFunc", wb2.getName("yourFunc").getNameName()); + // Verify that myFunc and yourFunc were successfully added to Workbook names + HSSFWorkbook wb2 = HSSFTestDataSamples.writeOutAndReadBack(wb); + try { + // HSSFWorkbook/EXCEL97-specific side-effects user-defined function names must be added to Workbook's defined names in order to be saved. + assertNotNull(wb2.getName("myFunc")); + assertEqualsIgnoreCase("myFunc", wb2.getName("myFunc").getNameName()); + assertNotNull(wb2.getName("yourFunc")); + assertEqualsIgnoreCase("yourFunc", wb2.getName("yourFunc").getNameName()); - // Manually check to make sure file isn't corrupted - // TODO: develop a process for occasionally manually reviewing workbooks - // to verify workbooks are not corrupted - /* - final File fileIn = HSSFTestDataSamples.getSampleFile(testFile); - final File reSavedFile = new File(fileIn.getParentFile(), fileIn.getName().replace(".xls", "-saved.xls")); - FileOutputStream fos = new FileOutputStream(reSavedFile); - wb2.write(fos); - fos.close(); - */ - } finally { - wb2.close(); - } - } finally { - wb.close(); - } - } - - private final static void assertEqualsIgnoreCase(String expected, String actual) { - assertEquals(expected.toLowerCase(Locale.ROOT), actual.toLowerCase(Locale.ROOT)); - } + // Manually check to make sure file isn't corrupted + // TODO: develop a process for occasionally manually reviewing workbooks + // to verify workbooks are not corrupted + /* + final File fileIn = HSSFTestDataSamples.getSampleFile(testFile); + final File reSavedFile = new File(fileIn.getParentFile(), fileIn.getName().replace(".xls", "-saved.xls")); + FileOutputStream fos = new FileOutputStream(reSavedFile); + wb2.write(fos); + fos.close(); + */ + } finally { + wb2.close(); + } + } finally { + wb.close(); + } + } + + private final static void assertEqualsIgnoreCase(String expected, String actual) { + assertEquals(expected.toLowerCase(Locale.ROOT), actual.toLowerCase(Locale.ROOT)); + } - @Test - public void testEmbeddedSlash() { - confirmTokenClasses("HYPERLINK(\"http://www.jakarta.org\",\"Jakarta\")", - StringPtg.class, StringPtg.class, FuncVarPtg.class); - } + @Test + public void testEmbeddedSlash() { + confirmTokenClasses("HYPERLINK(\"http://www.jakarta.org\",\"Jakarta\")", + StringPtg.class, StringPtg.class, FuncVarPtg.class); + } - @Test - public void testConcatenate() { - confirmTokenClasses("CONCATENATE(\"first\",\"second\")", - StringPtg.class, StringPtg.class, FuncVarPtg.class); - } + @Test + public void testConcatenate() { + confirmTokenClasses("CONCATENATE(\"first\",\"second\")", + StringPtg.class, StringPtg.class, FuncVarPtg.class); + } - @Test - public void testWorksheetReferences() throws IOException { - HSSFWorkbook wb = new HSSFWorkbook(); + @Test + public void testWorksheetReferences() throws IOException { + HSSFWorkbook wb = new HSSFWorkbook(); - wb.createSheet("NoQuotesNeeded"); - wb.createSheet("Quotes Needed Here &#$@"); + wb.createSheet("NoQuotesNeeded"); + wb.createSheet("Quotes Needed Here &#$@"); - HSSFSheet sheet = wb.createSheet("Test"); - HSSFRow row = sheet.createRow(0); - HSSFCell cell; + HSSFSheet sheet = wb.createSheet("Test"); + HSSFRow row = sheet.createRow(0); + HSSFCell cell; - cell = row.createCell(0); - cell.setCellFormula("NoQuotesNeeded!A1"); + cell = row.createCell(0); + cell.setCellFormula("NoQuotesNeeded!A1"); - cell = row.createCell(1); - cell.setCellFormula("'Quotes Needed Here &#$@'!A1"); - - wb.close(); - } + cell = row.createCell(1); + cell.setCellFormula("'Quotes Needed Here &#$@'!A1"); + + wb.close(); + } - @Test - public void testUnaryMinus() { - confirmTokenClasses("-A1", RefPtg.class, UnaryMinusPtg.class); - } + @Test + public void testUnaryMinus() { + confirmTokenClasses("-A1", RefPtg.class, UnaryMinusPtg.class); + } - @Test - public void testUnaryPlus() { - confirmTokenClasses("+A1", RefPtg.class, UnaryPlusPtg.class); - } + @Test + public void testUnaryPlus() { + confirmTokenClasses("+A1", RefPtg.class, UnaryPlusPtg.class); + } - /** - * There may be multiple ways to encode an expression involving {@link UnaryPlusPtg} - * or {@link UnaryMinusPtg}. These may be perfectly equivalent from a formula - * evaluation perspective, or formula rendering. However, differences in the way - * POI encodes formulas may cause unnecessary confusion. These non-critical tests - * check that POI follows the same encoding rules as Excel. - */ - @Test - public void testExactEncodingOfUnaryPlusAndMinus() { - // as tested in Excel: - confirmUnary("-3", -3, NumberPtg.class); - confirmUnary("--4", -4, NumberPtg.class, UnaryMinusPtg.class); - confirmUnary("+++5", 5, IntPtg.class, UnaryPlusPtg.class, UnaryPlusPtg.class); - confirmUnary("++-6", -6, NumberPtg.class, UnaryPlusPtg.class, UnaryPlusPtg.class); + /** + * There may be multiple ways to encode an expression involving {@link UnaryPlusPtg} + * or {@link UnaryMinusPtg}. These may be perfectly equivalent from a formula + * evaluation perspective, or formula rendering. However, differences in the way + * POI encodes formulas may cause unnecessary confusion. These non-critical tests + * check that POI follows the same encoding rules as Excel. + */ + @Test + public void testExactEncodingOfUnaryPlusAndMinus() { + // as tested in Excel: + confirmUnary("-3", -3, NumberPtg.class); + confirmUnary("--4", -4, NumberPtg.class, UnaryMinusPtg.class); + confirmUnary("+++5", 5, IntPtg.class, UnaryPlusPtg.class, UnaryPlusPtg.class); + confirmUnary("++-6", -6, NumberPtg.class, UnaryPlusPtg.class, UnaryPlusPtg.class); - // Spaces muck things up a bit. It would be clearer why the following cases are - // reasonable if POI encoded tAttrSpace in the right places. - // Otherwise these differences look capricious. - confirmUnary("+ 12", 12, IntPtg.class, UnaryPlusPtg.class); - confirmUnary("- 13", 13, IntPtg.class, UnaryMinusPtg.class); - } + // Spaces muck things up a bit. It would be clearer why the following cases are + // reasonable if POI encoded tAttrSpace in the right places. + // Otherwise these differences look capricious. + confirmUnary("+ 12", 12, IntPtg.class, UnaryPlusPtg.class); + confirmUnary("- 13", 13, IntPtg.class, UnaryMinusPtg.class); + } - private static void confirmUnary(String formulaText, double val, Class...expectedTokenTypes) { - Ptg[] ptgs = parseFormula(formulaText); - confirmTokenClasses(ptgs, expectedTokenTypes); - Ptg ptg0 = ptgs[0]; - if (ptg0 instanceof IntPtg) { - IntPtg intPtg = (IntPtg) ptg0; - assertEquals((int)val, intPtg.getValue()); - } else if (ptg0 instanceof NumberPtg) { - NumberPtg numberPtg = (NumberPtg) ptg0; - assertEquals(val, numberPtg.getValue(), 0.0); - } else { - fail("bad ptg0 " + ptg0); - } - } + private static void confirmUnary(String formulaText, double val, Class...expectedTokenTypes) { + Ptg[] ptgs = parseFormula(formulaText); + confirmTokenClasses(ptgs, expectedTokenTypes); + Ptg ptg0 = ptgs[0]; + if (ptg0 instanceof IntPtg) { + IntPtg intPtg = (IntPtg) ptg0; + assertEquals((int)val, intPtg.getValue()); + } else if (ptg0 instanceof NumberPtg) { + NumberPtg numberPtg = (NumberPtg) ptg0; + assertEquals(val, numberPtg.getValue(), 0.0); + } else { + fail("bad ptg0 " + ptg0); + } + } - @Test - public void testLeadingSpaceInString() { - String value = " hi "; - Ptg[] ptgs = parseFormula("\"" + value + "\""); - confirmTokenClasses(ptgs, StringPtg.class); - assertTrue("ptg0 contains exact value", ((StringPtg)ptgs[0]).getValue().equals(value)); - } + @Test + public void testLeadingSpaceInString() { + String value = " hi "; + Ptg[] ptgs = parseFormula("\"" + value + "\""); + confirmTokenClasses(ptgs, StringPtg.class); + assertTrue("ptg0 contains exact value", ((StringPtg)ptgs[0]).getValue().equals(value)); + } - @Test - public void testLookupAndMatchFunctionArgs() { - Ptg[] ptgs = parseFormula("lookup(A1, A3:A52, B3:B52)"); - confirmTokenClasses(ptgs, RefPtg.class, AreaPtg.class, AreaPtg.class, FuncVarPtg.class); - assertTrue("ptg0 has Value class", ptgs[0].getPtgClass() == Ptg.CLASS_VALUE); + @Test + public void testLookupAndMatchFunctionArgs() { + Ptg[] ptgs = parseFormula("lookup(A1, A3:A52, B3:B52)"); + confirmTokenClasses(ptgs, RefPtg.class, AreaPtg.class, AreaPtg.class, FuncVarPtg.class); + assertTrue("ptg0 has Value class", ptgs[0].getPtgClass() == Ptg.CLASS_VALUE); - ptgs = parseFormula("match(A1, A3:A52)"); - confirmTokenClasses(ptgs, RefPtg.class, AreaPtg.class, FuncVarPtg.class); - assertTrue("ptg0 has Value class", ptgs[0].getPtgClass() == Ptg.CLASS_VALUE); - } + ptgs = parseFormula("match(A1, A3:A52)"); + confirmTokenClasses(ptgs, RefPtg.class, AreaPtg.class, FuncVarPtg.class); + assertTrue("ptg0 has Value class", ptgs[0].getPtgClass() == Ptg.CLASS_VALUE); + } - /** bug 33160*/ - @Test - public void testLargeInt() { - confirmTokenClasses("40", IntPtg.class); - confirmTokenClasses("40000", IntPtg.class); - } + /** bug 33160*/ + @Test + public void testLargeInt() { + confirmTokenClasses("40", IntPtg.class); + confirmTokenClasses("40000", IntPtg.class); + } - /** bug 33160 */ - @Test - public void testSimpleLongFormula() { - confirmTokenClasses("40000/2", IntPtg.class, IntPtg.class, DividePtg.class); - } + /** bug 33160 */ + @Test + public void testSimpleLongFormula() { + confirmTokenClasses("40000/2", IntPtg.class, IntPtg.class, DividePtg.class); + } - /** bug 35027, underscore in sheet name */ - @Test - public void testUnderscore() throws IOException { - HSSFWorkbook wb = new HSSFWorkbook(); + /** bug 35027, underscore in sheet name */ + @Test + public void testUnderscore() throws IOException { + HSSFWorkbook wb = new HSSFWorkbook(); - wb.createSheet("Cash_Flow"); + wb.createSheet("Cash_Flow"); - HSSFSheet sheet = wb.createSheet("Test"); - HSSFRow row = sheet.createRow(0); - HSSFCell cell; + HSSFSheet sheet = wb.createSheet("Test"); + HSSFRow row = sheet.createRow(0); + HSSFCell cell; - cell = row.createCell(0); - cell.setCellFormula("Cash_Flow!A1"); - - wb.close(); - } + cell = row.createCell(0); + cell.setCellFormula("Cash_Flow!A1"); + + wb.close(); + } /** bug 49725, defined names with underscore */ - @Test + @Test public void testNamesWithUnderscore() throws IOException { HSSFWorkbook wb = new HSSFWorkbook(); //or new XSSFWorkbook(); HSSFSheet sheet = wb.createSheet("NamesWithUnderscore"); @@ -398,162 +398,162 @@ public final class TestFormulaParser { } // bug 38396 : Formula with exponential numbers not parsed correctly. - @Test - public void testExponentialParsing() { - confirmTokenClasses("1.3E21/2", NumberPtg.class, IntPtg.class, DividePtg.class); - confirmTokenClasses("1322E21/2", NumberPtg.class, IntPtg.class, DividePtg.class); - confirmTokenClasses("1.3E1/2", NumberPtg.class, IntPtg.class, DividePtg.class); - } + @Test + public void testExponentialParsing() { + confirmTokenClasses("1.3E21/2", NumberPtg.class, IntPtg.class, DividePtg.class); + confirmTokenClasses("1322E21/2", NumberPtg.class, IntPtg.class, DividePtg.class); + confirmTokenClasses("1.3E1/2", NumberPtg.class, IntPtg.class, DividePtg.class); + } - @Test - public void testExponentialInSheet() throws IOException { - HSSFWorkbook wb = new HSSFWorkbook(); + @Test + public void testExponentialInSheet() throws IOException { + HSSFWorkbook wb = new HSSFWorkbook(); - wb.createSheet("Cash_Flow"); + wb.createSheet("Cash_Flow"); - HSSFSheet sheet = wb.createSheet("Test"); - HSSFRow row = sheet.createRow(0); - HSSFCell cell = row.createCell(0); - String formula = null; + HSSFSheet sheet = wb.createSheet("Test"); + HSSFRow row = sheet.createRow(0); + HSSFCell cell = row.createCell(0); + String formula = null; - cell.setCellFormula("1.3E21/3"); - formula = cell.getCellFormula(); - assertEquals("Exponential formula string", "1.3E+21/3", formula); + cell.setCellFormula("1.3E21/3"); + formula = cell.getCellFormula(); + assertEquals("Exponential formula string", "1.3E+21/3", formula); - cell.setCellFormula("-1.3E21/3"); - formula = cell.getCellFormula(); - assertEquals("Exponential formula string", "-1.3E+21/3", formula); + cell.setCellFormula("-1.3E21/3"); + formula = cell.getCellFormula(); + assertEquals("Exponential formula string", "-1.3E+21/3", formula); - cell.setCellFormula("1322E21/3"); - formula = cell.getCellFormula(); - assertEquals("Exponential formula string", "1.322E+24/3", formula); + cell.setCellFormula("1322E21/3"); + formula = cell.getCellFormula(); + assertEquals("Exponential formula string", "1.322E+24/3", formula); - cell.setCellFormula("-1322E21/3"); - formula = cell.getCellFormula(); - assertEquals("Exponential formula string", "-1.322E+24/3", formula); + cell.setCellFormula("-1322E21/3"); + formula = cell.getCellFormula(); + assertEquals("Exponential formula string", "-1.322E+24/3", formula); - cell.setCellFormula("1.3E1/3"); - formula = cell.getCellFormula(); - assertEquals("Exponential formula string", "13/3", formula); + cell.setCellFormula("1.3E1/3"); + formula = cell.getCellFormula(); + assertEquals("Exponential formula string", "13/3", formula); - cell.setCellFormula("-1.3E1/3"); - formula = cell.getCellFormula(); - assertEquals("Exponential formula string", "-13/3", formula); + cell.setCellFormula("-1.3E1/3"); + formula = cell.getCellFormula(); + assertEquals("Exponential formula string", "-13/3", formula); - cell.setCellFormula("1.3E-4/3"); - formula = cell.getCellFormula(); - assertEquals("Exponential formula string", "0.00013/3", formula); + cell.setCellFormula("1.3E-4/3"); + formula = cell.getCellFormula(); + assertEquals("Exponential formula string", "0.00013/3", formula); - cell.setCellFormula("-1.3E-4/3"); - formula = cell.getCellFormula(); - assertEquals("Exponential formula string", "-0.00013/3", formula); + cell.setCellFormula("-1.3E-4/3"); + formula = cell.getCellFormula(); + assertEquals("Exponential formula string", "-0.00013/3", formula); - cell.setCellFormula("13E-15/3"); - formula = cell.getCellFormula(); - assertEquals("Exponential formula string", "0.000000000000013/3", formula); + cell.setCellFormula("13E-15/3"); + formula = cell.getCellFormula(); + assertEquals("Exponential formula string", "0.000000000000013/3", formula); - cell.setCellFormula("-13E-15/3"); - formula = cell.getCellFormula(); - assertEquals("Exponential formula string", "-0.000000000000013/3", formula); + cell.setCellFormula("-13E-15/3"); + formula = cell.getCellFormula(); + assertEquals("Exponential formula string", "-0.000000000000013/3", formula); - cell.setCellFormula("1.3E3/3"); - formula = cell.getCellFormula(); - assertEquals("Exponential formula string", "1300/3", formula); + cell.setCellFormula("1.3E3/3"); + formula = cell.getCellFormula(); + assertEquals("Exponential formula string", "1300/3", formula); - cell.setCellFormula("-1.3E3/3"); - formula = cell.getCellFormula(); - assertEquals("Exponential formula string", "-1300/3", formula); + cell.setCellFormula("-1.3E3/3"); + formula = cell.getCellFormula(); + assertEquals("Exponential formula string", "-1300/3", formula); - cell.setCellFormula("1300000000000000/3"); - formula = cell.getCellFormula(); - assertEquals("Exponential formula string", "1300000000000000/3", formula); + cell.setCellFormula("1300000000000000/3"); + formula = cell.getCellFormula(); + assertEquals("Exponential formula string", "1300000000000000/3", formula); - cell.setCellFormula("-1300000000000000/3"); - formula = cell.getCellFormula(); - assertEquals("Exponential formula string", "-1300000000000000/3", formula); + cell.setCellFormula("-1300000000000000/3"); + formula = cell.getCellFormula(); + assertEquals("Exponential formula string", "-1300000000000000/3", formula); - cell.setCellFormula("-10E-1/3.1E2*4E3/3E4"); - formula = cell.getCellFormula(); - assertEquals("Exponential formula string", "-1/310*4000/30000", formula); - - wb.close(); - } + cell.setCellFormula("-10E-1/3.1E2*4E3/3E4"); + formula = cell.getCellFormula(); + assertEquals("Exponential formula string", "-1/310*4000/30000", formula); + + wb.close(); + } - @Test - public void testNumbers() throws IOException { - HSSFWorkbook wb = new HSSFWorkbook(); + @Test + public void testNumbers() throws IOException { + HSSFWorkbook wb = new HSSFWorkbook(); - wb.createSheet("Cash_Flow"); + wb.createSheet("Cash_Flow"); - HSSFSheet sheet = wb.createSheet("Test"); - HSSFRow row = sheet.createRow(0); - HSSFCell cell = row.createCell(0); - String formula = null; + HSSFSheet sheet = wb.createSheet("Test"); + HSSFRow row = sheet.createRow(0); + HSSFCell cell = row.createCell(0); + String formula = null; - // starts from decimal point + // starts from decimal point - cell.setCellFormula(".1"); - formula = cell.getCellFormula(); - assertEquals("0.1", formula); + cell.setCellFormula(".1"); + formula = cell.getCellFormula(); + assertEquals("0.1", formula); - cell.setCellFormula("+.1"); - formula = cell.getCellFormula(); - assertEquals("0.1", formula); + cell.setCellFormula("+.1"); + formula = cell.getCellFormula(); + assertEquals("0.1", formula); - cell.setCellFormula("-.1"); - formula = cell.getCellFormula(); - assertEquals("-0.1", formula); + cell.setCellFormula("-.1"); + formula = cell.getCellFormula(); + assertEquals("-0.1", formula); - // has exponent + // has exponent - cell.setCellFormula("10E1"); - formula = cell.getCellFormula(); - assertEquals("100", formula); + cell.setCellFormula("10E1"); + formula = cell.getCellFormula(); + assertEquals("100", formula); - cell.setCellFormula("10E+1"); - formula = cell.getCellFormula(); - assertEquals("100", formula); + cell.setCellFormula("10E+1"); + formula = cell.getCellFormula(); + assertEquals("100", formula); - cell.setCellFormula("10E-1"); - formula = cell.getCellFormula(); - assertEquals("1", formula); - - wb.close(); - } + cell.setCellFormula("10E-1"); + formula = cell.getCellFormula(); + assertEquals("1", formula); + + wb.close(); + } - @Test - public void testRanges() throws IOException { - HSSFWorkbook wb = new HSSFWorkbook(); + @Test + public void testRanges() throws IOException { + HSSFWorkbook wb = new HSSFWorkbook(); - wb.createSheet("Cash_Flow"); + wb.createSheet("Cash_Flow"); - HSSFSheet sheet = wb.createSheet("Test"); - HSSFRow row = sheet.createRow(0); - HSSFCell cell = row.createCell(0); - String formula = null; + HSSFSheet sheet = wb.createSheet("Test"); + HSSFRow row = sheet.createRow(0); + HSSFCell cell = row.createCell(0); + String formula = null; - cell.setCellFormula("A1.A2"); - formula = cell.getCellFormula(); - assertEquals("A1:A2", formula); + cell.setCellFormula("A1.A2"); + formula = cell.getCellFormula(); + assertEquals("A1:A2", formula); - cell.setCellFormula("A1..A2"); - formula = cell.getCellFormula(); - assertEquals("A1:A2", formula); + cell.setCellFormula("A1..A2"); + formula = cell.getCellFormula(); + assertEquals("A1:A2", formula); - cell.setCellFormula("A1...A2"); - formula = cell.getCellFormula(); - assertEquals("A1:A2", formula); - - wb.close(); - } + cell.setCellFormula("A1...A2"); + formula = cell.getCellFormula(); + assertEquals("A1:A2", formula); + + wb.close(); + } - @Test - public void testMultiSheetReference() throws IOException { + @Test + public void testMultiSheetReference() throws IOException { HSSFWorkbook wb = new HSSFWorkbook(); wb.createSheet("Cash_Flow"); wb.createSheet("Test Sheet"); - + HSSFSheet sheet = wb.createSheet("Test"); HSSFRow row = sheet.createRow(0); HSSFCell cell = row.createCell(0); @@ -596,632 +596,632 @@ public final class TestFormulaParser { assertEquals("Cash_Flow:\'Test Sheet\'!A1:B2", formula); wb.close(); - } - - /** - * Test for bug observable at svn revision 618865 (5-Feb-2008)
- * a formula consisting of a single no-arg function got rendered without the function braces - */ - @Test - public void testToFormulaStringZeroArgFunction() throws IOException { - HSSFWorkbook book = new HSSFWorkbook(); - - Ptg[] ptgs = { - FuncPtg.create(10), - }; - assertEquals("NA()", HSSFFormulaParser.toFormulaString(book, ptgs)); - - book.close(); - } - - @Test - public void testPercent() { - - confirmTokenClasses("5%", IntPtg.class, PercentPtg.class); - // spaces OK - confirmTokenClasses(" 250 % ", IntPtg.class, PercentPtg.class); - // double percent OK - confirmTokenClasses("12345.678%%", NumberPtg.class, PercentPtg.class, PercentPtg.class); - - // percent of a bracketed expression - confirmTokenClasses("(A1+35)%*B1%", RefPtg.class, IntPtg.class, AddPtg.class, ParenthesisPtg.class, - PercentPtg.class, RefPtg.class, PercentPtg.class, MultiplyPtg.class); - - // percent of a text quantity - confirmTokenClasses("\"8.75\"%", StringPtg.class, PercentPtg.class); - - // percent to the power of - confirmTokenClasses("50%^3", IntPtg.class, PercentPtg.class, IntPtg.class, PowerPtg.class); - - // things that parse OK but would *evaluate* to an error - confirmTokenClasses("\"abc\"%", StringPtg.class, PercentPtg.class); - confirmTokenClasses("#N/A%", ErrPtg.class, PercentPtg.class); - } - - /** - * Tests combinations of various operators in the absence of brackets - */ - @Test - public void testPrecedenceAndAssociativity() { - - // TRUE=TRUE=2=2 evaluates to FALSE - confirmTokenClasses("TRUE=TRUE=2=2", BoolPtg.class, BoolPtg.class, EqualPtg.class, - IntPtg.class, EqualPtg.class, IntPtg.class, EqualPtg.class); - - // 2^3^2 evaluates to 64 not 512 - confirmTokenClasses("2^3^2", IntPtg.class, IntPtg.class, PowerPtg.class, - IntPtg.class, PowerPtg.class); - - // "abc" & 2 + 3 & "def" evaluates to "abc5def" - confirmTokenClasses("\"abc\"&2+3&\"def\"", StringPtg.class, IntPtg.class, IntPtg.class, - AddPtg.class, ConcatPtg.class, StringPtg.class, ConcatPtg.class); - - // (1 / 2) - (3 * 4) - confirmTokenClasses("1/2-3*4", IntPtg.class, IntPtg.class, DividePtg.class, - IntPtg.class, IntPtg.class, MultiplyPtg.class, SubtractPtg.class); - - // 2 * (2^2) - // NOT: (2 *2) ^ 2 -> int int multiply int power - confirmTokenClasses("2*2^2", IntPtg.class, IntPtg.class, IntPtg.class, PowerPtg.class, MultiplyPtg.class); - - // 2^200% -> 2 not 1.6E58 - confirmTokenClasses("2^200%", IntPtg.class, IntPtg.class, PercentPtg.class, PowerPtg.class); - } - - /* package */ static Ptg[] confirmTokenClasses(String formula, Class...expectedClasses) { - Ptg[] ptgs = parseFormula(formula); - confirmTokenClasses(ptgs, expectedClasses); - return ptgs; - } - - private static void confirmTokenClasses(Ptg[] ptgs, Class...expectedClasses) { - assertEquals(expectedClasses.length, ptgs.length); - for (int i = 0; i < expectedClasses.length; i++) { - if(expectedClasses[i] != ptgs[i].getClass()) { - fail("difference at token[" + i + "]: expected (" - + expectedClasses[i].getName() + ") but got (" - + ptgs[i].getClass().getName() + ")"); - } - } - } - - @Test - public void testPower() { - confirmTokenClasses("2^5", IntPtg.class, IntPtg.class, PowerPtg.class); - } - - private static Ptg parseSingleToken(String formula, Class ptgClass) { - Ptg[] ptgs = parseFormula(formula); - assertEquals(1, ptgs.length); - Ptg result = ptgs[0]; - assertEquals(ptgClass, result.getClass()); - return result; - } - - @Test - public void testParseNumber() { - IntPtg ip; - - // bug 33160 - ip = (IntPtg) parseSingleToken("40", IntPtg.class); - assertEquals(40, ip.getValue()); - ip = (IntPtg) parseSingleToken("40000", IntPtg.class); - assertEquals(40000, ip.getValue()); - - // check the upper edge of the IntPtg range: - ip = (IntPtg) parseSingleToken("65535", IntPtg.class); - assertEquals(65535, ip.getValue()); - NumberPtg np = (NumberPtg) parseSingleToken("65536", NumberPtg.class); - assertEquals(65536, np.getValue(), 0); - - np = (NumberPtg) parseSingleToken("65534.6", NumberPtg.class); - assertEquals(65534.6, np.getValue(), 0); - } - - public void testMissingArgs() { - - confirmTokenClasses("if(A1, ,C1)", - RefPtg.class, - AttrPtg.class, // tAttrIf - MissingArgPtg.class, - AttrPtg.class, // tAttrSkip - RefPtg.class, - AttrPtg.class, // tAttrSkip - FuncVarPtg.class - ); - - confirmTokenClasses("counta( , A1:B2, )", MissingArgPtg.class, AreaPtg.class, MissingArgPtg.class, - FuncVarPtg.class); - } - - @Test - public void testParseErrorLiterals() { - - confirmParseErrorLiteral(ErrPtg.NULL_INTERSECTION, "#NULL!"); - confirmParseErrorLiteral(ErrPtg.DIV_ZERO, "#DIV/0!"); - confirmParseErrorLiteral(ErrPtg.VALUE_INVALID, "#VALUE!"); - confirmParseErrorLiteral(ErrPtg.REF_INVALID, "#REF!"); - confirmParseErrorLiteral(ErrPtg.NAME_INVALID, "#NAME?"); - confirmParseErrorLiteral(ErrPtg.NUM_ERROR, "#NUM!"); - confirmParseErrorLiteral(ErrPtg.N_A, "#N/A"); - parseFormula("HLOOKUP(F7,#REF!,G7,#REF!)"); - } - - private static void confirmParseErrorLiteral(ErrPtg expectedToken, String formula) { - assertEquals(expectedToken, parseSingleToken(formula, ErrPtg.class)); - } - - /** - * To aid readability the parameters have been encoded with single quotes instead of double - * quotes. This method converts single quotes to double quotes before performing the parse - * and result check. - */ - private static void confirmStringParse(String singleQuotedValue) { - // formula: internal quotes become double double, surround with double quotes - String formula = '"' + singleQuotedValue.replaceAll("'", "\"\"") + '"'; - String expectedValue = singleQuotedValue.replace('\'', '"'); - - StringPtg sp = (StringPtg) parseSingleToken(formula, StringPtg.class); - assertEquals(expectedValue, sp.getValue()); - } - - @Test - public void testParseStringLiterals_bug28754() throws IOException { - - StringPtg sp; - try { - sp = (StringPtg) parseSingleToken("\"test\"\"ing\"", StringPtg.class); - } catch (RuntimeException e) { - if(e.getMessage().startsWith("Cannot Parse")) { - fail("Identified bug 28754a"); - } - throw e; - } - assertEquals("test\"ing", sp.getValue()); - - HSSFWorkbook wb = new HSSFWorkbook(); - try { - HSSFSheet sheet = wb.createSheet(); - wb.setSheetName(0, "Sheet1"); + } - HSSFRow row = sheet.createRow(0); - HSSFCell cell = row.createCell(0); - cell.setCellFormula("right(\"test\"\"ing\", 3)"); - String actualCellFormula = cell.getCellFormula(); - if("RIGHT(\"test\"ing\",3)".equals(actualCellFormula)) { - fail("Identified bug 28754b"); - } - assertEquals("RIGHT(\"test\"\"ing\",3)", actualCellFormula); - } finally { - wb.close(); - } - } + /** + * Test for bug observable at svn revision 618865 (5-Feb-2008)
+ * a formula consisting of a single no-arg function got rendered without the function braces + */ + @Test + public void testToFormulaStringZeroArgFunction() throws IOException { + HSSFWorkbook book = new HSSFWorkbook(); - @Test - public void testParseStringLiterals() { - confirmStringParse("goto considered harmful"); + Ptg[] ptgs = { + FuncPtg.create(10), + }; + assertEquals("NA()", HSSFFormulaParser.toFormulaString(book, ptgs)); + + book.close(); + } - confirmStringParse("goto 'considered' harmful"); + @Test + public void testPercent() { - confirmStringParse(""); - confirmStringParse("'"); - confirmStringParse("''"); - confirmStringParse("' '"); - confirmStringParse(" ' "); - } + confirmTokenClasses("5%", IntPtg.class, PercentPtg.class); + // spaces OK + confirmTokenClasses(" 250 % ", IntPtg.class, PercentPtg.class); + // double percent OK + confirmTokenClasses("12345.678%%", NumberPtg.class, PercentPtg.class, PercentPtg.class); - @Test - public void testParseSumIfSum() { - String formulaString; - Ptg[] ptgs; - ptgs = parseFormula("sum(5, 2, if(3>2, sum(A1:A2), 6))"); - formulaString = toFormulaString(ptgs); - assertEquals("SUM(5,2,IF(3>2,SUM(A1:A2),6))", formulaString); + // percent of a bracketed expression + confirmTokenClasses("(A1+35)%*B1%", RefPtg.class, IntPtg.class, AddPtg.class, ParenthesisPtg.class, + PercentPtg.class, RefPtg.class, PercentPtg.class, MultiplyPtg.class); - ptgs = parseFormula("if(1<2,sum(5, 2, if(3>2, sum(A1:A2), 6)),4)"); - formulaString = toFormulaString(ptgs); - assertEquals("IF(1<2,SUM(5,2,IF(3>2,SUM(A1:A2),6)),4)", formulaString); - } - - @Test - public void testParserErrors() { - parseExpectedException(" 12 . 345 "); - parseExpectedException("1 .23 "); + // percent of a text quantity + confirmTokenClasses("\"8.75\"%", StringPtg.class, PercentPtg.class); - parseExpectedException("sum(#NAME)"); - parseExpectedException("1 + #N / A * 2"); - parseExpectedException("#value?"); - parseExpectedException("#DIV/ 0+2"); + // percent to the power of + confirmTokenClasses("50%^3", IntPtg.class, PercentPtg.class, IntPtg.class, PowerPtg.class); + + // things that parse OK but would *evaluate* to an error + confirmTokenClasses("\"abc\"%", StringPtg.class, PercentPtg.class); + confirmTokenClasses("#N/A%", ErrPtg.class, PercentPtg.class); + } + + /** + * Tests combinations of various operators in the absence of brackets + */ + @Test + public void testPrecedenceAndAssociativity() { + + // TRUE=TRUE=2=2 evaluates to FALSE + confirmTokenClasses("TRUE=TRUE=2=2", BoolPtg.class, BoolPtg.class, EqualPtg.class, + IntPtg.class, EqualPtg.class, IntPtg.class, EqualPtg.class); + + // 2^3^2 evaluates to 64 not 512 + confirmTokenClasses("2^3^2", IntPtg.class, IntPtg.class, PowerPtg.class, + IntPtg.class, PowerPtg.class); + + // "abc" & 2 + 3 & "def" evaluates to "abc5def" + confirmTokenClasses("\"abc\"&2+3&\"def\"", StringPtg.class, IntPtg.class, IntPtg.class, + AddPtg.class, ConcatPtg.class, StringPtg.class, ConcatPtg.class); + + // (1 / 2) - (3 * 4) + confirmTokenClasses("1/2-3*4", IntPtg.class, IntPtg.class, DividePtg.class, + IntPtg.class, IntPtg.class, MultiplyPtg.class, SubtractPtg.class); + + // 2 * (2^2) + // NOT: (2 *2) ^ 2 -> int int multiply int power + confirmTokenClasses("2*2^2", IntPtg.class, IntPtg.class, IntPtg.class, PowerPtg.class, MultiplyPtg.class); + + // 2^200% -> 2 not 1.6E58 + confirmTokenClasses("2^200%", IntPtg.class, IntPtg.class, PercentPtg.class, PowerPtg.class); + } + + /* package */ static Ptg[] confirmTokenClasses(String formula, Class...expectedClasses) { + Ptg[] ptgs = parseFormula(formula); + confirmTokenClasses(ptgs, expectedClasses); + return ptgs; + } + + private static void confirmTokenClasses(Ptg[] ptgs, Class...expectedClasses) { + assertEquals(expectedClasses.length, ptgs.length); + for (int i = 0; i < expectedClasses.length; i++) { + if(expectedClasses[i] != ptgs[i].getClass()) { + fail("difference at token[" + i + "]: expected (" + + expectedClasses[i].getName() + ") but got (" + + ptgs[i].getClass().getName() + ")"); + } + } + } + + @Test + public void testPower() { + confirmTokenClasses("2^5", IntPtg.class, IntPtg.class, PowerPtg.class); + } + + private static Ptg parseSingleToken(String formula, Class ptgClass) { + Ptg[] ptgs = parseFormula(formula); + assertEquals(1, ptgs.length); + Ptg result = ptgs[0]; + assertEquals(ptgClass, result.getClass()); + return result; + } + + @Test + public void testParseNumber() { + IntPtg ip; + + // bug 33160 + ip = (IntPtg) parseSingleToken("40", IntPtg.class); + assertEquals(40, ip.getValue()); + ip = (IntPtg) parseSingleToken("40000", IntPtg.class); + assertEquals(40000, ip.getValue()); + + // check the upper edge of the IntPtg range: + ip = (IntPtg) parseSingleToken("65535", IntPtg.class); + assertEquals(65535, ip.getValue()); + NumberPtg np = (NumberPtg) parseSingleToken("65536", NumberPtg.class); + assertEquals(65536, np.getValue(), 0); + + np = (NumberPtg) parseSingleToken("65534.6", NumberPtg.class); + assertEquals(65534.6, np.getValue(), 0); + } + + public void testMissingArgs() { + + confirmTokenClasses("if(A1, ,C1)", + RefPtg.class, + AttrPtg.class, // tAttrIf + MissingArgPtg.class, + AttrPtg.class, // tAttrSkip + RefPtg.class, + AttrPtg.class, // tAttrSkip + FuncVarPtg.class + ); + + confirmTokenClasses("counta( , A1:B2, )", MissingArgPtg.class, AreaPtg.class, MissingArgPtg.class, + FuncVarPtg.class); + } + + @Test + public void testParseErrorLiterals() { + + confirmParseErrorLiteral(ErrPtg.NULL_INTERSECTION, "#NULL!"); + confirmParseErrorLiteral(ErrPtg.DIV_ZERO, "#DIV/0!"); + confirmParseErrorLiteral(ErrPtg.VALUE_INVALID, "#VALUE!"); + confirmParseErrorLiteral(ErrPtg.REF_INVALID, "#REF!"); + confirmParseErrorLiteral(ErrPtg.NAME_INVALID, "#NAME?"); + confirmParseErrorLiteral(ErrPtg.NUM_ERROR, "#NUM!"); + confirmParseErrorLiteral(ErrPtg.N_A, "#N/A"); + parseFormula("HLOOKUP(F7,#REF!,G7,#REF!)"); + } + + private static void confirmParseErrorLiteral(ErrPtg expectedToken, String formula) { + assertEquals(expectedToken, parseSingleToken(formula, ErrPtg.class)); + } + + /** + * To aid readability the parameters have been encoded with single quotes instead of double + * quotes. This method converts single quotes to double quotes before performing the parse + * and result check. + */ + private static void confirmStringParse(String singleQuotedValue) { + // formula: internal quotes become double double, surround with double quotes + String formula = '"' + singleQuotedValue.replaceAll("'", "\"\"") + '"'; + String expectedValue = singleQuotedValue.replace('\'', '"'); + + StringPtg sp = (StringPtg) parseSingleToken(formula, StringPtg.class); + assertEquals(expectedValue, sp.getValue()); + } + + @Test + public void testParseStringLiterals_bug28754() throws IOException { + + StringPtg sp; + try { + sp = (StringPtg) parseSingleToken("\"test\"\"ing\"", StringPtg.class); + } catch (RuntimeException e) { + if(e.getMessage().startsWith("Cannot Parse")) { + fail("Identified bug 28754a"); + } + throw e; + } + assertEquals("test\"ing", sp.getValue()); + + HSSFWorkbook wb = new HSSFWorkbook(); + try { + HSSFSheet sheet = wb.createSheet(); + wb.setSheetName(0, "Sheet1"); + + HSSFRow row = sheet.createRow(0); + HSSFCell cell = row.createCell(0); + cell.setCellFormula("right(\"test\"\"ing\", 3)"); + String actualCellFormula = cell.getCellFormula(); + if("RIGHT(\"test\"ing\",3)".equals(actualCellFormula)) { + fail("Identified bug 28754b"); + } + assertEquals("RIGHT(\"test\"\"ing\",3)", actualCellFormula); + } finally { + wb.close(); + } + } + + @Test + public void testParseStringLiterals() { + confirmStringParse("goto considered harmful"); + + confirmStringParse("goto 'considered' harmful"); + + confirmStringParse(""); + confirmStringParse("'"); + confirmStringParse("''"); + confirmStringParse("' '"); + confirmStringParse(" ' "); + } + + @Test + public void testParseSumIfSum() { + String formulaString; + Ptg[] ptgs; + ptgs = parseFormula("sum(5, 2, if(3>2, sum(A1:A2), 6))"); + formulaString = toFormulaString(ptgs); + assertEquals("SUM(5,2,IF(3>2,SUM(A1:A2),6))", formulaString); + + ptgs = parseFormula("if(1<2,sum(5, 2, if(3>2, sum(A1:A2), 6)),4)"); + formulaString = toFormulaString(ptgs); + assertEquals("IF(1<2,SUM(5,2,IF(3>2,SUM(A1:A2),6)),4)", formulaString); + } + + @Test + public void testParserErrors() { + parseExpectedException(" 12 . 345 "); + parseExpectedException("1 .23 "); + + parseExpectedException("sum(#NAME)"); + parseExpectedException("1 + #N / A * 2"); + parseExpectedException("#value?"); + parseExpectedException("#DIV/ 0+2"); - parseExpectedException("IF(TRUE)"); - parseExpectedException("countif(A1:B5, C1, D1)"); - - parseExpectedException("("); - parseExpectedException(")"); - parseExpectedException("+"); - parseExpectedException("42+"); - - parseExpectedException("IF("); - } + parseExpectedException("IF(TRUE)"); + parseExpectedException("countif(A1:B5, C1, D1)"); + + parseExpectedException("("); + parseExpectedException(")"); + parseExpectedException("+"); + parseExpectedException("42+"); + + parseExpectedException("IF("); + } - private static void parseExpectedException(String formula) { - try { - parseFormula(formula); - fail("Expected FormulaParseException: " + formula); - } catch (FormulaParseException e) { - // expected during successful test - assertNotNull(e.getMessage()); - } - } + private static void parseExpectedException(String formula) { + try { + parseFormula(formula); + fail("Expected FormulaParseException: " + formula); + } catch (FormulaParseException e) { + // expected during successful test + assertNotNull(e.getMessage()); + } + } - @Test - public void testSetFormulaWithRowBeyond32768_Bug44539() throws IOException { + @Test + public void testSetFormulaWithRowBeyond32768_Bug44539() throws IOException { - HSSFWorkbook wb = new HSSFWorkbook(); - HSSFSheet sheet = wb.createSheet(); - wb.setSheetName(0, "Sheet1"); + HSSFWorkbook wb = new HSSFWorkbook(); + HSSFSheet sheet = wb.createSheet(); + wb.setSheetName(0, "Sheet1"); - HSSFRow row = sheet.createRow(0); - HSSFCell cell = row.createCell(0); - cell.setCellFormula("SUM(A32769:A32770)"); - if("SUM(A-32767:A-32766)".equals(cell.getCellFormula())) { - fail("Identified bug 44539"); - } - assertEquals("SUM(A32769:A32770)", cell.getCellFormula()); - - wb.close(); - } + HSSFRow row = sheet.createRow(0); + HSSFCell cell = row.createCell(0); + cell.setCellFormula("SUM(A32769:A32770)"); + if("SUM(A-32767:A-32766)".equals(cell.getCellFormula())) { + fail("Identified bug 44539"); + } + assertEquals("SUM(A32769:A32770)", cell.getCellFormula()); + + wb.close(); + } - @Test - public void testSpaceAtStartOfFormula() { - // Simulating cell formula of "= 4" (note space) - // The same Ptg array can be observed if an excel file is saved with that exact formula + @Test + public void testSpaceAtStartOfFormula() { + // Simulating cell formula of "= 4" (note space) + // The same Ptg array can be observed if an excel file is saved with that exact formula - AttrPtg spacePtg = AttrPtg.createSpace(AttrPtg.SpaceType.SPACE_BEFORE, 1); - Ptg[] ptgs = { spacePtg, new IntPtg(4), }; - String formulaString; - try { - formulaString = toFormulaString(ptgs); - } catch (IllegalStateException e) { - if(e.getMessage().equalsIgnoreCase("too much stuff left on the stack")) { - fail("Identified bug 44609"); - } - // else some unexpected error - throw e; - } - // FormulaParser strips spaces anyway - assertEquals("4", formulaString); + AttrPtg spacePtg = AttrPtg.createSpace(AttrPtg.SpaceType.SPACE_BEFORE, 1); + Ptg[] ptgs = { spacePtg, new IntPtg(4), }; + String formulaString; + try { + formulaString = toFormulaString(ptgs); + } catch (IllegalStateException e) { + if(e.getMessage().equalsIgnoreCase("too much stuff left on the stack")) { + fail("Identified bug 44609"); + } + // else some unexpected error + throw e; + } + // FormulaParser strips spaces anyway + assertEquals("4", formulaString); - ptgs = new Ptg[] { new IntPtg(3), spacePtg, new IntPtg(4), spacePtg, AddPtg.instance, }; - formulaString = toFormulaString(ptgs); - assertEquals("3+4", formulaString); - } + ptgs = new Ptg[] { new IntPtg(3), spacePtg, new IntPtg(4), spacePtg, AddPtg.instance, }; + formulaString = toFormulaString(ptgs); + assertEquals("3+4", formulaString); + } - /** - * Checks some internal error detecting logic ('stack underflow error' in toFormulaString) - */ - @Test - public void testTooFewOperandArgs() { - // Simulating badly encoded cell formula of "=/1" - // Not sure if Excel could ever produce this - Ptg[] ptgs = { - // Excel would probably have put tMissArg here - new IntPtg(1), - DividePtg.instance, - }; - try { - toFormulaString(ptgs); - fail("Expected exception was not thrown"); - } catch (IllegalStateException e) { - // expected during successful test - assertTrue(e.getMessage().startsWith("Too few arguments supplied to operation")); - } - } - /** - * Make sure that POI uses the right Func Ptg when encoding formulas. Functions with variable - * number of args should get FuncVarPtg, functions with fixed args should get FuncPtg.

- * - * Prior to the fix for bug 44675 POI would encode FuncVarPtg for all functions. In many cases - * Excel tolerates the wrong Ptg and evaluates the formula OK (e.g. SIN), but in some cases - * (e.g. COUNTIF) Excel fails to evaluate the formula, giving '#VALUE!' instead. - */ - @Test - public void testFuncPtgSelection() { + /** + * Checks some internal error detecting logic ('stack underflow error' in toFormulaString) + */ + @Test + public void testTooFewOperandArgs() { + // Simulating badly encoded cell formula of "=/1" + // Not sure if Excel could ever produce this + Ptg[] ptgs = { + // Excel would probably have put tMissArg here + new IntPtg(1), + DividePtg.instance, + }; + try { + toFormulaString(ptgs); + fail("Expected exception was not thrown"); + } catch (IllegalStateException e) { + // expected during successful test + assertTrue(e.getMessage().startsWith("Too few arguments supplied to operation")); + } + } + /** + * Make sure that POI uses the right Func Ptg when encoding formulas. Functions with variable + * number of args should get FuncVarPtg, functions with fixed args should get FuncPtg.

+ * + * Prior to the fix for bug 44675 POI would encode FuncVarPtg for all functions. In many cases + * Excel tolerates the wrong Ptg and evaluates the formula OK (e.g. SIN), but in some cases + * (e.g. COUNTIF) Excel fails to evaluate the formula, giving '#VALUE!' instead. + */ + @Test + public void testFuncPtgSelection() { - Ptg[] ptgs = parseFormula("countif(A1:A2, 1)"); - assertEquals(3, ptgs.length); - if(ptgs[2] instanceof FuncVarPtg) { - fail("Identified bug 44675"); - } - confirmTokenClasses(ptgs, AreaPtg.class, IntPtg.class, FuncPtg.class); + Ptg[] ptgs = parseFormula("countif(A1:A2, 1)"); + assertEquals(3, ptgs.length); + if(ptgs[2] instanceof FuncVarPtg) { + fail("Identified bug 44675"); + } + confirmTokenClasses(ptgs, AreaPtg.class, IntPtg.class, FuncPtg.class); - confirmTokenClasses("sin(1)", IntPtg.class, FuncPtg.class); - } + confirmTokenClasses("sin(1)", IntPtg.class, FuncPtg.class); + } - @Test - public void testWrongNumberOfFunctionArgs() throws IOException { - confirmArgCountMsg("sin()", "Too few arguments to function 'SIN'. Expected 1 but got 0."); - confirmArgCountMsg("countif(1, 2, 3, 4)", "Too many arguments to function 'COUNTIF'. Expected 2 but got 4."); - confirmArgCountMsg("index(1, 2, 3, 4, 5, 6)", "Too many arguments to function 'INDEX'. At most 4 were expected but got 6."); - confirmArgCountMsg("vlookup(1, 2)", "Too few arguments to function 'VLOOKUP'. At least 3 were expected but got 2."); - } + @Test + public void testWrongNumberOfFunctionArgs() throws IOException { + confirmArgCountMsg("sin()", "Too few arguments to function 'SIN'. Expected 1 but got 0."); + confirmArgCountMsg("countif(1, 2, 3, 4)", "Too many arguments to function 'COUNTIF'. Expected 2 but got 4."); + confirmArgCountMsg("index(1, 2, 3, 4, 5, 6)", "Too many arguments to function 'INDEX'. At most 4 were expected but got 6."); + confirmArgCountMsg("vlookup(1, 2)", "Too few arguments to function 'VLOOKUP'. At least 3 were expected but got 2."); + } - private static void confirmArgCountMsg(String formula, String expectedMessage) throws IOException { - HSSFWorkbook book = new HSSFWorkbook(); - try { - HSSFFormulaParser.parse(formula, book); - fail("Didn't get parse exception as expected"); - } catch (FormulaParseException e) { - confirmParseException(e, expectedMessage); - } - book.close(); - } + private static void confirmArgCountMsg(String formula, String expectedMessage) throws IOException { + HSSFWorkbook book = new HSSFWorkbook(); + try { + HSSFFormulaParser.parse(formula, book); + fail("Didn't get parse exception as expected"); + } catch (FormulaParseException e) { + confirmParseException(e, expectedMessage); + } + book.close(); + } - @Test - public void testParseErrorExpectedMsg() { + @Test + public void testParseErrorExpectedMsg() { - try { - parseFormula("round(3.14;2)"); - fail("Didn't get parse exception as expected"); - } catch (FormulaParseException e) { - confirmParseException(e, - "Parse error near char 10 ';' in specified formula 'round(3.14;2)'. Expected ',' or ')'"); - } + try { + parseFormula("round(3.14;2)"); + fail("Didn't get parse exception as expected"); + } catch (FormulaParseException e) { + confirmParseException(e, + "Parse error near char 10 ';' in specified formula 'round(3.14;2)'. Expected ',' or ')'"); + } - try { - parseFormula(" =2+2"); - fail("Didn't get parse exception as expected"); - } catch (FormulaParseException e) { - confirmParseException(e, - "The specified formula ' =2+2' starts with an equals sign which is not allowed."); - } - } + try { + parseFormula(" =2+2"); + fail("Didn't get parse exception as expected"); + } catch (FormulaParseException e) { + confirmParseException(e, + "The specified formula ' =2+2' starts with an equals sign which is not allowed."); + } + } - /** - * this function name has a dot in it. - */ - @Test - public void testParseErrorTypeFunction() { + /** + * this function name has a dot in it. + */ + @Test + public void testParseErrorTypeFunction() { - Ptg[] ptgs; - try { - ptgs = parseFormula("error.type(A1)"); - } catch (IllegalArgumentException e) { - if (e.getMessage().equals("Invalid Formula cell reference: 'error'")) { - fail("Identified bug 45334"); - } - throw e; - } - confirmTokenClasses(ptgs, RefPtg.class, FuncPtg.class); - assertEquals("ERROR.TYPE", ((FuncPtg) ptgs[1]).getName()); - } + Ptg[] ptgs; + try { + ptgs = parseFormula("error.type(A1)"); + } catch (IllegalArgumentException e) { + if (e.getMessage().equals("Invalid Formula cell reference: 'error'")) { + fail("Identified bug 45334"); + } + throw e; + } + confirmTokenClasses(ptgs, RefPtg.class, FuncPtg.class); + assertEquals("ERROR.TYPE", ((FuncPtg) ptgs[1]).getName()); + } - @Test - public void testNamedRangeThatLooksLikeCell() throws IOException { - HSSFWorkbook wb = new HSSFWorkbook(); - HSSFSheet sheet = wb.createSheet("Sheet1"); - HSSFName name = wb.createName(); - name.setRefersToFormula("Sheet1!B1"); - name.setNameName("pfy1"); + @Test + public void testNamedRangeThatLooksLikeCell() throws IOException { + HSSFWorkbook wb = new HSSFWorkbook(); + HSSFSheet sheet = wb.createSheet("Sheet1"); + HSSFName name = wb.createName(); + name.setRefersToFormula("Sheet1!B1"); + name.setNameName("pfy1"); - Ptg[] ptgs; - try { - ptgs = HSSFFormulaParser.parse("count(pfy1)", wb); - } catch (IllegalArgumentException e) { - if (e.getMessage().equals("Specified colIx (1012) is out of range")) { - fail("Identified bug 45354"); - } - wb.close(); - throw e; - } - confirmTokenClasses(ptgs, NamePtg.class, FuncVarPtg.class); + Ptg[] ptgs; + try { + ptgs = HSSFFormulaParser.parse("count(pfy1)", wb); + } catch (IllegalArgumentException e) { + if (e.getMessage().equals("Specified colIx (1012) is out of range")) { + fail("Identified bug 45354"); + } + wb.close(); + throw e; + } + confirmTokenClasses(ptgs, NamePtg.class, FuncVarPtg.class); - HSSFCell cell = sheet.createRow(0).createCell(0); - cell.setCellFormula("count(pfy1)"); - assertEquals("COUNT(pfy1)", cell.getCellFormula()); - try { - cell.setCellFormula("count(pf1)"); - fail("Expected formula parse execption"); - } catch (FormulaParseException e) { - confirmParseException(e, - "Specified named range 'pf1' does not exist in the current workbook."); - } - cell.setCellFormula("count(fp1)"); // plain cell ref, col is in range - wb.close(); - } + HSSFCell cell = sheet.createRow(0).createCell(0); + cell.setCellFormula("count(pfy1)"); + assertEquals("COUNT(pfy1)", cell.getCellFormula()); + try { + cell.setCellFormula("count(pf1)"); + fail("Expected formula parse execption"); + } catch (FormulaParseException e) { + confirmParseException(e, + "Specified named range 'pf1' does not exist in the current workbook."); + } + cell.setCellFormula("count(fp1)"); // plain cell ref, col is in range + wb.close(); + } - @Test - public void testParseAreaRefHighRow_bug45358() throws IOException { - Ptg[] ptgs; - AreaI aptg; + @Test + public void testParseAreaRefHighRow_bug45358() throws IOException { + Ptg[] ptgs; + AreaI aptg; - HSSFWorkbook book = new HSSFWorkbook(); - book.createSheet("Sheet1"); + HSSFWorkbook book = new HSSFWorkbook(); + book.createSheet("Sheet1"); - ptgs = HSSFFormulaParser.parse("Sheet1!A10:A40000", book); - aptg = (AreaI) ptgs[0]; - if (aptg.getLastRow() == -25537) { - fail("Identified bug 45358"); - } - assertEquals(39999, aptg.getLastRow()); + ptgs = HSSFFormulaParser.parse("Sheet1!A10:A40000", book); + aptg = (AreaI) ptgs[0]; + if (aptg.getLastRow() == -25537) { + fail("Identified bug 45358"); + } + assertEquals(39999, aptg.getLastRow()); - ptgs = HSSFFormulaParser.parse("Sheet1!A10:A65536", book); - aptg = (AreaI) ptgs[0]; - assertEquals(65535, aptg.getLastRow()); + ptgs = HSSFFormulaParser.parse("Sheet1!A10:A65536", book); + aptg = (AreaI) ptgs[0]; + assertEquals(65535, aptg.getLastRow()); - // plain area refs should be ok too - ptgs = parseFormula("A10:A65536"); - aptg = (AreaI) ptgs[0]; - assertEquals(65535, aptg.getLastRow()); + // plain area refs should be ok too + ptgs = parseFormula("A10:A65536"); + aptg = (AreaI) ptgs[0]; + assertEquals(65535, aptg.getLastRow()); - book.close(); - } - - @Test - public void testParseArray() { - Ptg[] ptgs; - ptgs = parseFormula("mode({1,2,2,#REF!;FALSE,3,3,2})"); - confirmTokenClasses(ptgs, ArrayPtg.class, FuncVarPtg.class); - assertEquals("{1,2,2,#REF!;FALSE,3,3,2}", ptgs[0].toFormulaString()); + book.close(); + } + + @Test + public void testParseArray() { + Ptg[] ptgs; + ptgs = parseFormula("mode({1,2,2,#REF!;FALSE,3,3,2})"); + confirmTokenClasses(ptgs, ArrayPtg.class, FuncVarPtg.class); + assertEquals("{1,2,2,#REF!;FALSE,3,3,2}", ptgs[0].toFormulaString()); - ArrayPtg aptg = (ArrayPtg) ptgs[0]; - Object[][] values = aptg.getTokenArrayValues(); - assertEquals(ErrorConstant.valueOf(FormulaError.REF.getCode()), values[0][3]); - assertEquals(Boolean.FALSE, values[1][0]); - } + ArrayPtg aptg = (ArrayPtg) ptgs[0]; + Object[][] values = aptg.getTokenArrayValues(); + assertEquals(ErrorConstant.valueOf(FormulaError.REF.getCode()), values[0][3]); + assertEquals(Boolean.FALSE, values[1][0]); + } - @Test - public void testParseStringElementInArray() { - Ptg[] ptgs; - ptgs = parseFormula("MAX({\"5\"},3)"); - confirmTokenClasses(ptgs, ArrayPtg.class, IntPtg.class, FuncVarPtg.class); - Object element = ((ArrayPtg)ptgs[0]).getTokenArrayValues()[0][0]; - if (element instanceof UnicodeString) { - // this would cause ClassCastException below - fail("Wrong encoding of array element value"); - } - assertEquals(String.class, element.getClass()); + @Test + public void testParseStringElementInArray() { + Ptg[] ptgs; + ptgs = parseFormula("MAX({\"5\"},3)"); + confirmTokenClasses(ptgs, ArrayPtg.class, IntPtg.class, FuncVarPtg.class); + Object element = ((ArrayPtg)ptgs[0]).getTokenArrayValues()[0][0]; + if (element instanceof UnicodeString) { + // this would cause ClassCastException below + fail("Wrong encoding of array element value"); + } + assertEquals(String.class, element.getClass()); - // make sure the formula encodes OK - int encSize = Ptg.getEncodedSize(ptgs); - byte[] data = new byte[encSize]; - Ptg.serializePtgs(ptgs, data, 0); - byte[] expData = HexRead.readFromString( - "20 00 00 00 00 00 00 00 " // tArray - + "1E 03 00 " // tInt(3) - + "42 02 07 00 " // tFuncVar(MAX) 2-arg - + "00 00 00 " // Array data: 1 col, 1 row - + "02 01 00 00 35" // elem (type=string, len=1, "5") - ); - assertArrayEquals(expData, data); - int initSize = Ptg.getEncodedSizeWithoutArrayData(ptgs); - Ptg[] ptgs2 = Ptg.readTokens(initSize, new LittleEndianByteArrayInputStream(data)); - confirmTokenClasses(ptgs2, ArrayPtg.class, IntPtg.class, FuncVarPtg.class); - } + // make sure the formula encodes OK + int encSize = Ptg.getEncodedSize(ptgs); + byte[] data = new byte[encSize]; + Ptg.serializePtgs(ptgs, data, 0); + byte[] expData = HexRead.readFromString( + "20 00 00 00 00 00 00 00 " // tArray + + "1E 03 00 " // tInt(3) + + "42 02 07 00 " // tFuncVar(MAX) 2-arg + + "00 00 00 " // Array data: 1 col, 1 row + + "02 01 00 00 35" // elem (type=string, len=1, "5") + ); + assertArrayEquals(expData, data); + int initSize = Ptg.getEncodedSizeWithoutArrayData(ptgs); + Ptg[] ptgs2 = Ptg.readTokens(initSize, new LittleEndianByteArrayInputStream(data)); + confirmTokenClasses(ptgs2, ArrayPtg.class, IntPtg.class, FuncVarPtg.class); + } - @Test - public void testParseArrayNegativeElement() { - Ptg[] ptgs; - try { - ptgs = parseFormula("{-42}"); - } catch (FormulaParseException e) { - if (e.getMessage().equals("Parse error near char 1 '-' in specified formula '{-42}'. Expected Integer")) { - fail("Identified bug - failed to parse negative array element."); - } - throw e; - } - confirmTokenClasses(ptgs, ArrayPtg.class); - Object element = ((ArrayPtg)ptgs[0]).getTokenArrayValues()[0][0]; + @Test + public void testParseArrayNegativeElement() { + Ptg[] ptgs; + try { + ptgs = parseFormula("{-42}"); + } catch (FormulaParseException e) { + if (e.getMessage().equals("Parse error near char 1 '-' in specified formula '{-42}'. Expected Integer")) { + fail("Identified bug - failed to parse negative array element."); + } + throw e; + } + confirmTokenClasses(ptgs, ArrayPtg.class); + Object element = ((ArrayPtg)ptgs[0]).getTokenArrayValues()[0][0]; - assertEquals(-42.0, ((Double)element).doubleValue(), 0.0); + assertEquals(-42.0, ((Double)element).doubleValue(), 0.0); - // Should be able to handle whitespace between unary minus and digits (Excel - // accepts this formula after presenting the user with a confirmation dialog). - ptgs = parseFormula("{- 5}"); - element = ((ArrayPtg)ptgs[0]).getTokenArrayValues()[0][0]; - assertEquals(-5.0, ((Double)element).doubleValue(), 0.0); - } + // Should be able to handle whitespace between unary minus and digits (Excel + // accepts this formula after presenting the user with a confirmation dialog). + ptgs = parseFormula("{- 5}"); + element = ((ArrayPtg)ptgs[0]).getTokenArrayValues()[0][0]; + assertEquals(-5.0, ((Double)element).doubleValue(), 0.0); + } - @Test - public void testRangeOperator() throws IOException { + @Test + public void testRangeOperator() throws IOException { - HSSFWorkbook wb = new HSSFWorkbook(); - HSSFSheet sheet = wb.createSheet(); - HSSFCell cell = sheet.createRow(0).createCell(0); + HSSFWorkbook wb = new HSSFWorkbook(); + HSSFSheet sheet = wb.createSheet(); + HSSFCell cell = sheet.createRow(0).createCell(0); - wb.setSheetName(0, "Sheet1"); - cell.setCellFormula("Sheet1!B$4:Sheet1!$C1"); // explicit range ':' operator - assertEquals("Sheet1!B$4:Sheet1!$C1", cell.getCellFormula()); + wb.setSheetName(0, "Sheet1"); + cell.setCellFormula("Sheet1!B$4:Sheet1!$C1"); // explicit range ':' operator + assertEquals("Sheet1!B$4:Sheet1!$C1", cell.getCellFormula()); - cell.setCellFormula("Sheet1!B$4:$C1"); // plain area ref - assertEquals("Sheet1!B1:$C$4", cell.getCellFormula()); // note - area ref is normalised + cell.setCellFormula("Sheet1!B$4:$C1"); // plain area ref + assertEquals("Sheet1!B1:$C$4", cell.getCellFormula()); // note - area ref is normalised - cell.setCellFormula("Sheet1!$C1...B$4"); // different syntax for plain area ref - assertEquals("Sheet1!B1:$C$4", cell.getCellFormula()); + cell.setCellFormula("Sheet1!$C1...B$4"); // different syntax for plain area ref + assertEquals("Sheet1!B1:$C$4", cell.getCellFormula()); - // with funny sheet name - wb.setSheetName(0, "A1...A2"); - cell.setCellFormula("A1...A2!B1"); - assertEquals("A1...A2!B1", cell.getCellFormula()); - - wb.close(); - } + // with funny sheet name + wb.setSheetName(0, "A1...A2"); + cell.setCellFormula("A1...A2!B1"); + assertEquals("A1...A2!B1", cell.getCellFormula()); + + wb.close(); + } - @Test - public void testBooleanNamedSheet() throws IOException { - HSSFWorkbook wb = new HSSFWorkbook(); - HSSFSheet sheet = wb.createSheet("true"); - HSSFCell cell = sheet.createRow(0).createCell(0); - cell.setCellFormula("'true'!B2"); + @Test + public void testBooleanNamedSheet() throws IOException { + HSSFWorkbook wb = new HSSFWorkbook(); + HSSFSheet sheet = wb.createSheet("true"); + HSSFCell cell = sheet.createRow(0).createCell(0); + cell.setCellFormula("'true'!B2"); - assertEquals("'true'!B2", cell.getCellFormula()); - - wb.close(); - } + assertEquals("'true'!B2", cell.getCellFormula()); + + wb.close(); + } - @Test - public void testParseExternalWorkbookReference() throws IOException { - HSSFWorkbook wbA = HSSFTestDataSamples.openSampleWorkbook("multibookFormulaA.xls"); - HSSFCell cell = wbA.getSheetAt(0).getRow(0).getCell(0); + @Test + public void testParseExternalWorkbookReference() throws IOException { + HSSFWorkbook wbA = HSSFTestDataSamples.openSampleWorkbook("multibookFormulaA.xls"); + HSSFCell cell = wbA.getSheetAt(0).getRow(0).getCell(0); - // make sure formula in sample is as expected - assertEquals("[multibookFormulaB.xls]BSheet1!B1", cell.getCellFormula()); - Ptg[] expectedPtgs = FormulaExtractor.getPtgs(cell); - confirmSingle3DRef(expectedPtgs, 1); + // make sure formula in sample is as expected + assertEquals("[multibookFormulaB.xls]BSheet1!B1", cell.getCellFormula()); + Ptg[] expectedPtgs = FormulaExtractor.getPtgs(cell); + confirmSingle3DRef(expectedPtgs, 1); - // now try (re-)parsing the formula - Ptg[] actualPtgs = HSSFFormulaParser.parse("[multibookFormulaB.xls]BSheet1!B1", wbA); - confirmSingle3DRef(actualPtgs, 1); // externalSheetIndex 1 -> BSheet1 + // now try (re-)parsing the formula + Ptg[] actualPtgs = HSSFFormulaParser.parse("[multibookFormulaB.xls]BSheet1!B1", wbA); + confirmSingle3DRef(actualPtgs, 1); // externalSheetIndex 1 -> BSheet1 - // try parsing a formula pointing to a different external sheet - Ptg[] otherPtgs = HSSFFormulaParser.parse("[multibookFormulaB.xls]AnotherSheet!B1", wbA); - confirmSingle3DRef(otherPtgs, 0); // externalSheetIndex 0 -> AnotherSheet + // try parsing a formula pointing to a different external sheet + Ptg[] otherPtgs = HSSFFormulaParser.parse("[multibookFormulaB.xls]AnotherSheet!B1", wbA); + confirmSingle3DRef(otherPtgs, 0); // externalSheetIndex 0 -> AnotherSheet - // try setting the same formula in a cell - cell.setCellFormula("[multibookFormulaB.xls]AnotherSheet!B1"); - assertEquals("[multibookFormulaB.xls]AnotherSheet!B1", cell.getCellFormula()); - - wbA.close(); - } - - private static void confirmSingle3DRef(Ptg[] ptgs, int expectedExternSheetIndex) { - assertEquals(1, ptgs.length); - Ptg ptg0 = ptgs[0]; - assertTrue(ptg0 instanceof Ref3DPtg); - assertEquals(expectedExternSheetIndex, ((Ref3DPtg)ptg0).getExternSheetIndex()); - } + // try setting the same formula in a cell + cell.setCellFormula("[multibookFormulaB.xls]AnotherSheet!B1"); + assertEquals("[multibookFormulaB.xls]AnotherSheet!B1", cell.getCellFormula()); + + wbA.close(); + } + + private static void confirmSingle3DRef(Ptg[] ptgs, int expectedExternSheetIndex) { + assertEquals(1, ptgs.length); + Ptg ptg0 = ptgs[0]; + assertTrue(ptg0 instanceof Ref3DPtg); + assertEquals(expectedExternSheetIndex, ((Ref3DPtg)ptg0).getExternSheetIndex()); + } - @Test - public void testUnion() throws IOException { - String formula = "Sheet1!$B$2:$C$3,OFFSET(Sheet1!$E$2:$E$4,1,Sheet1!$A$1),Sheet1!$D$6"; - HSSFWorkbook wb = new HSSFWorkbook(); - wb.createSheet("Sheet1"); - Ptg[] ptgs = FormulaParser.parse(formula, HSSFEvaluationWorkbook.create(wb), FormulaType.CELL, -1); + @Test + public void testUnion() throws IOException { + String formula = "Sheet1!$B$2:$C$3,OFFSET(Sheet1!$E$2:$E$4,1,Sheet1!$A$1),Sheet1!$D$6"; + HSSFWorkbook wb = new HSSFWorkbook(); + wb.createSheet("Sheet1"); + Ptg[] ptgs = FormulaParser.parse(formula, HSSFEvaluationWorkbook.create(wb), FormulaType.CELL, -1); - confirmTokenClasses(ptgs, - // TODO - AttrPtg.class, // Excel prepends this - MemFuncPtg.class, - Area3DPtg.class, - Area3DPtg.class, - IntPtg.class, - Ref3DPtg.class, - FuncVarPtg.class, - UnionPtg.class, - Ref3DPtg.class, - UnionPtg.class - ); - MemFuncPtg mf = (MemFuncPtg)ptgs[0]; - assertEquals(45, mf.getLenRefSubexpression()); + confirmTokenClasses(ptgs, + // TODO - AttrPtg.class, // Excel prepends this + MemFuncPtg.class, + Area3DPtg.class, + Area3DPtg.class, + IntPtg.class, + Ref3DPtg.class, + FuncVarPtg.class, + UnionPtg.class, + Ref3DPtg.class, + UnionPtg.class + ); + MemFuncPtg mf = (MemFuncPtg)ptgs[0]; + assertEquals(45, mf.getLenRefSubexpression()); // We don't check the type of the operands. confirmTokenClasses("1,2", MemAreaPtg.class, IntPtg.class, IntPtg.class, UnionPtg.class); wb.close(); - } + } - @Test - public void testIntersection() throws IOException { + @Test + public void testIntersection() throws IOException { String formula = "Sheet1!$B$2:$C$3 OFFSET(Sheet1!$E$2:$E$4, 1,Sheet1!$A$1) Sheet1!$D$6"; HSSFWorkbook wb = new HSSFWorkbook(); wb.createSheet("Sheet1"); @@ -1246,30 +1246,30 @@ public final class TestFormulaParser { confirmTokenClasses("1 2", MemAreaPtg.class, IntPtg.class, IntPtg.class, IntersectionPtg.class); wb.close(); - } - - @Test - public void testComparisonInParen() { - confirmTokenClasses("(A1 > B2)", + } + + @Test + public void testComparisonInParen() { + confirmTokenClasses("(A1 > B2)", RefPtg.class, RefPtg.class, GreaterThanPtg.class, ParenthesisPtg.class ); - } - - @Test - public void testUnionInParen() { - confirmTokenClasses("(A1:B2,B2:C3)", + } + + @Test + public void testUnionInParen() { + confirmTokenClasses("(A1:B2,B2:C3)", MemAreaPtg.class, AreaPtg.class, AreaPtg.class, UnionPtg.class, ParenthesisPtg.class ); - } + } - @Test + @Test public void testIntersectionInParen() { confirmTokenClasses("(A1:B2 B2:C3)", MemAreaPtg.class, @@ -1280,316 +1280,316 @@ public final class TestFormulaParser { ); } - @Test - public void testRange_bug46643() throws IOException { - String formula = "Sheet1!A1:Sheet1!B3"; - HSSFWorkbook wb = new HSSFWorkbook(); - wb.createSheet("Sheet1"); - Ptg[] ptgs = FormulaParser.parse(formula, HSSFEvaluationWorkbook.create(wb), FormulaType.CELL, -1); + @Test + public void testRange_bug46643() throws IOException { + String formula = "Sheet1!A1:Sheet1!B3"; + HSSFWorkbook wb = new HSSFWorkbook(); + wb.createSheet("Sheet1"); + Ptg[] ptgs = FormulaParser.parse(formula, HSSFEvaluationWorkbook.create(wb), FormulaType.CELL, -1, -1); - if (ptgs.length == 3) { - confirmTokenClasses(ptgs, Ref3DPtg.class, Ref3DPtg.class, RangePtg.class); - fail("Identified bug 46643"); - } + if (ptgs.length == 3) { + confirmTokenClasses(ptgs, Ref3DPtg.class, Ref3DPtg.class, RangePtg.class); + fail("Identified bug 46643"); + } - confirmTokenClasses(ptgs, - MemFuncPtg.class, - Ref3DPtg.class, - Ref3DPtg.class, - RangePtg.class - ); - MemFuncPtg mf = (MemFuncPtg)ptgs[0]; - assertEquals(15, mf.getLenRefSubexpression()); - wb.close(); - } + confirmTokenClasses(ptgs, + MemFuncPtg.class, + Ref3DPtg.class, + Ref3DPtg.class, + RangePtg.class + ); + MemFuncPtg mf = (MemFuncPtg)ptgs[0]; + assertEquals(15, mf.getLenRefSubexpression()); + wb.close(); + } - /** Named ranges with backslashes, e.g. 'POI\\2009' */ - @Test - public void testBackSlashInNames() throws IOException { - HSSFWorkbook wb = new HSSFWorkbook(); + /** Named ranges with backslashes, e.g. 'POI\\2009' */ + @Test + public void testBackSlashInNames() throws IOException { + HSSFWorkbook wb = new HSSFWorkbook(); - HSSFName name = wb.createName(); - name.setNameName("POI\\2009"); - name.setRefersToFormula("Sheet1!$A$1"); + HSSFName name = wb.createName(); + name.setNameName("POI\\2009"); + name.setRefersToFormula("Sheet1!$A$1"); - HSSFSheet sheet = wb.createSheet(); - HSSFRow row = sheet.createRow(0); + HSSFSheet sheet = wb.createSheet(); + HSSFRow row = sheet.createRow(0); - HSSFCell cell_C1 = row.createCell(2); - cell_C1.setCellFormula("POI\\2009"); - assertEquals("POI\\2009", cell_C1.getCellFormula()); + HSSFCell cell_C1 = row.createCell(2); + cell_C1.setCellFormula("POI\\2009"); + assertEquals("POI\\2009", cell_C1.getCellFormula()); - HSSFCell cell_D1 = row.createCell(2); - cell_D1.setCellFormula("NOT(POI\\2009=\"3.5-final\")"); - assertEquals("NOT(POI\\2009=\"3.5-final\")", cell_D1.getCellFormula()); - - wb.close(); - } + HSSFCell cell_D1 = row.createCell(2); + cell_D1.setCellFormula("NOT(POI\\2009=\"3.5-final\")"); + assertEquals("NOT(POI\\2009=\"3.5-final\")", cell_D1.getCellFormula()); + + wb.close(); + } - /** - * TODO - delete equiv test: - * {@link BaseTestBugzillaIssues#test42448()} - */ - @Test - public void testParseAbnormalSheetNamesAndRanges_bug42448() throws IOException { - HSSFWorkbook wb = new HSSFWorkbook(); - wb.createSheet("A"); - try { - HSSFFormulaParser.parse("SUM(A!C7:A!C67)", wb); - } catch (StringIndexOutOfBoundsException e) { - fail("Identified bug 42448"); - } - // the exact example from the bugzilla description: - HSSFFormulaParser.parse("SUMPRODUCT(A!C7:A!C67, B8:B68) / B69", wb); - - wb.close(); - } + /** + * TODO - delete equiv test: + * {@link BaseTestBugzillaIssues#test42448()} + */ + @Test + public void testParseAbnormalSheetNamesAndRanges_bug42448() throws IOException { + HSSFWorkbook wb = new HSSFWorkbook(); + wb.createSheet("A"); + try { + HSSFFormulaParser.parse("SUM(A!C7:A!C67)", wb); + } catch (StringIndexOutOfBoundsException e) { + fail("Identified bug 42448"); + } + // the exact example from the bugzilla description: + HSSFFormulaParser.parse("SUMPRODUCT(A!C7:A!C67, B8:B68) / B69", wb); + + wb.close(); + } - @Test - public void testRangeFuncOperand_bug46951() throws IOException { - HSSFWorkbook wb = new HSSFWorkbook(); - Ptg[] ptgs; - try { - ptgs = HSSFFormulaParser.parse("SUM(C1:OFFSET(C1,0,B1))", wb); - } catch (RuntimeException e) { - if (e.getMessage().equals("Specified named range 'OFFSET' does not exist in the current workbook.")) { - fail("Identified bug 46951"); - } - wb.close(); - throw e; - } - confirmTokenClasses(ptgs, - MemFuncPtg.class, // [len=23] - RefPtg.class, // [C1] - RefPtg.class, // [C1] - IntPtg.class, // [0] - RefPtg.class, // [B1] - FuncVarPtg.class, // [OFFSET nArgs=3] - RangePtg.class, // - AttrPtg.class // [sum ] - ); - wb.close(); - } + @Test + public void testRangeFuncOperand_bug46951() throws IOException { + HSSFWorkbook wb = new HSSFWorkbook(); + Ptg[] ptgs; + try { + ptgs = HSSFFormulaParser.parse("SUM(C1:OFFSET(C1,0,B1))", wb); + } catch (RuntimeException e) { + if (e.getMessage().equals("Specified named range 'OFFSET' does not exist in the current workbook.")) { + fail("Identified bug 46951"); + } + wb.close(); + throw e; + } + confirmTokenClasses(ptgs, + MemFuncPtg.class, // [len=23] + RefPtg.class, // [C1] + RefPtg.class, // [C1] + IntPtg.class, // [0] + RefPtg.class, // [B1] + FuncVarPtg.class, // [OFFSET nArgs=3] + RangePtg.class, // + AttrPtg.class // [sum ] + ); + wb.close(); + } - @Test - public void testUnionOfFullCollFullRowRef() throws IOException { - Ptg[] ptgs; - ptgs = parseFormula("3:4"); - ptgs = parseFormula("$Z:$AC"); - confirmTokenClasses(ptgs, AreaPtg.class); - ptgs = parseFormula("B:B"); + @Test + public void testUnionOfFullCollFullRowRef() throws IOException { + Ptg[] ptgs; + ptgs = parseFormula("3:4"); + ptgs = parseFormula("$Z:$AC"); + confirmTokenClasses(ptgs, AreaPtg.class); + ptgs = parseFormula("B:B"); - ptgs = parseFormula("$11:$13"); - confirmTokenClasses(ptgs, AreaPtg.class); + ptgs = parseFormula("$11:$13"); + confirmTokenClasses(ptgs, AreaPtg.class); - ptgs = parseFormula("$A:$A,$1:$4"); - confirmTokenClasses(ptgs, MemAreaPtg.class, - AreaPtg.class, - AreaPtg.class, - UnionPtg.class - ); + ptgs = parseFormula("$A:$A,$1:$4"); + confirmTokenClasses(ptgs, MemAreaPtg.class, + AreaPtg.class, + AreaPtg.class, + UnionPtg.class + ); - HSSFWorkbook wb = new HSSFWorkbook(); - wb.createSheet("Sheet1"); - ptgs = HSSFFormulaParser.parse("Sheet1!$A:$A,Sheet1!$1:$4", wb); - confirmTokenClasses(ptgs, MemFuncPtg.class, - Area3DPtg.class, - Area3DPtg.class, - UnionPtg.class - ); + HSSFWorkbook wb = new HSSFWorkbook(); + wb.createSheet("Sheet1"); + ptgs = HSSFFormulaParser.parse("Sheet1!$A:$A,Sheet1!$1:$4", wb); + confirmTokenClasses(ptgs, MemFuncPtg.class, + Area3DPtg.class, + Area3DPtg.class, + UnionPtg.class + ); - ptgs = HSSFFormulaParser.parse("'Sheet1'!$A:$A,'Sheet1'!$1:$4", wb); - confirmTokenClasses(ptgs, - MemFuncPtg.class, - Area3DPtg.class, - Area3DPtg.class, - UnionPtg.class - ); - - wb.close(); - } + ptgs = HSSFFormulaParser.parse("'Sheet1'!$A:$A,'Sheet1'!$1:$4", wb); + confirmTokenClasses(ptgs, + MemFuncPtg.class, + Area3DPtg.class, + Area3DPtg.class, + UnionPtg.class + ); + + wb.close(); + } - @Test - public void testExplicitRangeWithTwoSheetNames() throws IOException { - HSSFWorkbook wb = new HSSFWorkbook(); - wb.createSheet("Sheet1"); - Ptg[] ptgs = HSSFFormulaParser.parse("Sheet1!F1:Sheet1!G2", wb); - confirmTokenClasses(ptgs, - MemFuncPtg.class, - Ref3DPtg.class, - Ref3DPtg.class, - RangePtg.class - ); - MemFuncPtg mf; - mf = (MemFuncPtg)ptgs[0]; - assertEquals(15, mf.getLenRefSubexpression()); - wb.close(); - } + @Test + public void testExplicitRangeWithTwoSheetNames() throws IOException { + HSSFWorkbook wb = new HSSFWorkbook(); + wb.createSheet("Sheet1"); + Ptg[] ptgs = HSSFFormulaParser.parse("Sheet1!F1:Sheet1!G2", wb); + confirmTokenClasses(ptgs, + MemFuncPtg.class, + Ref3DPtg.class, + Ref3DPtg.class, + RangePtg.class + ); + MemFuncPtg mf; + mf = (MemFuncPtg)ptgs[0]; + assertEquals(15, mf.getLenRefSubexpression()); + wb.close(); + } - /** - * Checks that the area-ref and explicit range operators get the right associativity - * and that the {@link MemFuncPtg} / {@link MemAreaPtg} is added correctly - */ - @Test - public void testComplexExplicitRangeEncodings() { + /** + * Checks that the area-ref and explicit range operators get the right associativity + * and that the {@link MemFuncPtg} / {@link MemAreaPtg} is added correctly + */ + @Test + public void testComplexExplicitRangeEncodings() { - Ptg[] ptgs; - ptgs = parseFormula("SUM(OFFSET(A1,0,0):B2:C3:D4:E5:OFFSET(F6,1,1):G7)"); - confirmTokenClasses(ptgs, - // AttrPtg.class, // [volatile ] // POI doesn't do this yet (Apr 2009) - MemFuncPtg.class, // len 57 - RefPtg.class, // [A1] - IntPtg.class, // [0] - IntPtg.class, // [0] - FuncVarPtg.class, // [OFFSET nArgs=3] - AreaPtg.class, // [B2:C3] - RangePtg.class, - AreaPtg.class, // [D4:E5] - RangePtg.class, - RefPtg.class, // [F6] - IntPtg.class, // [1] - IntPtg.class, // [1] - FuncVarPtg.class, // [OFFSET nArgs=3] - RangePtg.class, - RefPtg.class, // [G7] - RangePtg.class, - AttrPtg.class // [sum ] - ); + Ptg[] ptgs; + ptgs = parseFormula("SUM(OFFSET(A1,0,0):B2:C3:D4:E5:OFFSET(F6,1,1):G7)"); + confirmTokenClasses(ptgs, + // AttrPtg.class, // [volatile ] // POI doesn't do this yet (Apr 2009) + MemFuncPtg.class, // len 57 + RefPtg.class, // [A1] + IntPtg.class, // [0] + IntPtg.class, // [0] + FuncVarPtg.class, // [OFFSET nArgs=3] + AreaPtg.class, // [B2:C3] + RangePtg.class, + AreaPtg.class, // [D4:E5] + RangePtg.class, + RefPtg.class, // [F6] + IntPtg.class, // [1] + IntPtg.class, // [1] + FuncVarPtg.class, // [OFFSET nArgs=3] + RangePtg.class, + RefPtg.class, // [G7] + RangePtg.class, + AttrPtg.class // [sum ] + ); - MemFuncPtg mf = (MemFuncPtg)ptgs[0]; - assertEquals(57, mf.getLenRefSubexpression()); - assertEquals("D4:E5", ((AreaPtgBase)ptgs[7]).toFormulaString()); - assertTrue(((AttrPtg)ptgs[16]).isSum()); + MemFuncPtg mf = (MemFuncPtg)ptgs[0]; + assertEquals(57, mf.getLenRefSubexpression()); + assertEquals("D4:E5", ((AreaPtgBase)ptgs[7]).toFormulaString()); + assertTrue(((AttrPtg)ptgs[16]).isSum()); - ptgs = parseFormula("SUM(A1:B2:C3:D4)"); - confirmTokenClasses(ptgs, - // AttrPtg.class, // [volatile ] // POI doesn't do this yet (Apr 2009) - MemAreaPtg.class, // len 19 - AreaPtg.class, // [A1:B2] - AreaPtg.class, // [C3:D4] - RangePtg.class, - AttrPtg.class // [sum ] - ); - MemAreaPtg ma = (MemAreaPtg)ptgs[0]; - assertEquals(19, ma.getLenRefSubexpression()); - } + ptgs = parseFormula("SUM(A1:B2:C3:D4)"); + confirmTokenClasses(ptgs, + // AttrPtg.class, // [volatile ] // POI doesn't do this yet (Apr 2009) + MemAreaPtg.class, // len 19 + AreaPtg.class, // [A1:B2] + AreaPtg.class, // [C3:D4] + RangePtg.class, + AttrPtg.class // [sum ] + ); + MemAreaPtg ma = (MemAreaPtg)ptgs[0]; + assertEquals(19, ma.getLenRefSubexpression()); + } - /** - * Mostly confirming that erroneous conditions are detected. Actual error message wording is not critical. - * - */ - @Test - public void testEdgeCaseParserErrors() throws IOException { - HSSFWorkbook wb = new HSSFWorkbook(); - wb.createSheet("Sheet1"); + /** + * Mostly confirming that erroneous conditions are detected. Actual error message wording is not critical. + * + */ + @Test + public void testEdgeCaseParserErrors() throws IOException { + HSSFWorkbook wb = new HSSFWorkbook(); + wb.createSheet("Sheet1"); - confirmParseError(wb, "A1:ROUND(B1,1)", "The RHS of the range operator ':' at position 3 is not a proper reference."); + confirmParseError(wb, "A1:ROUND(B1,1)", "The RHS of the range operator ':' at position 3 is not a proper reference."); - confirmParseError(wb, "Sheet1!!!", "Parse error near char 7 '!' in specified formula 'Sheet1!!!'. Expected number, string, or defined name"); - confirmParseError(wb, "Sheet1!.Name", "Parse error near char 7 '.' in specified formula 'Sheet1!.Name'. Expected number, string, or defined name"); - confirmParseError(wb, "Sheet1!Sheet1", "Specified name 'Sheet1' for sheet Sheet1 not found"); - confirmParseError(wb, "Sheet1!F:Sheet1!G", "'Sheet1!F' is not a proper reference."); - confirmParseError(wb, "Sheet1!F..foobar", "Complete area reference expected after sheet name at index 11."); - confirmParseError(wb, "Sheet1!A .. B", "Dotted range (full row or column) expression 'A .. B' must not contain whitespace."); - confirmParseError(wb, "Sheet1!A...B", "Dotted range (full row or column) expression 'A...B' must have exactly 2 dots."); - confirmParseError(wb, "Sheet1!A foobar", "Second part of cell reference expected after sheet name at index 10."); + confirmParseError(wb, "Sheet1!!!", "Parse error near char 7 '!' in specified formula 'Sheet1!!!'. Expected number, string, defined name, or data table"); + confirmParseError(wb, "Sheet1!.Name", "Parse error near char 7 '.' in specified formula 'Sheet1!.Name'. Expected number, string, defined name, or data table"); + confirmParseError(wb, "Sheet1!Sheet1", "Specified name 'Sheet1' for sheet Sheet1 not found"); + confirmParseError(wb, "Sheet1!F:Sheet1!G", "'Sheet1!F' is not a proper reference."); + confirmParseError(wb, "Sheet1!F..foobar", "Complete area reference expected after sheet name at index 11."); + confirmParseError(wb, "Sheet1!A .. B", "Dotted range (full row or column) expression 'A .. B' must not contain whitespace."); + confirmParseError(wb, "Sheet1!A...B", "Dotted range (full row or column) expression 'A...B' must have exactly 2 dots."); + confirmParseError(wb, "Sheet1!A foobar", "Second part of cell reference expected after sheet name at index 10."); - confirmParseError(wb, "foobar", "Specified named range 'foobar' does not exist in the current workbook."); - confirmParseError(wb, "A1:1", "The RHS of the range operator ':' at position 3 is not a proper reference."); - wb.close(); - } + confirmParseError(wb, "foobar", "Specified named range 'foobar' does not exist in the current workbook."); + confirmParseError(wb, "A1:1", "The RHS of the range operator ':' at position 3 is not a proper reference."); + wb.close(); + } - private static void confirmParseError(HSSFWorkbook wb, String formula, String expectedMessage) { + private static void confirmParseError(HSSFWorkbook wb, String formula, String expectedMessage) { - try { - HSSFFormulaParser.parse(formula, wb); - fail("Expected formula parse execption"); - } catch (FormulaParseException e) { - confirmParseException(e, expectedMessage); - } - } + try { + HSSFFormulaParser.parse(formula, wb); + fail("Expected formula parse execption"); + } catch (FormulaParseException e) { + confirmParseException(e, expectedMessage); + } + } - /** - * In bug 47078, POI had trouble evaluating a defined name flagged as 'complex'. - * POI should also be able to parse such defined names. - */ - @Test - public void testParseComplexName() throws IOException { + /** + * In bug 47078, POI had trouble evaluating a defined name flagged as 'complex'. + * POI should also be able to parse such defined names. + */ + @Test + public void testParseComplexName() throws IOException { - // Mock up a spreadsheet to match the critical details of the sample - HSSFWorkbook wb = new HSSFWorkbook(); - wb.createSheet("Sheet1"); - HSSFName definedName = wb.createName(); - definedName.setNameName("foo"); - definedName.setRefersToFormula("Sheet1!B2"); + // Mock up a spreadsheet to match the critical details of the sample + HSSFWorkbook wb = new HSSFWorkbook(); + wb.createSheet("Sheet1"); + HSSFName definedName = wb.createName(); + definedName.setNameName("foo"); + definedName.setRefersToFormula("Sheet1!B2"); - // Set the complex flag - POI doesn't usually manipulate this flag - NameRecord nameRec = TestHSSFName.getNameRecord(definedName); - nameRec.setOptionFlag((short)0x10); // 0x10 -> complex + // Set the complex flag - POI doesn't usually manipulate this flag + NameRecord nameRec = TestHSSFName.getNameRecord(definedName); + nameRec.setOptionFlag((short)0x10); // 0x10 -> complex - Ptg[] result; - try { - result = HSSFFormulaParser.parse("1+foo", wb); - } catch (FormulaParseException e) { - if (e.getMessage().equals("Specified name 'foo' is not a range as expected.")) { - fail("Identified bug 47078c"); - } - wb.close(); - throw e; - } - confirmTokenClasses(result, IntPtg.class, NamePtg.class, AddPtg.class); - - wb.close(); - } + Ptg[] result; + try { + result = HSSFFormulaParser.parse("1+foo", wb); + } catch (FormulaParseException e) { + if (e.getMessage().equals("Specified name 'foo' is not a range as expected.")) { + fail("Identified bug 47078c"); + } + wb.close(); + throw e; + } + confirmTokenClasses(result, IntPtg.class, NamePtg.class, AddPtg.class); + + wb.close(); + } - /** - * Zero is not a valid row number so cell references like 'A0' are not valid. - * Actually, they should be treated like defined names. - *
- * In addition, leading zeros (on the row component) should be removed from cell - * references during parsing. - */ - @Test - public void testZeroRowRefs() throws IOException { - String badCellRef = "B0"; // bad because zero is not a valid row number - String leadingZeroCellRef = "B000001"; // this should get parsed as "B1" - HSSFWorkbook wb = new HSSFWorkbook(); + /** + * Zero is not a valid row number so cell references like 'A0' are not valid. + * Actually, they should be treated like defined names. + *
+ * In addition, leading zeros (on the row component) should be removed from cell + * references during parsing. + */ + @Test + public void testZeroRowRefs() throws IOException { + String badCellRef = "B0"; // bad because zero is not a valid row number + String leadingZeroCellRef = "B000001"; // this should get parsed as "B1" + HSSFWorkbook wb = new HSSFWorkbook(); - try { - HSSFFormulaParser.parse(badCellRef, wb); - fail("Identified bug 47312b - Shouldn't be able to parse cell ref '" - + badCellRef + "'."); - } catch (FormulaParseException e) { - // expected during successful test - confirmParseException(e, "Specified named range '" - + badCellRef + "' does not exist in the current workbook."); - } + try { + HSSFFormulaParser.parse(badCellRef, wb); + fail("Identified bug 47312b - Shouldn't be able to parse cell ref '" + + badCellRef + "'."); + } catch (FormulaParseException e) { + // expected during successful test + confirmParseException(e, "Specified named range '" + + badCellRef + "' does not exist in the current workbook."); + } - Ptg[] ptgs; - try { - ptgs = HSSFFormulaParser.parse(leadingZeroCellRef, wb); - assertEquals("B1", ((RefPtg) ptgs[0]).toFormulaString()); - } catch (FormulaParseException e) { - confirmParseException(e, "Specified named range '" - + leadingZeroCellRef + "' does not exist in the current workbook."); - // close but no cigar - fail("Identified bug 47312c - '" + leadingZeroCellRef + "' should parse as 'B1'."); - } + Ptg[] ptgs; + try { + ptgs = HSSFFormulaParser.parse(leadingZeroCellRef, wb); + assertEquals("B1", ((RefPtg) ptgs[0]).toFormulaString()); + } catch (FormulaParseException e) { + confirmParseException(e, "Specified named range '" + + leadingZeroCellRef + "' does not exist in the current workbook."); + // close but no cigar + fail("Identified bug 47312c - '" + leadingZeroCellRef + "' should parse as 'B1'."); + } - // create a defined name called 'B0' and try again - Name n = wb.createName(); - n.setNameName("B0"); - n.setRefersToFormula("1+1"); - ptgs = HSSFFormulaParser.parse("B0", wb); - confirmTokenClasses(ptgs, NamePtg.class); - - wb.close(); - } + // create a defined name called 'B0' and try again + Name n = wb.createName(); + n.setNameName("B0"); + n.setRefersToFormula("1+1"); + ptgs = HSSFFormulaParser.parse("B0", wb); + confirmTokenClasses(ptgs, NamePtg.class); + + wb.close(); + } - private static void confirmParseException(FormulaParseException e, String expMsg) { - assertEquals(expMsg, e.getMessage()); - } + private static void confirmParseException(FormulaParseException e, String expMsg) { + assertEquals(expMsg, e.getMessage()); + } @Test public void test57196_Formula() throws IOException { diff --git a/src/testcases/org/apache/poi/hssf/record/TestSharedFormulaRecord.java b/src/testcases/org/apache/poi/hssf/record/TestSharedFormulaRecord.java index 3a5a43493..14de4bcda 100644 --- a/src/testcases/org/apache/poi/hssf/record/TestSharedFormulaRecord.java +++ b/src/testcases/org/apache/poi/hssf/record/TestSharedFormulaRecord.java @@ -38,67 +38,67 @@ import org.apache.poi.util.LittleEndianInput; */ public final class TestSharedFormulaRecord extends TestCase { - /** - * A sample spreadsheet known to have one sheet with 4 shared formula ranges - */ - private static final String SHARED_FORMULA_TEST_XLS = "SharedFormulaTest.xls"; - /** - * 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))" - * 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*, - * tAreaN*) with operand class 'array'. - */ - private static final byte[] SHARED_FORMULA_WITH_REF_ARRAYS_DATA = { - 0x1A, 0x00, - 0x63, 0x02, 0x00, 0x00, 0x00, - 0x6C, 0x00, 0x00, 0x02, (byte)0x80, // tRefNA - 0x0B, - 0x15, - 0x13, - 0x13, - 0x63, 0x03, 0x00, 0x00, 0x00, - 0x15, - 0x13, - 0x13, - 0x42, 0x02, (byte)0xE4, 0x00, - }; + /** + * A sample spreadsheet known to have one sheet with 4 shared formula ranges + */ + private static final String SHARED_FORMULA_TEST_XLS = "SharedFormulaTest.xls"; + /** + * 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))" + * 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*, + * tAreaN*) with operand class 'array'. + */ + private static final byte[] SHARED_FORMULA_WITH_REF_ARRAYS_DATA = { + 0x1A, 0x00, + 0x63, 0x02, 0x00, 0x00, 0x00, + 0x6C, 0x00, 0x00, 0x02, (byte)0x80, // tRefNA + 0x0B, + 0x15, + 0x13, + 0x13, + 0x63, 0x03, 0x00, 0x00, 0x00, + 0x15, + 0x13, + 0x13, + 0x42, 0x02, (byte)0xE4, 0x00, + }; - /** - * The method SharedFormulaRecord.convertSharedFormulas() converts formulas from - * '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 - * incorrect encoding. The formula here is one such example (Excel displays #VALUE!). - */ - public void testConvertSharedFormulasOperandClasses_bug45123() { + /** + * The method SharedFormulaRecord.convertSharedFormulas() converts formulas from + * '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 + * incorrect encoding. The formula here is one such example (Excel displays #VALUE!). + */ + public void testConvertSharedFormulasOperandClasses_bug45123() { - LittleEndianInput in = TestcaseRecordInputStream.createLittleEndian(SHARED_FORMULA_WITH_REF_ARRAYS_DATA); - int encodedLen = in.readUShort(); - Ptg[] sharedFormula = Ptg.readTokens(encodedLen, in); + LittleEndianInput in = TestcaseRecordInputStream.createLittleEndian(SHARED_FORMULA_WITH_REF_ARRAYS_DATA); + int encodedLen = in.readUShort(); + Ptg[] sharedFormula = Ptg.readTokens(encodedLen, in); 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]; - assertEquals("$C101", refPtg.toFormulaString()); - if (refPtg.getPtgClass() == Ptg.CLASS_REF) { - throw new AssertionFailedError("Identified bug 45123"); - } + RefPtg refPtg = (RefPtg) convertedFormula[1]; + assertEquals("$C101", refPtg.toFormulaString()); + if (refPtg.getPtgClass() == Ptg.CLASS_REF) { + throw new AssertionFailedError("Identified bug 45123"); + } - confirmOperandClasses(sharedFormula, convertedFormula); - } + confirmOperandClasses(sharedFormula, convertedFormula); + } - private static void confirmOperandClasses(Ptg[] originalPtgs, Ptg[] convertedPtgs) { - assertEquals(originalPtgs.length, convertedPtgs.length); - for (int i = 0; i < convertedPtgs.length; i++) { - Ptg originalPtg = originalPtgs[i]; - Ptg convertedPtg = convertedPtgs[i]; - if (originalPtg.getPtgClass() != convertedPtg.getPtgClass()) { - throw new ComparisonFailure("Different operand class for token[" + i + "]", - String.valueOf(originalPtg.getPtgClass()), String.valueOf(convertedPtg.getPtgClass())); - } - } - } + private static void confirmOperandClasses(Ptg[] originalPtgs, Ptg[] convertedPtgs) { + assertEquals(originalPtgs.length, convertedPtgs.length); + for (int i = 0; i < convertedPtgs.length; i++) { + Ptg originalPtg = originalPtgs[i]; + Ptg convertedPtg = convertedPtgs[i]; + if (originalPtg.getPtgClass() != convertedPtg.getPtgClass()) { + throw new ComparisonFailure("Different operand class for token[" + i + "]", + String.valueOf(originalPtg.getPtgClass()), String.valueOf(convertedPtg.getPtgClass())); + } + } + } public void testConvertSharedFormulas() { HSSFWorkbook wb = new HSSFWorkbook(); @@ -138,111 +138,111 @@ public final class TestSharedFormulaRecord extends TestCase { } /** - * Make sure that POI preserves {@link SharedFormulaRecord}s - */ - public void testPreserveOnReserialize() { - HSSFWorkbook wb; - HSSFSheet sheet; - HSSFCell cellB32769; - HSSFCell cellC32769; + * Make sure that POI preserves {@link SharedFormulaRecord}s + */ + public void testPreserveOnReserialize() { + HSSFWorkbook wb; + HSSFSheet sheet; + HSSFCell cellB32769; + HSSFCell cellC32769; - // Reading directly from XLS file - wb = HSSFTestDataSamples.openSampleWorkbook(SHARED_FORMULA_TEST_XLS); - sheet = wb.getSheetAt(0); - cellB32769 = sheet.getRow(32768).getCell(1); - cellC32769 = sheet.getRow(32768).getCell(2); - // check reading of formulas which are shared (two cells from a 1R x 8C range) - assertEquals("B32770*2", cellB32769.getCellFormula()); - assertEquals("C32770*2", cellC32769.getCellFormula()); - confirmCellEvaluation(wb, cellB32769, 4); - confirmCellEvaluation(wb, cellC32769, 6); - // Confirm this example really does have SharedFormulas. - // there are 3 others besides the one at A32769:H32769 - assertEquals(4, countSharedFormulas(sheet)); + // Reading directly from XLS file + wb = HSSFTestDataSamples.openSampleWorkbook(SHARED_FORMULA_TEST_XLS); + sheet = wb.getSheetAt(0); + cellB32769 = sheet.getRow(32768).getCell(1); + cellC32769 = sheet.getRow(32768).getCell(2); + // check reading of formulas which are shared (two cells from a 1R x 8C range) + assertEquals("B32770*2", cellB32769.getCellFormula()); + assertEquals("C32770*2", cellC32769.getCellFormula()); + confirmCellEvaluation(wb, cellB32769, 4); + confirmCellEvaluation(wb, cellC32769, 6); + // Confirm this example really does have SharedFormulas. + // there are 3 others besides the one at A32769:H32769 + assertEquals(4, countSharedFormulas(sheet)); - // Re-serialize and check again - wb = HSSFTestDataSamples.writeOutAndReadBack(wb); - sheet = wb.getSheetAt(0); - cellB32769 = sheet.getRow(32768).getCell(1); - cellC32769 = sheet.getRow(32768).getCell(2); - assertEquals("B32770*2", cellB32769.getCellFormula()); - confirmCellEvaluation(wb, cellB32769, 4); - assertEquals(4, countSharedFormulas(sheet)); - } + // Re-serialize and check again + wb = HSSFTestDataSamples.writeOutAndReadBack(wb); + sheet = wb.getSheetAt(0); + cellB32769 = sheet.getRow(32768).getCell(1); + cellC32769 = sheet.getRow(32768).getCell(2); + assertEquals("B32770*2", cellB32769.getCellFormula()); + confirmCellEvaluation(wb, cellB32769, 4); + assertEquals(4, countSharedFormulas(sheet)); + } - public void testUnshareFormulaDueToChangeFormula() { - HSSFWorkbook wb; - HSSFSheet sheet; - HSSFCell cellB32769; - HSSFCell cellC32769; + public void testUnshareFormulaDueToChangeFormula() { + HSSFWorkbook wb; + HSSFSheet sheet; + HSSFCell cellB32769; + HSSFCell cellC32769; - wb = HSSFTestDataSamples.openSampleWorkbook(SHARED_FORMULA_TEST_XLS); - sheet = wb.getSheetAt(0); - cellB32769 = sheet.getRow(32768).getCell(1); - cellC32769 = sheet.getRow(32768).getCell(2); + wb = HSSFTestDataSamples.openSampleWorkbook(SHARED_FORMULA_TEST_XLS); + sheet = wb.getSheetAt(0); + cellB32769 = sheet.getRow(32768).getCell(1); + cellC32769 = sheet.getRow(32768).getCell(2); - // Updating cell formula, causing it to become unshared - cellB32769.setCellFormula("1+1"); - confirmCellEvaluation(wb, cellB32769, 2); - // currently (Oct 2008) POI handles this by exploding the whole shared formula group - assertEquals(3, countSharedFormulas(sheet)); // one less now - // check that nearby cell of the same group still has the same formula - assertEquals("C32770*2", cellC32769.getCellFormula()); - confirmCellEvaluation(wb, cellC32769, 6); - } - public void testUnshareFormulaDueToDelete() { - HSSFWorkbook wb; - HSSFSheet sheet; - HSSFCell cell; - final int ROW_IX = 2; + // Updating cell formula, causing it to become unshared + cellB32769.setCellFormula("1+1"); + confirmCellEvaluation(wb, cellB32769, 2); + // currently (Oct 2008) POI handles this by exploding the whole shared formula group + assertEquals(3, countSharedFormulas(sheet)); // one less now + // check that nearby cell of the same group still has the same formula + assertEquals("C32770*2", cellC32769.getCellFormula()); + confirmCellEvaluation(wb, cellC32769, 6); + } + public void testUnshareFormulaDueToDelete() { + HSSFWorkbook wb; + HSSFSheet sheet; + HSSFCell cell; + final int ROW_IX = 2; - // changing shared formula cell to blank - wb = HSSFTestDataSamples.openSampleWorkbook(SHARED_FORMULA_TEST_XLS); - sheet = wb.getSheetAt(0); + // changing shared formula cell to blank + wb = HSSFTestDataSamples.openSampleWorkbook(SHARED_FORMULA_TEST_XLS); + sheet = wb.getSheetAt(0); - assertEquals("A$1*2", sheet.getRow(ROW_IX).getCell(1).getCellFormula()); - cell = sheet.getRow(ROW_IX).getCell(1); - cell.setCellType(HSSFCell.CELL_TYPE_BLANK); - assertEquals(3, countSharedFormulas(sheet)); + assertEquals("A$1*2", sheet.getRow(ROW_IX).getCell(1).getCellFormula()); + cell = sheet.getRow(ROW_IX).getCell(1); + cell.setCellType(HSSFCell.CELL_TYPE_BLANK); + assertEquals(3, countSharedFormulas(sheet)); - wb = HSSFTestDataSamples.writeOutAndReadBack(wb); - sheet = wb.getSheetAt(0); - assertEquals("A$1*2", sheet.getRow(ROW_IX+1).getCell(1).getCellFormula()); + wb = HSSFTestDataSamples.writeOutAndReadBack(wb); + sheet = wb.getSheetAt(0); + assertEquals("A$1*2", sheet.getRow(ROW_IX+1).getCell(1).getCellFormula()); - // deleting shared formula cell - wb = HSSFTestDataSamples.openSampleWorkbook(SHARED_FORMULA_TEST_XLS); - sheet = wb.getSheetAt(0); + // deleting shared formula cell + wb = HSSFTestDataSamples.openSampleWorkbook(SHARED_FORMULA_TEST_XLS); + sheet = wb.getSheetAt(0); - assertEquals("A$1*2", sheet.getRow(ROW_IX).getCell(1).getCellFormula()); - cell = sheet.getRow(ROW_IX).getCell(1); - sheet.getRow(ROW_IX).removeCell(cell); - assertEquals(3, countSharedFormulas(sheet)); + assertEquals("A$1*2", sheet.getRow(ROW_IX).getCell(1).getCellFormula()); + cell = sheet.getRow(ROW_IX).getCell(1); + sheet.getRow(ROW_IX).removeCell(cell); + assertEquals(3, countSharedFormulas(sheet)); - wb = HSSFTestDataSamples.writeOutAndReadBack(wb); - sheet = wb.getSheetAt(0); - assertEquals("A$1*2", sheet.getRow(ROW_IX+1).getCell(1).getCellFormula()); - } + wb = HSSFTestDataSamples.writeOutAndReadBack(wb); + sheet = wb.getSheetAt(0); + assertEquals("A$1*2", sheet.getRow(ROW_IX+1).getCell(1).getCellFormula()); + } - private static void confirmCellEvaluation(HSSFWorkbook wb, HSSFCell cell, double expectedValue) { - HSSFFormulaEvaluator fe = new HSSFFormulaEvaluator(wb); - CellValue cv = fe.evaluate(cell); - assertEquals(HSSFCell.CELL_TYPE_NUMERIC, cv.getCellType()); - assertEquals(expectedValue, cv.getNumberValue(), 0.0); - } + private static void confirmCellEvaluation(HSSFWorkbook wb, HSSFCell cell, double expectedValue) { + HSSFFormulaEvaluator fe = new HSSFFormulaEvaluator(wb); + CellValue cv = fe.evaluate(cell); + assertEquals(HSSFCell.CELL_TYPE_NUMERIC, cv.getCellType()); + assertEquals(expectedValue, cv.getNumberValue(), 0.0); + } - /** - * @return the number of {@link SharedFormulaRecord}s encoded for the specified sheet - */ - private static int countSharedFormulas(HSSFSheet sheet) { - Record[] records = RecordInspector.getRecords(sheet, 0); - int count = 0; - for (int i = 0; i < records.length; i++) { - Record rec = records[i]; - if(rec instanceof SharedFormulaRecord) { - count++; - } - } - return count; - } + /** + * @return the number of {@link SharedFormulaRecord}s encoded for the specified sheet + */ + private static int countSharedFormulas(HSSFSheet sheet) { + Record[] records = RecordInspector.getRecords(sheet, 0); + int count = 0; + for (int i = 0; i < records.length; i++) { + Record rec = records[i]; + if(rec instanceof SharedFormulaRecord) { + count++; + } + } + return count; + } } diff --git a/src/testcases/org/apache/poi/ss/formula/functions/TestIndirect.java b/src/testcases/org/apache/poi/ss/formula/functions/TestIndirect.java index 10f7a70a1..c2118a147 100644 --- a/src/testcases/org/apache/poi/ss/formula/functions/TestIndirect.java +++ b/src/testcases/org/apache/poi/ss/formula/functions/TestIndirect.java @@ -36,32 +36,32 @@ import org.junit.Test; * Tests for the INDIRECT() function.

*/ public final class TestIndirect { - // convenient access to namespace - // private static final ErrorEval EE = null; + // convenient access to namespace + // private static final ErrorEval EE = null; - private static void createDataRow(HSSFSheet sheet, int rowIndex, double... vals) { - HSSFRow row = sheet.createRow(rowIndex); - for (int i = 0; i < vals.length; i++) { - row.createCell(i).setCellValue(vals[i]); - } - } + private static void createDataRow(HSSFSheet sheet, int rowIndex, double... vals) { + HSSFRow row = sheet.createRow(rowIndex); + for (int i = 0; i < vals.length; i++) { + row.createCell(i).setCellValue(vals[i]); + } + } - private static HSSFWorkbook createWBA() { - HSSFWorkbook wb = new HSSFWorkbook(); - HSSFSheet sheet1 = wb.createSheet("Sheet1"); - HSSFSheet sheet2 = wb.createSheet("Sheet2"); - HSSFSheet sheet3 = wb.createSheet("John's sales"); + private static HSSFWorkbook createWBA() { + HSSFWorkbook wb = new HSSFWorkbook(); + HSSFSheet sheet1 = wb.createSheet("Sheet1"); + HSSFSheet sheet2 = wb.createSheet("Sheet2"); + HSSFSheet sheet3 = wb.createSheet("John's sales"); - createDataRow(sheet1, 0, 11, 12, 13, 14); - createDataRow(sheet1, 1, 21, 22, 23, 24); - createDataRow(sheet1, 2, 31, 32, 33, 34); + createDataRow(sheet1, 0, 11, 12, 13, 14); + createDataRow(sheet1, 1, 21, 22, 23, 24); + createDataRow(sheet1, 2, 31, 32, 33, 34); - createDataRow(sheet2, 0, 50, 55, 60, 65); - createDataRow(sheet2, 1, 51, 56, 61, 66); - createDataRow(sheet2, 2, 52, 57, 62, 67); + createDataRow(sheet2, 0, 50, 55, 60, 65); + createDataRow(sheet2, 1, 51, 56, 61, 66); + createDataRow(sheet2, 2, 52, 57, 62, 67); - createDataRow(sheet3, 0, 30, 31, 32); - createDataRow(sheet3, 1, 33, 34, 35); + createDataRow(sheet3, 0, 30, 31, 32); + createDataRow(sheet3, 1, 33, 34, 35); HSSFName name1 = wb.createName(); name1.setNameName("sales1"); @@ -75,131 +75,131 @@ public final class TestIndirect { row.createCell(0).setCellValue("sales1"); //A4 row.createCell(1).setCellValue("sales2"); //B4 - return wb; - } + return wb; + } - private static HSSFWorkbook createWBB() { - HSSFWorkbook wb = new HSSFWorkbook(); - HSSFSheet sheet1 = wb.createSheet("Sheet1"); - HSSFSheet sheet2 = wb.createSheet("Sheet2"); - HSSFSheet sheet3 = wb.createSheet("## Look here!"); + private static HSSFWorkbook createWBB() { + HSSFWorkbook wb = new HSSFWorkbook(); + HSSFSheet sheet1 = wb.createSheet("Sheet1"); + HSSFSheet sheet2 = wb.createSheet("Sheet2"); + HSSFSheet sheet3 = wb.createSheet("## Look here!"); - createDataRow(sheet1, 0, 400, 440, 480, 520); - createDataRow(sheet1, 1, 420, 460, 500, 540); + createDataRow(sheet1, 0, 400, 440, 480, 520); + createDataRow(sheet1, 1, 420, 460, 500, 540); - createDataRow(sheet2, 0, 50, 55, 60, 65); - createDataRow(sheet2, 1, 51, 56, 61, 66); + createDataRow(sheet2, 0, 50, 55, 60, 65); + createDataRow(sheet2, 1, 51, 56, 61, 66); - createDataRow(sheet3, 0, 42); + createDataRow(sheet3, 0, 42); - return wb; - } + return wb; + } - @Test - public void testBasic() throws Exception { + @Test + public void testBasic() throws Exception { - HSSFWorkbook wbA = createWBA(); - HSSFCell c = wbA.getSheetAt(0).createRow(5).createCell(2); - HSSFFormulaEvaluator feA = new HSSFFormulaEvaluator(wbA); + HSSFWorkbook wbA = createWBA(); + HSSFCell c = wbA.getSheetAt(0).createRow(5).createCell(2); + HSSFFormulaEvaluator feA = new HSSFFormulaEvaluator(wbA); - // non-error cases - confirm(feA, c, "INDIRECT(\"C2\")", 23); - confirm(feA, c, "INDIRECT(\"$C2\")", 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); // spaces in area ref - 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); // case-insensitive sheet name - 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(\"A1\", TRUE)", 11); // explicit arg1. only TRUE supported so far + // non-error cases + confirm(feA, c, "INDIRECT(\"C2\")", 23); + confirm(feA, c, "INDIRECT(\"$C2\")", 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); // spaces in area ref + 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); // case-insensitive sheet name + 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(\"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(B4))", 351); // indirect defined name pointinh to other sheet - // simple error propagation: + // simple error propagation: - // 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(#NAME?, \"x\")", ErrorEval.NAME_INVALID); - confirm(feA, c, "INDIRECT(#NUM!, #N/A)", ErrorEval.NUM_ERROR); + // 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(#NAME?, \"x\")", ErrorEval.NAME_INVALID); + confirm(feA, c, "INDIRECT(#NUM!, #N/A)", ErrorEval.NUM_ERROR); - // arg1 is evaluated to boolean before arg0 is decoded - 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\", \"flase\")", ErrorEval.VALUE_INVALID); // must be "TRUE" or "FALSE" + // arg1 is evaluated to boolean before arg0 is decoded + 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\", \"flase\")", ErrorEval.VALUE_INVALID); // must be "TRUE" or "FALSE" - // 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); + // 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, "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(\"Sheet3!A1\")", ErrorEval.REF_INVALID); // unknown sheet -// if (false) { // TODO - support evaluation of defined names -// 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!A 1\")", ErrorEval.REF_INVALID); // space in cell ref - - wbA.close(); - } + 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(\"Sheet3!A1\")", ErrorEval.REF_INVALID); // unknown sheet +// if (false) { // TODO - support evaluation of defined names +// 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!A 1\")", ErrorEval.REF_INVALID); // space in cell ref + + wbA.close(); + } - @Test - public void testMultipleWorkbooks() throws Exception { - HSSFWorkbook wbA = createWBA(); - HSSFCell cellA = wbA.getSheetAt(0).createRow(10).createCell(0); - HSSFFormulaEvaluator feA = new HSSFFormulaEvaluator(wbA); + @Test + public void testMultipleWorkbooks() throws Exception { + HSSFWorkbook wbA = createWBA(); + HSSFCell cellA = wbA.getSheetAt(0).createRow(10).createCell(0); + HSSFFormulaEvaluator feA = new HSSFFormulaEvaluator(wbA); - HSSFWorkbook wbB = createWBB(); - HSSFCell cellB = wbB.getSheetAt(0).createRow(10).createCell(0); - HSSFFormulaEvaluator feB = new HSSFFormulaEvaluator(wbB); + HSSFWorkbook wbB = createWBB(); + HSSFCell cellB = wbB.getSheetAt(0).createRow(10).createCell(0); + HSSFFormulaEvaluator feB = new HSSFFormulaEvaluator(wbB); - String[] workbookNames = { "MyBook", "Figures for January", }; - HSSFFormulaEvaluator[] evaluators = { feA, feB, }; - HSSFFormulaEvaluator.setupEnvironment(workbookNames, evaluators); + String[] workbookNames = { "MyBook", "Figures for January", }; + HSSFFormulaEvaluator[] evaluators = { feA, feB, }; + HSSFFormulaEvaluator.setupEnvironment(workbookNames, evaluators); - 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(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 - // 2 level recursion - 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 - - wbB.close(); - wbA.close(); - } + // 2 level recursion + 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 + + wbB.close(); + wbA.close(); + } - private static void confirm(FormulaEvaluator fe, Cell cell, String formula, - double expectedResult) { - fe.clearAllCachedResultValues(); - cell.setCellFormula(formula); - 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); - } - - private static void confirm(FormulaEvaluator fe, Cell cell, String formula, - ErrorEval expectedResult) { - fe.clearAllCachedResultValues(); - cell.setCellFormula(formula); - CellValue cv = fe.evaluate(cell); - if (cv.getCellType() != Cell.CELL_TYPE_ERROR) { - fail("expected error cell type but got " + cv.formatAsString()); - } - int expCode = expectedResult.getErrorCode(); - if (cv.getErrorValue() != expCode) { - fail("Expected error '" + ErrorEval.getText(expCode) - + "' but got '" + cv.formatAsString() + "'."); - } - } + private static void confirm(FormulaEvaluator fe, Cell cell, String formula, + double expectedResult) { + fe.clearAllCachedResultValues(); + cell.setCellFormula(formula); + 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); + } + + private static void confirm(FormulaEvaluator fe, Cell cell, String formula, + ErrorEval expectedResult) { + fe.clearAllCachedResultValues(); + cell.setCellFormula(formula); + CellValue cv = fe.evaluate(cell); + if (cv.getCellType() != Cell.CELL_TYPE_ERROR) { + fail("expected error cell type but got " + cv.formatAsString()); + } + int expCode = expectedResult.getErrorCode(); + if (cv.getErrorValue() != expCode) { + fail("Expected error '" + ErrorEval.getText(expCode) + + "' but got '" + cv.formatAsString() + "'."); + } + } }