From 9c3d11b9536387782aba5ab0bcb99f8247de47e9 Mon Sep 17 00:00:00 2001 From: Yegor Kozlov Date: Wed, 10 Nov 2010 16:09:04 +0000 Subject: [PATCH] Fixed evaluation of cell references with column index greater than 255, see bugzilla 50096 git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1033556 13f79535-47bb-0310-9956-ffa450edef68 --- src/documentation/content/xdocs/status.xml | 1 + .../poi/hssf/record/SharedFormulaRecord.java | 65 +----------- .../poi/hssf/record/formula/RefPtgBase.java | 9 +- .../hssf/record/formula/SharedFormula.java | 97 ++++++++++++++++++ .../apache/poi/xssf/usermodel/XSSFCell.java | 5 +- .../usermodel/TestXSSFFormulaEvaluation.java | 32 ++++++ .../hssf/record/TestSharedFormulaRecord.java | 19 ++-- .../hssf/record/formula/TestReferencePtg.java | 15 +++ test-data/spreadsheet/50096.xlsx | Bin 0 -> 12391 bytes 9 files changed, 172 insertions(+), 71 deletions(-) create mode 100644 src/java/org/apache/poi/hssf/record/formula/SharedFormula.java create mode 100644 test-data/spreadsheet/50096.xlsx diff --git a/src/documentation/content/xdocs/status.xml b/src/documentation/content/xdocs/status.xml index deb2890c7..cf143a31f 100644 --- a/src/documentation/content/xdocs/status.xml +++ b/src/documentation/content/xdocs/status.xml @@ -34,6 +34,7 @@ + 50096 - Fixed evaluation of cell references with column index greater than 255 49761 - Tolerate Double.NaN when reading .xls files 50211 - Use cached formula result when auto-sizing formula cells 50118 - OLE2 does allow a directory with an empty name, so support this in POIFS diff --git a/src/java/org/apache/poi/hssf/record/SharedFormulaRecord.java b/src/java/org/apache/poi/hssf/record/SharedFormulaRecord.java index 0b0625855..9e5b9c8b3 100644 --- a/src/java/org/apache/poi/hssf/record/SharedFormulaRecord.java +++ b/src/java/org/apache/poi/hssf/record/SharedFormulaRecord.java @@ -20,6 +20,7 @@ package org.apache.poi.hssf.record; import org.apache.poi.hssf.record.formula.*; import org.apache.poi.hssf.util.CellRangeAddress8Bit; import org.apache.poi.ss.formula.Formula; +import org.apache.poi.ss.SpreadsheetVersion; import org.apache.poi.util.HexDump; import org.apache.poi.util.LittleEndianOutput; @@ -96,51 +97,6 @@ public final class SharedFormulaRecord extends SharedValueRecordBase { return sid; } - /** - * Creates a non shared formula from the shared formula counterpart
- * - * Perhaps this functionality could be implemented in terms of the raw - * byte array inside {@link Formula}. - */ - public static Ptg[] convertSharedFormulas(Ptg[] ptgs, int formulaRow, int formulaColumn) { - - Ptg[] newPtgStack = new Ptg[ptgs.length]; - - for (int k = 0; k < ptgs.length; k++) { - Ptg ptg = ptgs[k]; - byte originalOperandClass = -1; - if (!ptg.isBaseToken()) { - originalOperandClass = ptg.getPtgClass(); - } - if (ptg instanceof RefPtgBase) { - RefPtgBase refNPtg = (RefPtgBase)ptg; - ptg = new RefPtg(fixupRelativeRow(formulaRow,refNPtg.getRow(),refNPtg.isRowRelative()), - fixupRelativeColumn(formulaColumn,refNPtg.getColumn(),refNPtg.isColRelative()), - refNPtg.isRowRelative(), - refNPtg.isColRelative()); - ptg.setClass(originalOperandClass); - } else if (ptg instanceof AreaPtgBase) { - AreaPtgBase areaNPtg = (AreaPtgBase)ptg; - ptg = new AreaPtg(fixupRelativeRow(formulaRow,areaNPtg.getFirstRow(),areaNPtg.isFirstRowRelative()), - fixupRelativeRow(formulaRow,areaNPtg.getLastRow(),areaNPtg.isLastRowRelative()), - fixupRelativeColumn(formulaColumn,areaNPtg.getFirstColumn(),areaNPtg.isFirstColRelative()), - fixupRelativeColumn(formulaColumn,areaNPtg.getLastColumn(),areaNPtg.isLastColRelative()), - areaNPtg.isFirstRowRelative(), - areaNPtg.isLastRowRelative(), - areaNPtg.isFirstColRelative(), - areaNPtg.isLastColRelative()); - ptg.setClass(originalOperandClass); - } else if (ptg instanceof OperandPtg) { - // Any subclass of OperandPtg is mutable, so it's safest to not share these instances. - ptg = ((OperandPtg) ptg).copy(); - } else { - // all other Ptgs are immutable and can be shared - } - newPtgStack[k] = ptg; - } - return newPtgStack; - } - /** * @return the equivalent {@link Ptg} array that the formula would have, were it not shared. */ @@ -152,23 +108,8 @@ public final class SharedFormulaRecord extends SharedValueRecordBase { throw new RuntimeException("Shared Formula Conversion: Coding Error"); } - return convertSharedFormulas(field_7_parsed_expr.getTokens(), formulaRow, formulaColumn); - } - - private static int fixupRelativeColumn(int currentcolumn, int column, boolean relative) { - if(relative) { - // mask out upper bits to produce 'wrapping' at column 256 ("IV") - return (column + currentcolumn) & 0x00FF; - } - return column; - } - - private static int fixupRelativeRow(int currentrow, int row, boolean relative) { - if(relative) { - // mask out upper bits to produce 'wrapping' at row 65536 - return (row+currentrow) & 0x00FFFF; - } - return row; + SharedFormula sf = new SharedFormula(SpreadsheetVersion.EXCEL97); + return sf.convertSharedFormulas(field_7_parsed_expr.getTokens(), formulaRow, formulaColumn); } public Object clone() { 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 70ed4da87..23c8f996f 100644 --- a/src/java/org/apache/poi/hssf/record/formula/RefPtgBase.java +++ b/src/java/org/apache/poi/hssf/record/formula/RefPtgBase.java @@ -40,7 +40,14 @@ public abstract class RefPtgBase extends OperandPtg { private int field_2_col; private static final BitField rowRelative = BitFieldFactory.getInstance(0x8000); private static final BitField colRelative = BitFieldFactory.getInstance(0x4000); - private static final BitField column = BitFieldFactory.getInstance(0x00FF); + + /** + * YK: subclasses of RefPtgBase are used by the FormulaParser and FormulaEvaluator accross HSSF and XSSF. + * The bit mask should accomodate the maximum number of avaiable columns, i.e. 0x3FFF. + * + * @see org.apache.poi.ss.SpreadsheetVersion + */ + private static final BitField column = BitFieldFactory.getInstance(0x3FFF); protected RefPtgBase() { // Required for clone methods diff --git a/src/java/org/apache/poi/hssf/record/formula/SharedFormula.java b/src/java/org/apache/poi/hssf/record/formula/SharedFormula.java new file mode 100644 index 000000000..db55a7015 --- /dev/null +++ b/src/java/org/apache/poi/hssf/record/formula/SharedFormula.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.hssf.record.formula; + +import org.apache.poi.ss.SpreadsheetVersion; + +/** + * Encapsulates logic to convert shared formulaa into non shared equivalent + */ +public class SharedFormula { + + private final int _columnWrappingMask; + private final int _rowWrappingMask; + + public SharedFormula(SpreadsheetVersion ssVersion){ + _columnWrappingMask = ssVersion.getLastColumnIndex(); //"IV" for .xls and "XFD" for .xlsx + _rowWrappingMask = ssVersion.getLastRowIndex(); + } + + /** + * Creates a non shared formula from the shared formula counterpart, i.e. + * Converts the shared formula into the equivalent {@link Ptg} array that it would have, + * were it not shared. + * + * @param ptgs parsed tokens of the shared formula + * @param formulaRow + * @param formulaColumn + */ + public Ptg[] convertSharedFormulas(Ptg[] ptgs, int formulaRow, int formulaColumn) { + + Ptg[] newPtgStack = new Ptg[ptgs.length]; + + for (int k = 0; k < ptgs.length; k++) { + Ptg ptg = ptgs[k]; + byte originalOperandClass = -1; + if (!ptg.isBaseToken()) { + originalOperandClass = ptg.getPtgClass(); + } + if (ptg instanceof RefPtgBase) { + RefPtgBase refNPtg = (RefPtgBase)ptg; + ptg = new RefPtg(fixupRelativeRow(formulaRow,refNPtg.getRow(),refNPtg.isRowRelative()), + fixupRelativeColumn(formulaColumn,refNPtg.getColumn(),refNPtg.isColRelative()), + refNPtg.isRowRelative(), + refNPtg.isColRelative()); + ptg.setClass(originalOperandClass); + } else if (ptg instanceof AreaPtgBase) { + AreaPtgBase areaNPtg = (AreaPtgBase)ptg; + ptg = new AreaPtg(fixupRelativeRow(formulaRow,areaNPtg.getFirstRow(),areaNPtg.isFirstRowRelative()), + fixupRelativeRow(formulaRow,areaNPtg.getLastRow(),areaNPtg.isLastRowRelative()), + fixupRelativeColumn(formulaColumn,areaNPtg.getFirstColumn(),areaNPtg.isFirstColRelative()), + fixupRelativeColumn(formulaColumn,areaNPtg.getLastColumn(),areaNPtg.isLastColRelative()), + areaNPtg.isFirstRowRelative(), + areaNPtg.isLastRowRelative(), + areaNPtg.isFirstColRelative(), + areaNPtg.isLastColRelative()); + ptg.setClass(originalOperandClass); + } else if (ptg instanceof OperandPtg) { + // Any subclass of OperandPtg is mutable, so it's safest to not share these instances. + ptg = ((OperandPtg) ptg).copy(); + } else { + // all other Ptgs are immutable and can be shared + } + newPtgStack[k] = ptg; + } + return newPtgStack; + } + + private int fixupRelativeColumn(int currentcolumn, int column, boolean relative) { + if(relative) { + // mask out upper bits to produce 'wrapping' at the maximum column ("IV" for .xls and "XFD" for .xlsx) + return (column + currentcolumn) & _columnWrappingMask; + } + return column; + } + + private int fixupRelativeRow(int currentrow, int row, boolean relative) { + if(relative) { + return (row+currentrow) & _rowWrappingMask; + } + return row; + } + +} 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 e0998bc59..3d9d9275e 100644 --- a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFCell.java +++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFCell.java @@ -24,6 +24,7 @@ import java.util.Date; import org.apache.poi.hssf.record.SharedFormulaRecord; import org.apache.poi.hssf.record.formula.Ptg; +import org.apache.poi.hssf.record.formula.SharedFormula; import org.apache.poi.hssf.record.formula.eval.ErrorEval; import org.apache.poi.ss.SpreadsheetVersion; import org.apache.poi.ss.formula.FormulaParser; @@ -391,8 +392,10 @@ public final class XSSFCell implements Cell { int sheetIndex = sheet.getWorkbook().getSheetIndex(sheet); XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(sheet.getWorkbook()); + SharedFormula sf = new SharedFormula(SpreadsheetVersion.EXCEL2007); + Ptg[] ptgs = FormulaParser.parse(sharedFormula, fpb, FormulaType.CELL, sheetIndex); - Ptg[] fmla = SharedFormulaRecord.convertSharedFormulas(ptgs, + Ptg[] fmla = sf.convertSharedFormulas(ptgs, getRowIndex() - ref.getFirstRow(), getColumnIndex() - ref.getFirstColumn()); return FormulaRenderer.toFormulaString(fpb, fmla); } diff --git a/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFFormulaEvaluation.java b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFFormulaEvaluation.java index 2c66b957e..a452e5f9c 100644 --- a/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFFormulaEvaluation.java +++ b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFFormulaEvaluation.java @@ -20,6 +20,7 @@ package org.apache.poi.xssf.usermodel; import junit.framework.TestCase; import org.apache.poi.ss.usermodel.*; +import org.apache.poi.ss.util.CellReference; import org.apache.poi.xssf.XSSFTestDataSamples; import org.apache.poi.xssf.XSSFITestDataProvider; @@ -57,4 +58,35 @@ public final class TestXSSFFormulaEvaluation extends BaseTestFormulaEvaluator { XSSFCell d3 = sheet.getRow(2).getCell(3); assertEquals(result, evaluator.evaluateInCell(d3).getNumericCellValue()); } + + /** + * Evaluation of cell references with column indexes greater than 255. See bugzilla 50096 + */ + public void testEvaluateColumnGreaterThan255() { + XSSFWorkbook wb = (XSSFWorkbook) _testDataProvider.openSampleWorkbook("50096.xlsx"); + XSSFFormulaEvaluator evaluator = wb.getCreationHelper().createFormulaEvaluator(); + + /** + * The first row simply contains the numbers 1 - 300. + * The second row simply refers to the cell value above in the first row by a simple formula. + */ + for (int i = 245; i < 265; i++) { + XSSFCell cell_noformula = wb.getSheetAt(0).getRow(0).getCell(i); + XSSFCell cell_formula = wb.getSheetAt(0).getRow(1).getCell(i); + + CellReference ref_noformula = new CellReference(cell_noformula.getRowIndex(), cell_noformula.getColumnIndex()); + CellReference ref_formula = new CellReference(cell_noformula.getRowIndex(), cell_noformula.getColumnIndex()); + String fmla = cell_formula.getCellFormula(); + // assure that the formula refers to the cell above. + // the check below is 'deep' and involves conversion of the shared formula: + // in the sample file a shared formula in GN1 is spanned in the range GN2:IY2, + assertEquals(ref_noformula.formatAsString(), fmla); + + CellValue cv_noformula = evaluator.evaluate(cell_noformula); + CellValue cv_formula = evaluator.evaluate(cell_formula); + assertEquals("Wrong evaluation result in " + ref_formula.formatAsString(), + cv_noformula.getNumberValue(), cv_formula.getNumberValue()); + } + + } } diff --git a/src/testcases/org/apache/poi/hssf/record/TestSharedFormulaRecord.java b/src/testcases/org/apache/poi/hssf/record/TestSharedFormulaRecord.java index 10a64813c..1547f9456 100644 --- a/src/testcases/org/apache/poi/hssf/record/TestSharedFormulaRecord.java +++ b/src/testcases/org/apache/poi/hssf/record/TestSharedFormulaRecord.java @@ -24,11 +24,13 @@ import junit.framework.TestCase; import org.apache.poi.hssf.HSSFTestDataSamples; import org.apache.poi.hssf.record.formula.Ptg; import org.apache.poi.hssf.record.formula.RefPtg; +import org.apache.poi.hssf.record.formula.SharedFormula; import org.apache.poi.hssf.usermodel.*; import org.apache.poi.ss.usermodel.CellValue; import org.apache.poi.ss.formula.FormulaParser; import org.apache.poi.ss.formula.FormulaRenderer; import org.apache.poi.ss.formula.FormulaType; +import org.apache.poi.ss.SpreadsheetVersion; import org.apache.poi.util.LittleEndianInput; /** @@ -74,7 +76,8 @@ public final class TestSharedFormulaRecord extends TestCase { int encodedLen = in.readUShort(); Ptg[] sharedFormula = Ptg.readTokens(encodedLen, in); - Ptg[] convertedFormula = SharedFormulaRecord.convertSharedFormulas(sharedFormula, 100, 200); + SharedFormula sf = new SharedFormula(SpreadsheetVersion.EXCEL97); + Ptg[] convertedFormula = sf.convertSharedFormulas(sharedFormula, 100, 200); RefPtg refPtg = (RefPtg) convertedFormula[1]; assertEquals("$C101", refPtg.toFormulaString()); @@ -102,32 +105,34 @@ public final class TestSharedFormulaRecord extends TestCase { HSSFEvaluationWorkbook fpb = HSSFEvaluationWorkbook.create(wb); Ptg[] sharedFormula, convertedFormula; + SharedFormula sf = new SharedFormula(SpreadsheetVersion.EXCEL97); + sharedFormula = FormulaParser.parse("A2", fpb, FormulaType.CELL, -1); - convertedFormula = SharedFormulaRecord.convertSharedFormulas(sharedFormula, 0, 0); + convertedFormula = sf.convertSharedFormulas(sharedFormula, 0, 0); confirmOperandClasses(sharedFormula, convertedFormula); //conversion relative to [0,0] should return the original formula assertEquals("A2", FormulaRenderer.toFormulaString(fpb, convertedFormula)); - convertedFormula = SharedFormulaRecord.convertSharedFormulas(sharedFormula, 1, 0); + convertedFormula = sf.convertSharedFormulas(sharedFormula, 1, 0); confirmOperandClasses(sharedFormula, convertedFormula); //one row down assertEquals("A3", FormulaRenderer.toFormulaString(fpb, convertedFormula)); - convertedFormula = SharedFormulaRecord.convertSharedFormulas(sharedFormula, 1, 1); + convertedFormula = sf.convertSharedFormulas(sharedFormula, 1, 1); confirmOperandClasses(sharedFormula, convertedFormula); //one row down and one cell right assertEquals("B3", FormulaRenderer.toFormulaString(fpb, convertedFormula)); sharedFormula = FormulaParser.parse("SUM(A1:C1)", fpb, FormulaType.CELL, -1); - convertedFormula = SharedFormulaRecord.convertSharedFormulas(sharedFormula, 0, 0); + convertedFormula = sf.convertSharedFormulas(sharedFormula, 0, 0); confirmOperandClasses(sharedFormula, convertedFormula); assertEquals("SUM(A1:C1)", FormulaRenderer.toFormulaString(fpb, convertedFormula)); - convertedFormula = SharedFormulaRecord.convertSharedFormulas(sharedFormula, 1, 0); + convertedFormula = sf.convertSharedFormulas(sharedFormula, 1, 0); confirmOperandClasses(sharedFormula, convertedFormula); assertEquals("SUM(A2:C2)", FormulaRenderer.toFormulaString(fpb, convertedFormula)); - convertedFormula = SharedFormulaRecord.convertSharedFormulas(sharedFormula, 1, 1); + convertedFormula = sf.convertSharedFormulas(sharedFormula, 1, 1); confirmOperandClasses(sharedFormula, convertedFormula); assertEquals("SUM(B2:D2)", FormulaRenderer.toFormulaString(fpb, convertedFormula)); } diff --git a/src/testcases/org/apache/poi/hssf/record/formula/TestReferencePtg.java b/src/testcases/org/apache/poi/hssf/record/formula/TestReferencePtg.java index dc8fd6b9b..4eef47b3e 100644 --- a/src/testcases/org/apache/poi/hssf/record/formula/TestReferencePtg.java +++ b/src/testcases/org/apache/poi/hssf/record/formula/TestReferencePtg.java @@ -103,5 +103,20 @@ public final class TestReferencePtg extends TestCase { } assertTrue(Arrays.equals(tRefN_data, outData)); } + + /** + * test that RefPtgBase can handle references with column index greater than 255, + * see Bugzilla 50096 + */ + public void testColumnGreater255() { + RefPtgBase ptg; + ptg = new RefPtg("IW1"); + assertEquals(256, ptg.getColumn()); + assertEquals("IW1", ptg.formatReferenceAsString()); + + ptg = new RefPtg("JA1"); + assertEquals(260, ptg.getColumn()); + assertEquals("JA1", ptg.formatReferenceAsString()); + } } diff --git a/test-data/spreadsheet/50096.xlsx b/test-data/spreadsheet/50096.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..b88784282f9a23b230e535c74408749d5a83b77d GIT binary patch literal 12391 zcmeHtby!v1*6*fMQV;=232CH5kdkgB1f)Y^Q(HQuH-dmjN+TuR-O?@HA_yo*iHO98 zyB3~vd|zMBeZTwM`+R?V_iP^K{LL|cW6ZV37;~<<*P3dID5zH<=nzZ@1VRhpa+3

SL@`j5* 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 literal 0 HcmV?d00001