From 96386e2e0ada2574c4d4ff697579b45e42fe6026 Mon Sep 17 00:00:00 2001 From: Nick Burch Date: Fri, 25 Jul 2014 13:59:07 +0000 Subject: [PATCH] Areas can have multi-sheet references too, so add FormulaParser support to these as well git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1613437 13f79535-47bb-0310-9956-ffa450edef68 --- .../apache/poi/ss/formula/ptg/Area3DPxg.java | 40 ++++++++++--- .../apache/poi/ss/formula/ptg/Ref3DPxg.java | 4 +- .../usermodel/XSSFEvaluationWorkbook.java | 6 +- .../usermodel/TestXSSFFormulaEvaluation.java | 16 ++--- .../xssf/usermodel/TestXSSFFormulaParser.java | 56 +++++++++++++++++- .../poi/hssf/model/TestFormulaParser.java | 21 +++++++ .../spreadsheet/55906-MultiSheetRefs.xls | Bin 23552 -> 24064 bytes .../spreadsheet/55906-MultiSheetRefs.xlsx | Bin 9420 -> 9494 bytes 8 files changed, 118 insertions(+), 25 deletions(-) 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 9fce295ac4..e27ccdd7a1 100644 --- a/src/java/org/apache/poi/ss/formula/ptg/Area3DPxg.java +++ b/src/java/org/apache/poi/ss/formula/ptg/Area3DPxg.java @@ -17,7 +17,9 @@ package org.apache.poi.ss.formula.ptg; +import org.apache.poi.ss.formula.SheetIdentifier; import org.apache.poi.ss.formula.SheetNameFormatter; +import org.apache.poi.ss.formula.SheetRangeIdentifier; import org.apache.poi.ss.util.AreaReference; import org.apache.poi.util.LittleEndianOutput; @@ -31,21 +33,27 @@ import org.apache.poi.util.LittleEndianOutput; */ public final class Area3DPxg extends AreaPtgBase implements Pxg { private int externalWorkbookNumber = -1; - private String sheetName; + private String firstSheetName; + private String lastSheetName; - public Area3DPxg(int externalWorkbookNumber, String sheetName, String arearef) { + public Area3DPxg(int externalWorkbookNumber, SheetIdentifier sheetName, String arearef) { this(externalWorkbookNumber, sheetName, new AreaReference(arearef)); } - public Area3DPxg(int externalWorkbookNumber, String sheetName, AreaReference arearef) { + public Area3DPxg(int externalWorkbookNumber, SheetIdentifier sheetName, AreaReference arearef) { super(arearef); this.externalWorkbookNumber = externalWorkbookNumber; - this.sheetName = sheetName; + this.firstSheetName = sheetName.getSheetIdentifier().getName(); + if (sheetName instanceof SheetRangeIdentifier) { + this.lastSheetName = ((SheetRangeIdentifier)sheetName).getLastSheetIdentifier().getName(); + } else { + this.lastSheetName = null; + } } - public Area3DPxg(String sheetName, String arearef) { + public Area3DPxg(SheetIdentifier sheetName, String arearef) { this(sheetName, new AreaReference(arearef)); } - public Area3DPxg(String sheetName, AreaReference arearef) { + public Area3DPxg(SheetIdentifier sheetName, AreaReference arearef) { this(-1, sheetName, arearef); } @@ -60,6 +68,10 @@ public final class Area3DPxg extends AreaPtgBase implements Pxg { sb.append("] "); } sb.append("sheet=").append(getSheetName()); + if (lastSheetName != null) { + sb.append(" : "); + sb.append("sheet=").append(lastSheetName); + } sb.append(" ! "); sb.append(formatReferenceAsString()); sb.append("]"); @@ -70,11 +82,17 @@ public final class Area3DPxg extends AreaPtgBase implements Pxg { return externalWorkbookNumber; } public String getSheetName() { - return sheetName; + return firstSheetName; + } + public String getLastSheetName() { + return lastSheetName; } public void setSheetName(String sheetName) { - this.sheetName = sheetName; + this.firstSheetName = sheetName; + } + public void setLastSheetName(String sheetName) { + this.lastSheetName = sheetName; } public String format2DRefAsString() { @@ -88,7 +106,11 @@ public final class Area3DPxg extends AreaPtgBase implements Pxg { sb.append(externalWorkbookNumber); sb.append(']'); } - SheetNameFormatter.appendFormat(sb, sheetName); + SheetNameFormatter.appendFormat(sb, firstSheetName); + if (lastSheetName != null) { + sb.append(':'); + SheetNameFormatter.appendFormat(sb, lastSheetName); + } sb.append('!'); sb.append(formatReferenceAsString()); return sb.toString(); diff --git a/src/java/org/apache/poi/ss/formula/ptg/Ref3DPxg.java b/src/java/org/apache/poi/ss/formula/ptg/Ref3DPxg.java index ce007d0415..41039cd927 100644 --- a/src/java/org/apache/poi/ss/formula/ptg/Ref3DPxg.java +++ b/src/java/org/apache/poi/ss/formula/ptg/Ref3DPxg.java @@ -81,14 +81,14 @@ public final class Ref3DPxg extends RefPtgBase implements Pxg { public int getExternalWorkbookNumber() { return externalWorkbookNumber; } - public String getSheetName() { // TODO Rename to getFirstSheetName + public String getSheetName() { return firstSheetName; } public String getLastSheetName() { return lastSheetName; } - public void setSheetName(String sheetName) { // TODO Rename to setFirstSheetName + public void setSheetName(String sheetName) { this.firstSheetName = sheetName; } public void setLastSheetName(String sheetName) { 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 5c3773d1cb..d51d9a6a6d 100644 --- a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFEvaluationWorkbook.java +++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFEvaluationWorkbook.java @@ -200,13 +200,11 @@ public final class XSSFEvaluationWorkbook implements FormulaRenderingWorkbook, E } } public Ptg get3DReferencePtg(AreaReference area, SheetIdentifier sheet) { - String sheetName = sheet._sheetIdentifier.getName(); - if (sheet._bookName != null) { int bookIndex = resolveBookIndex(sheet._bookName); - return new Area3DPxg(bookIndex, sheetName, area); + return new Area3DPxg(bookIndex, sheet, area); } else { - return new Area3DPxg(sheetName, area); + return new Area3DPxg(sheet, area); } } 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 657739f998..4e92fda697 100644 --- a/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFFormulaEvaluation.java +++ b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFFormulaEvaluation.java @@ -201,46 +201,46 @@ public final class TestXSSFFormulaEvaluation extends BaseTestFormulaEvaluator { Cell sumF = s1.getRow(2).getCell(0); assertNotNull(sumF); assertEquals("SUM(Sheet1:Sheet3!A1)", sumF.getCellFormula()); - assertEquals("66", evaluator.evaluate(sumF).formatAsString()); + assertEquals("66.0", evaluator.evaluate(sumF).formatAsString()); // Various Stats formulas on numbers Cell avgF = s1.getRow(2).getCell(1); assertNotNull(avgF); assertEquals("AVERAGE(Sheet1:Sheet3!A1)", avgF.getCellFormula()); - assertEquals("22", evaluator.evaluate(avgF).formatAsString()); + assertEquals("22.0", evaluator.evaluate(avgF).formatAsString()); Cell minF = s1.getRow(3).getCell(1); assertNotNull(minF); assertEquals("MIX(Sheet1:Sheet3!A$1)", minF.getCellFormula()); - assertEquals("11", evaluator.evaluate(minF).formatAsString()); + assertEquals("11.0", evaluator.evaluate(minF).formatAsString()); Cell maxF = s1.getRow(4).getCell(1); assertNotNull(maxF); assertEquals("MAX(Sheet1:Sheet3!A$1)", maxF.getCellFormula()); - assertEquals("33", evaluator.evaluate(maxF).formatAsString()); + assertEquals("33.0", evaluator.evaluate(maxF).formatAsString()); Cell countF = s1.getRow(5).getCell(1); assertNotNull(countF); assertEquals("COUNT(Sheet1:Sheet3!A$1)", countF.getCellFormula()); - assertEquals("3", evaluator.evaluate(countF).formatAsString()); + assertEquals("3.0", evaluator.evaluate(countF).formatAsString()); // Various CountAs on Strings Cell countA_1F = s1.getRow(2).getCell(2); assertNotNull(countA_1F); assertEquals("COUNTA(Sheet1:Sheet3!C1)", countA_1F.getCellFormula()); - assertEquals("3", evaluator.evaluate(countA_1F).formatAsString()); + assertEquals("3.0", evaluator.evaluate(countA_1F).formatAsString()); Cell countA_2F = s1.getRow(2).getCell(3); assertNotNull(countA_2F); assertEquals("COUNTA(Sheet1:Sheet3!D1)", countA_2F.getCellFormula()); - assertEquals("0", evaluator.evaluate(countA_2F).formatAsString()); + assertEquals("0.0", evaluator.evaluate(countA_2F).formatAsString()); Cell countA_3F = s1.getRow(2).getCell(4); assertNotNull(countA_3F); assertEquals("COUNTA(Sheet1:Sheet3!E1)", countA_3F.getCellFormula()); - assertEquals("3", evaluator.evaluate(countA_3F).formatAsString()); + assertEquals("3.0", evaluator.evaluate(countA_3F).formatAsString()); } } } 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 65e269d546..c7c466cf65 100644 --- a/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFFormulaParser.java +++ b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFFormulaParser.java @@ -31,6 +31,7 @@ import org.apache.poi.ss.formula.FormulaParsingWorkbook; import org.apache.poi.ss.formula.FormulaRenderingWorkbook; import org.apache.poi.ss.formula.FormulaType; import org.apache.poi.ss.formula.WorkbookDependentFormula; +import org.apache.poi.ss.formula.ptg.Area3DPtg; import org.apache.poi.ss.formula.ptg.Area3DPxg; import org.apache.poi.ss.formula.ptg.AreaPtg; import org.apache.poi.ss.formula.ptg.AttrPtg; @@ -242,7 +243,9 @@ public final class TestXSSFFormulaParser { /** * A handful of functions (such as SUM, COUNTA, MIN) support * multi-sheet references (eg Sheet1:Sheet3!A1 = Cell A1 from - * Sheets 1 through Sheet 3). + * Sheets 1 through Sheet 3) and multi-sheet area references + * (eg Sheet1:Sheet3!A1:B2 = Cells A1 through B2 from Sheets + * 1 through Sheet 3). * This test, based on common test files for HSSF and XSSF, checks * that we can read and parse these kinds of references * (but not evaluate - that's elsewhere in the test suite) @@ -274,6 +277,24 @@ public final class TestXSSFFormulaParser { assertNotNull(maxF); assertEquals("MAX(Sheet1:Sheet3!A$1)", maxF.getCellFormula()); + + Cell sumFA = s1.getRow(2).getCell(7); + assertNotNull(sumFA); + assertEquals("SUM(Sheet1:Sheet3!A1:B2)", sumFA.getCellFormula()); + + Cell avgFA = s1.getRow(2).getCell(8); + assertNotNull(avgFA); + assertEquals("AVERAGE(Sheet1:Sheet3!A1:B2)", avgFA.getCellFormula()); + + Cell maxFA = s1.getRow(4).getCell(8); + assertNotNull(maxFA); + assertEquals("MAX(Sheet1:Sheet3!A$1:B$2)", maxFA.getCellFormula()); + + Cell countFA = s1.getRow(5).getCell(8); + assertNotNull(countFA); + assertEquals("COUNT(Sheet1:Sheet3!$A$1:$B$2)", countFA.getCellFormula()); + + // Create a formula parser FormulaParsingWorkbook fpb = null; if (wb instanceof HSSFWorkbook) @@ -324,10 +345,41 @@ public final class TestXSSFFormulaParser { assertEquals("MIN", toFormulaString(ptgs[1], fpb)); - // Check we can round-trip - try to set a new one to a new cell + // SUM to a range of cells over 3 workbooks + ptgs = parse(fpb, "SUM(Sheet1:Sheet3!A1:B2)"); + assertEquals(2, ptgs.length); + if (wb instanceof HSSFWorkbook) { + assertEquals(Area3DPtg.class, ptgs[0].getClass()); + } else { + assertEquals(Area3DPxg.class, ptgs[0].getClass()); + } + assertEquals("Sheet1:Sheet3!A1:B2", toFormulaString(ptgs[0], fpb)); + assertEquals(AttrPtg.class, ptgs[1].getClass()); + assertEquals("SUM", toFormulaString(ptgs[1], fpb)); + + + // MIN to a range of cells over 3 workbooks, absolute reference + ptgs = parse(fpb, "MIN(Sheet1:Sheet3!$A$1:$B$2)"); + assertEquals(2, ptgs.length); + if (wb instanceof HSSFWorkbook) { + assertEquals(Area3DPtg.class, ptgs[0].getClass()); + } else { + assertEquals(Area3DPxg.class, ptgs[0].getClass()); + } + assertEquals("Sheet1:Sheet3!$A$1:$B$2", toFormulaString(ptgs[0], fpb)); + assertEquals(FuncVarPtg.class, ptgs[1].getClass()); + assertEquals("MIN", toFormulaString(ptgs[1], fpb)); + + + // Check we can round-trip - try to set a new one to a new single cell Cell newF = s1.getRow(0).createCell(10, Cell.CELL_TYPE_FORMULA); newF.setCellFormula("SUM(Sheet2:Sheet3!A1)"); assertEquals("SUM(Sheet2:Sheet3!A1)", newF.getCellFormula()); + + // Check we can round-trip - try to set a new one to a cell range + 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()); } } private static String toFormulaString(Ptg ptg, FormulaParsingWorkbook wb) { diff --git a/src/testcases/org/apache/poi/hssf/model/TestFormulaParser.java b/src/testcases/org/apache/poi/hssf/model/TestFormulaParser.java index 4546de31ad..fc5dd837ab 100644 --- a/src/testcases/org/apache/poi/hssf/model/TestFormulaParser.java +++ b/src/testcases/org/apache/poi/hssf/model/TestFormulaParser.java @@ -465,6 +465,9 @@ public final class TestFormulaParser extends TestCase { HSSFCell cell = row.createCell(0); String formula = null; + + // References to a single cell: + // One sheet cell.setCellFormula("Cash_Flow!A1"); formula = cell.getCellFormula(); @@ -479,6 +482,24 @@ public final class TestFormulaParser extends TestCase { cell.setCellFormula("Cash_Flow:\'Test Sheet\'!A1"); formula = cell.getCellFormula(); assertEquals("Cash_Flow:\'Test Sheet\'!A1", formula); + + + // References to a range (area) of cells: + + // One sheet + cell.setCellFormula("Cash_Flow!A1:B2"); + formula = cell.getCellFormula(); + assertEquals("Cash_Flow!A1:B2", formula); + + // Then the other + cell.setCellFormula("\'Test Sheet\'!A1:B2"); + formula = cell.getCellFormula(); + assertEquals("\'Test Sheet\'!A1:B2", formula); + + // Now both + cell.setCellFormula("Cash_Flow:\'Test Sheet\'!A1:B2"); + formula = cell.getCellFormula(); + assertEquals("Cash_Flow:\'Test Sheet\'!A1:B2", formula); } /** diff --git a/test-data/spreadsheet/55906-MultiSheetRefs.xls b/test-data/spreadsheet/55906-MultiSheetRefs.xls index 5e471ccc8b25c3ce71050e57d3b50a529f589e54..565330810ad9d0b496a4e2f62a85ffd148a228fa 100644 GIT binary patch delta 712 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 T00000&Xc1gBL=o100000dWL$? delta 1289 zcmV+k1@`)uO3X>HY84K&_Xm>(t5WRP%|AA_HXb(YP z$4|~rtPDrfYd%JJ73a_4u+}VKALY5Kn zU5_|Wa+!*98Ik$@_1O>NXv4~s<+6l`>_C&N`17Z5E7hZ30T>4vN*$4vF;zgR<|`U?dBDIA-IxSX2wN2 z+9dkA=1Y1>ktq&SffdxYI|{OhBz`dN6B>uspMM7dTYa#03|q_~2W}w6HW8<`e<9^# zeQ<)mJY>}NK+VXw5gQmno=0SGfgSv7HE@Xnv{g7<@71nt!+t9#g^aCpb0@byK`d7W zWqW8q?L(06MxZ%10EOL7uT6Wz42wf0w+=QFv(y!6V+rqKOJ3SCsm;@N!qZC2lFKd_(fm&E@b(yUIe-*07X z)`JF;JdE#>oAF6lr*jM&Hn8EZ`R#r30yS*Ko=dV?qc=dK7WC4iH$dYS^sIDb-uVa2 z8=PD7rjy$j^(0?gnSEPXX_dOZ(8d06(&uN|RiF*1U(dk~|}CCIP3ybMXRaQkwmyp5FH1 zjsF3Y(FPQ=D;Qt{1!5#-$wae|8z=#P!ypvJ-%b1u4eu@ev*;vkmrS!KXH1;Y_(tGv zBP)~uv)i|qwp;BoAAIDV!;f=r$Y%#x1>az-s8xZIC`JJ&R&${?1$tjC!YK-@ql(k2 zR#2cLSTrkdZ*ryys|~!GT7z){Y~V@C5~d5ZbxsqEE!%>mHu4VT#dU2Yb^d67Hds@( zqZ`0!9N%FHj&kZKZh@g5i0FjmY(VP0sX72>7%GsUoW)6$;9su;Bki9TI%VXP!X35m zbc#D>bJmN4`(VXjY?>x&vMz?tpWx5s^IKOn6sp}7161am5oUn8+LSMX?SlEX}_Ug&qt%@S#Xw;jDaQk58)wD$_WbgzKlfZERf6TMEK35CzbG!99fex2aD=nigAi1-gKcj!htsk_6G+ z8$pZYXJF2S%S_cgjV2HDK{?A{S(HG~3fC(;Ft}YWi#?DKx$U`j78$%SKvU0WmEc-* zk}FG6gAEL1j7Z~w(jX)n9tt<2P08JP!(&?gK*Q*f_dy~?qv@(FchsZ;s4GD{gLVf* zr5k|g&!j)zetCjT2IqAKryRN*+8mC5K&Rj8HvqG+A>siFwD$*+Qvv`0y#Cu004MwFLQKxY-Mwku_6>3VkBnCL;(N*WdZ;I5dZ)H0000000000002dk za3nqfz>}{eBO522q{^)T001BX000mG000000000000000k(2EtBL-|C00000r29fg -- 2.39.5