From c444cd27e9495b10ab898479afef1d3ffb95d37a Mon Sep 17 00:00:00 2001 From: Yegor Kozlov Date: Sun, 17 May 2009 16:35:25 +0000 Subject: [PATCH] Allow columns greater than 255 and rows greater than 0x100000 in XSSF formulas git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@775701 13f79535-47bb-0310-9956-ffa450edef68 --- src/documentation/content/xdocs/changes.xml | 1 + src/documentation/content/xdocs/status.xml | 1 + .../poi/hssf/record/formula/AreaPtgBase.java | 13 +-- .../poi/hssf/record/formula/RefPtgBase.java | 8 -- .../record/formula/SheetNameFormatter.java | 3 +- .../apache/poi/ss/formula/FormulaParser.java | 37 +++++-- .../org/apache/poi/ss/util/CellReference.java | 47 +++++---- .../xssf/usermodel/TestXSSFFormulaParser.java | 97 +++++++++++++++++++ .../poi/hssf/util/TestCellReference.java | 3 +- 9 files changed, 162 insertions(+), 48 deletions(-) create mode 100755 src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFFormulaParser.java diff --git a/src/documentation/content/xdocs/changes.xml b/src/documentation/content/xdocs/changes.xml index 0b0d34ea7..b80c6188e 100644 --- a/src/documentation/content/xdocs/changes.xml +++ b/src/documentation/content/xdocs/changes.xml @@ -37,6 +37,7 @@ + 46806 - Allow columns greater than 255 and rows greater than 0x100000 in XSSF formulas 41711 - Base class for "old version" exceptions, and new HSLF detection + use of old versions exception 47179 - Fix string encoding issues with HSMF chunks on non-windows platforms 47183 - Attachment support for HSMF diff --git a/src/documentation/content/xdocs/status.xml b/src/documentation/content/xdocs/status.xml index 380b622ce..a02803155 100644 --- a/src/documentation/content/xdocs/status.xml +++ b/src/documentation/content/xdocs/status.xml @@ -34,6 +34,7 @@ + 46806 - Allow columns greater than 255 and rows greater than 0x100000 in XSSF formulas 41711 - Base class for "old version" exceptions, and new HSLF detection + use of old versions exception 47179 - Fix string encoding issues with HSMF chunks on non-windows platforms 47183 - Attachment support for HSMF diff --git a/src/java/org/apache/poi/hssf/record/formula/AreaPtgBase.java b/src/java/org/apache/poi/hssf/record/formula/AreaPtgBase.java index 0c1e3e613..5cd5a9b74 100644 --- a/src/java/org/apache/poi/hssf/record/formula/AreaPtgBase.java +++ b/src/java/org/apache/poi/hssf/record/formula/AreaPtgBase.java @@ -71,11 +71,6 @@ public abstract class AreaPtgBase extends OperandPtg implements AreaI { protected AreaPtgBase(int firstRow, int lastRow, int firstColumn, int lastColumn, boolean firstRowRelative, boolean lastRowRelative, boolean firstColRelative, boolean lastColRelative) { - checkColumnBounds(firstColumn); - checkColumnBounds(lastColumn); - checkRowBounds(firstRow); - checkRowBounds(lastRow); - if (lastRow > firstRow) { setFirstRow(firstRow); setLastRow(lastRow); @@ -101,12 +96,12 @@ public abstract class AreaPtgBase extends OperandPtg implements AreaI { } } - private static void checkColumnBounds(int colIx) { + private static void $checkColumnBounds(int colIx) { if((colIx & 0x0FF) != colIx) { throw new IllegalArgumentException("colIx (" + colIx + ") is out of range"); } } - private static void checkRowBounds(int rowIx) { + private static void $checkRowBounds(int rowIx) { if((rowIx & 0x0FFFF) != rowIx) { throw new IllegalArgumentException("rowIx (" + rowIx + ") is out of range"); } @@ -137,7 +132,6 @@ public abstract class AreaPtgBase extends OperandPtg implements AreaI { * @param rowIx number (0-based) */ public final void setFirstRow(int rowIx) { - checkRowBounds(rowIx); field_1_first_row = rowIx; } @@ -152,7 +146,6 @@ public abstract class AreaPtgBase extends OperandPtg implements AreaI { * @param rowIx last row number in the area */ public final void setLastRow(int rowIx) { - checkRowBounds(rowIx); field_2_last_row = rowIx; } @@ -203,7 +196,6 @@ public abstract class AreaPtgBase extends OperandPtg implements AreaI { * set the first column in the area */ public final void setFirstColumn(int colIx) { - checkColumnBounds(colIx); field_3_first_column=columnMask.setValue(field_3_first_column, colIx); } @@ -262,7 +254,6 @@ public abstract class AreaPtgBase extends OperandPtg implements AreaI { * set the last column in the area */ public final void setLastColumn(int colIx) { - checkColumnBounds(colIx); field_4_last_column=columnMask.setValue(field_4_last_column, colIx); } diff --git a/src/java/org/apache/poi/hssf/record/formula/RefPtgBase.java b/src/java/org/apache/poi/hssf/record/formula/RefPtgBase.java index 205dd0ddf..1bef14d90 100644 --- a/src/java/org/apache/poi/hssf/record/formula/RefPtgBase.java +++ b/src/java/org/apache/poi/hssf/record/formula/RefPtgBase.java @@ -32,8 +32,6 @@ import org.apache.poi.ss.SpreadsheetVersion; */ public abstract class RefPtgBase extends OperandPtg { - private final static int MAX_ROW_NUMBER = SpreadsheetVersion.EXCEL97.getMaxRows(); - /** The row index - zero based unsigned 16 bit value */ private int field_1_row; /** @@ -67,9 +65,6 @@ public abstract class RefPtgBase extends OperandPtg { } public final void setRow(int rowIndex) { - if (rowIndex < 0 || rowIndex >= MAX_ROW_NUMBER) { - throw new IllegalArgumentException("rowIndex must be between 0 and " + MAX_ROW_NUMBER); - } field_1_row = rowIndex; } @@ -97,9 +92,6 @@ public abstract class RefPtgBase extends OperandPtg { } public final void setColumn(int col) { - if (col < 0 || col >= 0x100) { - throw new IllegalArgumentException("Specified colIx (" + col + ") is out of range"); - } field_2_col = column.setValue(field_2_col, col); } diff --git a/src/java/org/apache/poi/hssf/record/formula/SheetNameFormatter.java b/src/java/org/apache/poi/hssf/record/formula/SheetNameFormatter.java index 1d581c230..2e84d2f9e 100755 --- a/src/java/org/apache/poi/hssf/record/formula/SheetNameFormatter.java +++ b/src/java/org/apache/poi/hssf/record/formula/SheetNameFormatter.java @@ -21,6 +21,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.poi.hssf.util.CellReference; +import org.apache.poi.ss.SpreadsheetVersion; /** * Formats sheet names for use in formula expressions. @@ -183,7 +184,7 @@ public final class SheetNameFormatter { * @see org.apache.poi.hssf.util.CellReference */ /* package */ static boolean cellReferenceIsWithinRange(String lettersPrefix, String numbersSuffix) { - return CellReference.cellReferenceIsWithinRange(lettersPrefix, numbersSuffix); + return CellReference.cellReferenceIsWithinRange(lettersPrefix, numbersSuffix, SpreadsheetVersion.EXCEL97); } /** diff --git a/src/java/org/apache/poi/ss/formula/FormulaParser.java b/src/java/org/apache/poi/ss/formula/FormulaParser.java index 7cd7e7394..ec2252d5e 100644 --- a/src/java/org/apache/poi/ss/formula/FormulaParser.java +++ b/src/java/org/apache/poi/ss/formula/FormulaParser.java @@ -31,6 +31,7 @@ import org.apache.poi.hssf.usermodel.HSSFErrorConstants; import org.apache.poi.ss.util.AreaReference; import org.apache.poi.ss.util.CellReference; import org.apache.poi.ss.util.CellReference.NameType; +import org.apache.poi.ss.SpreadsheetVersion; /** * This class parses a formula string into a List of tokens in RPN order. @@ -140,6 +141,7 @@ public final class FormulaParser { private char look; private FormulaParsingWorkbook _book; + private SpreadsheetVersion _ssVersion; private int _sheetIndex; @@ -160,7 +162,8 @@ public final class FormulaParser { _formulaString = formula; _pointer=0; _book = book; - _formulaLength = _formulaString.length(); + _ssVersion = book == null ? SpreadsheetVersion.EXCEL97 : book.getSpreadsheetVersion(); + _formulaLength = _formulaString.length(); _sheetIndex = sheetIndex; } @@ -699,8 +702,8 @@ public final class FormulaParser { if (!isValidCellReference(rep)) { return null; } - } else if (hasLetters) { - if (!CellReference.isColumnWithnRange(rep.replace("$", ""))) { + } else if (hasLetters) { + if (!CellReference.isColumnWithnRange(rep.replace("$", ""), _ssVersion)) { return null; } } else if (hasDigits) { @@ -798,7 +801,6 @@ public final class FormulaParser { /** * Note - caller should reset {@link #_pointer} upon null result - * @param iden identifier prefix (if unquoted, it is terminated at first dot) * @return The sheet name as an identifier null if '!' is not found in the right place */ private SheetIdentifier parseSheetName() { @@ -878,8 +880,30 @@ public final class FormulaParser { /** * @return true if the specified name is a valid cell reference */ - private static boolean isValidCellReference(String str) { - return CellReference.classifyCellReference(str) == NameType.CELL; + 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()) != 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; } @@ -932,7 +956,6 @@ public final class FormulaParser { *

* For IF Formulas, additional PTGs are added to the tokens * @param name a {@link NamePtg} or {@link NameXPtg} or null - * @param numArgs * @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) { diff --git a/src/java/org/apache/poi/ss/util/CellReference.java b/src/java/org/apache/poi/ss/util/CellReference.java index 1b19f594a..b559c4302 100644 --- a/src/java/org/apache/poi/ss/util/CellReference.java +++ b/src/java/org/apache/poi/ss/util/CellReference.java @@ -21,6 +21,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.poi.hssf.record.formula.SheetNameFormatter; +import org.apache.poi.hssf.record.formula.function.FunctionMetadataRegistry; import org.apache.poi.ss.SpreadsheetVersion; /** @@ -62,10 +63,10 @@ public class CellReference { * digits or dot. (They can even end in dot). */ private static final Pattern NAMED_RANGE_NAME_PATTERN = Pattern.compile("[_A-Za-z][_.A-Za-z0-9]*"); - private static final String BIFF8_LAST_COLUMN = SpreadsheetVersion.EXCEL97.getLastColumnName(); - private static final int BIFF8_LAST_COLUMN_TEXT_LEN = BIFF8_LAST_COLUMN.length(); - private static final String BIFF8_LAST_ROW = String.valueOf(SpreadsheetVersion.EXCEL97.getMaxRows()); - private static final int BIFF8_LAST_ROW_TEXT_LEN = BIFF8_LAST_ROW.length(); + //private static final String BIFF8_LAST_COLUMN = SpreadsheetVersion.EXCEL97.getLastColumnName(); + //private static final int BIFF8_LAST_COLUMN_TEXT_LEN = BIFF8_LAST_COLUMN.length(); + //private static final String BIFF8_LAST_ROW = String.valueOf(SpreadsheetVersion.EXCEL97.getMaxRows()); + //private static final int BIFF8_LAST_ROW_TEXT_LEN = BIFF8_LAST_ROW.length(); private final int _rowIndex; private final int _colIndex; @@ -176,7 +177,7 @@ public class CellReference { * Classifies an identifier as either a simple (2D) cell reference or a named range name * @return one of the values from NameType */ - public static int classifyCellReference(String str) { + public static int classifyCellReference(String str, SpreadsheetVersion ssVersion) { int len = str.length(); if (len < 1) { throw new IllegalArgumentException("Empty string not allowed"); @@ -195,15 +196,15 @@ public class CellReference { } if (!Character.isDigit(str.charAt(len-1))) { // no digits at end of str - return validateNamedRangeName(str); + return validateNamedRangeName(str, ssVersion); } Matcher cellRefPatternMatcher = CELL_REF_PATTERN.matcher(str); if (!cellRefPatternMatcher.matches()) { - return validateNamedRangeName(str); + return validateNamedRangeName(str, ssVersion); } String lettersGroup = cellRefPatternMatcher.group(1); String digitsGroup = cellRefPatternMatcher.group(2); - if (cellReferenceIsWithinRange(lettersGroup, digitsGroup)) { + if (cellReferenceIsWithinRange(lettersGroup, digitsGroup, ssVersion)) { // valid cell reference return NameType.CELL; } @@ -219,11 +220,11 @@ public class CellReference { return NameType.NAMED_RANGE; } - private static int validateNamedRangeName(String str) { + private static int validateNamedRangeName(String str, SpreadsheetVersion ssVersion) { Matcher colMatcher = COLUMN_REF_PATTERN.matcher(str); if (colMatcher.matches()) { String colStr = colMatcher.group(1); - if (isColumnWithnRange(colStr)) { + if (isColumnWithnRange(colStr, ssVersion)) { return NameType.COLUMN; } } @@ -270,18 +271,21 @@ public class CellReference { * @param rowStr a string of only digit characters * @return true if the row and col parameters are within range of a BIFF8 spreadsheet. */ - public static boolean cellReferenceIsWithinRange(String colStr, String rowStr) { - if (!isColumnWithnRange(colStr)) { + public static boolean cellReferenceIsWithinRange(String colStr, String rowStr, SpreadsheetVersion ssVersion) { + if (!isColumnWithnRange(colStr, ssVersion)) { return false; } - int nDigits = rowStr.length(); - if(nDigits > BIFF8_LAST_ROW_TEXT_LEN) { + String lastRow = String.valueOf(ssVersion.getMaxRows()); + int lastRowLen = lastRow.length(); + + int nDigits = rowStr.length(); + if(nDigits > lastRowLen) { return false; } - if(nDigits == BIFF8_LAST_ROW_TEXT_LEN) { + if(nDigits == lastRowLen) { // ASCII comparison is valid if digit count is same - if(rowStr.compareTo(BIFF8_LAST_ROW) > 0) { + if(rowStr.compareTo(lastRow) > 0) { return false; } } else { @@ -292,14 +296,17 @@ public class CellReference { return true; } - public static boolean isColumnWithnRange(String colStr) { + public static boolean isColumnWithnRange(String colStr, SpreadsheetVersion ssVersion) { + String lastCol = ssVersion.getLastColumnName(); + int lastColLength = lastCol.length(); + int numberOfLetters = colStr.length(); - if(numberOfLetters > BIFF8_LAST_COLUMN_TEXT_LEN) { + if(numberOfLetters > lastColLength) { // "Sheet1" case etc return false; // that was easy } - if(numberOfLetters == BIFF8_LAST_COLUMN_TEXT_LEN) { - if(colStr.toUpperCase().compareTo(BIFF8_LAST_COLUMN) > 0) { + if(numberOfLetters == lastColLength) { + if(colStr.toUpperCase().compareTo(lastCol) > 0) { return false; } } else { diff --git a/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFFormulaParser.java b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFFormulaParser.java new file mode 100755 index 000000000..0d3de41cc --- /dev/null +++ b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFFormulaParser.java @@ -0,0 +1,97 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (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 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + +package org.apache.poi.xssf.usermodel; + +import junit.framework.TestCase; + +import org.apache.poi.hssf.record.formula.Ptg; +import org.apache.poi.hssf.record.formula.RefPtg; +import org.apache.poi.hssf.record.formula.IntPtg; +import org.apache.poi.hssf.record.formula.FuncPtg; +import org.apache.poi.ss.formula.FormulaParser; +import org.apache.poi.ss.formula.FormulaType; + +public final class TestXSSFFormulaParser extends TestCase { + + + public void testParse() { + XSSFWorkbook wb = new XSSFWorkbook(); + XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(wb); + String fmla; + Ptg[] ptgs; + + fmla = "ABC10"; + ptgs = FormulaParser.parse(fmla, fpb, FormulaType.CELL); + assertEquals(1, ptgs.length); + assertTrue("",(ptgs[0] instanceof RefPtg)); + + fmla = "A500000"; + ptgs = FormulaParser.parse(fmla, fpb, FormulaType.CELL); + assertEquals(1, ptgs.length); + assertTrue("",(ptgs[0] instanceof RefPtg)); + + fmla = "ABC500000"; + ptgs = FormulaParser.parse(fmla, fpb, FormulaType.CELL); + assertEquals(1, ptgs.length); + assertTrue("",(ptgs[0] instanceof RefPtg)); + + //highest allowed rows and column (XFD and 0x100000) + fmla = "XFD1048576"; + ptgs = FormulaParser.parse(fmla, fpb, FormulaType.CELL); + assertEquals(1, ptgs.length); + assertTrue("",(ptgs[0] instanceof RefPtg)); + + + //column greater than XFD + fmla = "XFE10"; + try { + ptgs = FormulaParser.parse(fmla, fpb, FormulaType.CELL); + fail("expected exception"); + } catch (Exception e){ + assertEquals("Specified named range 'XFE10' does not exist in the current workbook.", e.getMessage()); + } + + //row greater than 0x100000 + fmla = "XFD1048577"; + try { + ptgs = FormulaParser.parse(fmla, fpb, FormulaType.CELL); + fail("expected exception"); + } catch (Exception e){ + assertEquals("Specified named range 'XFD1048577' does not exist in the current workbook.", e.getMessage()); + } + } + + public void testBuiltInFormulas() { + XSSFWorkbook wb = new XSSFWorkbook(); + XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(wb); + String fmla; + Ptg[] ptgs; + + fmla = "LOG10"; + ptgs = FormulaParser.parse(fmla, fpb, FormulaType.CELL); + assertEquals(1, ptgs.length); + assertTrue("",(ptgs[0] instanceof RefPtg)); + + fmla = "LOG10(100)"; + ptgs = FormulaParser.parse(fmla, fpb, FormulaType.CELL); + assertEquals(2, ptgs.length); + assertTrue("",(ptgs[0] instanceof IntPtg)); + assertTrue("",(ptgs[1] instanceof FuncPtg)); + + } +} \ No newline at end of file diff --git a/src/testcases/org/apache/poi/hssf/util/TestCellReference.java b/src/testcases/org/apache/poi/hssf/util/TestCellReference.java index 9cc8659ff..acd3f00e7 100644 --- a/src/testcases/org/apache/poi/hssf/util/TestCellReference.java +++ b/src/testcases/org/apache/poi/hssf/util/TestCellReference.java @@ -22,6 +22,7 @@ import junit.framework.TestCase; import org.apache.poi.ss.util.CellReference; import org.apache.poi.ss.util.CellReference.NameType; +import org.apache.poi.ss.SpreadsheetVersion; public final class TestCellReference extends TestCase { @@ -104,7 +105,7 @@ public final class TestCellReference extends TestCase { } private void confirmNameType(String ref, int expectedResult) { - int actualResult = CellReference.classifyCellReference(ref); + int actualResult = CellReference.classifyCellReference(ref, SpreadsheetVersion.EXCEL97); assertEquals(expectedResult, actualResult); } }