You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

TestXSSFFormulaParser.java 34KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803
  1. /* ====================================================================
  2. Licensed to the Apache Software Foundation (ASF) under one or more
  3. contributor license agreements. See the NOTICE file distributed with
  4. this work for additional information regarding copyright ownership.
  5. The ASF licenses this file to You under the Apache License, Version 2.0
  6. (the "License"); you may not use this file except in compliance with
  7. the License. You may obtain a copy of the License at
  8. http://www.apache.org/licenses/LICENSE-2.0
  9. Unless required by applicable law or agreed to in writing, software
  10. distributed under the License is distributed on an "AS IS" BASIS,
  11. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. See the License for the specific language governing permissions and
  13. limitations under the License.
  14. ==================================================================== */
  15. package org.apache.poi.xssf.usermodel;
  16. import static org.junit.jupiter.api.Assertions.assertEquals;
  17. import static org.junit.jupiter.api.Assertions.assertNotNull;
  18. import static org.junit.jupiter.api.Assertions.assertNull;
  19. import static org.junit.jupiter.api.Assertions.assertThrows;
  20. import static org.junit.jupiter.api.Assertions.assertTrue;
  21. import java.io.IOException;
  22. import java.util.Arrays;
  23. import org.apache.poi.hssf.HSSFTestDataSamples;
  24. import org.apache.poi.hssf.usermodel.HSSFEvaluationWorkbook;
  25. import org.apache.poi.hssf.usermodel.HSSFWorkbook;
  26. import org.apache.poi.ss.formula.FormulaParseException;
  27. import org.apache.poi.ss.formula.FormulaParser;
  28. import org.apache.poi.ss.formula.FormulaParsingWorkbook;
  29. import org.apache.poi.ss.formula.FormulaRenderingWorkbook;
  30. import org.apache.poi.ss.formula.FormulaType;
  31. import org.apache.poi.ss.formula.WorkbookDependentFormula;
  32. import org.apache.poi.ss.formula.ptg.Area3DPtg;
  33. import org.apache.poi.ss.formula.ptg.Area3DPxg;
  34. import org.apache.poi.ss.formula.ptg.AreaPtg;
  35. import org.apache.poi.ss.formula.ptg.AttrPtg;
  36. import org.apache.poi.ss.formula.ptg.ErrPtg;
  37. import org.apache.poi.ss.formula.ptg.FuncPtg;
  38. import org.apache.poi.ss.formula.ptg.FuncVarPtg;
  39. import org.apache.poi.ss.formula.ptg.IntPtg;
  40. import org.apache.poi.ss.formula.ptg.IntersectionPtg;
  41. import org.apache.poi.ss.formula.ptg.MemAreaPtg;
  42. import org.apache.poi.ss.formula.ptg.MemFuncPtg;
  43. import org.apache.poi.ss.formula.ptg.NamePtg;
  44. import org.apache.poi.ss.formula.ptg.NameXPxg;
  45. import org.apache.poi.ss.formula.ptg.ParenthesisPtg;
  46. import org.apache.poi.ss.formula.ptg.Ptg;
  47. import org.apache.poi.ss.formula.ptg.Ref3DPtg;
  48. import org.apache.poi.ss.formula.ptg.Ref3DPxg;
  49. import org.apache.poi.ss.formula.ptg.RefPtg;
  50. import org.apache.poi.ss.formula.ptg.StringPtg;
  51. import org.apache.poi.ss.usermodel.Cell;
  52. import org.apache.poi.ss.usermodel.CellType;
  53. import org.apache.poi.ss.usermodel.FormulaEvaluator;
  54. import org.apache.poi.ss.usermodel.Row;
  55. import org.apache.poi.ss.usermodel.Sheet;
  56. import org.apache.poi.ss.usermodel.Workbook;
  57. import org.apache.poi.ss.util.CellReference;
  58. import org.apache.poi.xssf.XSSFTestDataSamples;
  59. import org.junit.jupiter.api.Test;
  60. public final class TestXSSFFormulaParser {
  61. private static Ptg[] parse(FormulaParsingWorkbook fpb, String fmla) {
  62. return FormulaParser.parse(fmla, fpb, FormulaType.CELL, -1);
  63. }
  64. private static Ptg[] parse(FormulaParsingWorkbook fpb, String fmla, int rowIndex) {
  65. return FormulaParser.parse(fmla, fpb, FormulaType.CELL, -1, rowIndex);
  66. }
  67. @Test
  68. void basicParsing() throws IOException {
  69. XSSFWorkbook wb = new XSSFWorkbook();
  70. XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(wb);
  71. Ptg[] ptgs;
  72. ptgs = parse(fpb, "ABC10");
  73. assertEquals(1, ptgs.length);
  74. assertTrue(ptgs[0] instanceof RefPtg, "Had " + Arrays.toString(ptgs));
  75. ptgs = parse(fpb, "A500000");
  76. assertEquals(1, ptgs.length);
  77. assertTrue(ptgs[0] instanceof RefPtg, "Had " + Arrays.toString(ptgs));
  78. ptgs = parse(fpb, "ABC500000");
  79. assertEquals(1, ptgs.length);
  80. assertTrue(ptgs[0] instanceof RefPtg, "Had " + Arrays.toString(ptgs));
  81. //highest allowed rows and column (XFD and 0x100000)
  82. ptgs = parse(fpb, "XFD1048576");
  83. assertEquals(1, ptgs.length);
  84. assertTrue(ptgs[0] instanceof RefPtg, "Had " + Arrays.toString(ptgs));
  85. //column greater than XFD
  86. FormulaParseException e;
  87. e = assertThrows(FormulaParseException.class, () -> parse(fpb, "XFE10"));
  88. assertEquals("Specified named range 'XFE10' does not exist in the current workbook.", e.getMessage());
  89. //row greater than 0x100000
  90. e = assertThrows(FormulaParseException.class, () -> parse(fpb, "XFD1048577"));
  91. assertEquals("Specified named range 'XFD1048577' does not exist in the current workbook.", e.getMessage());
  92. // Formula referencing one cell
  93. ptgs = parse(fpb, "ISEVEN(A1)");
  94. assertEquals(3, ptgs.length);
  95. assertEquals(NameXPxg.class, ptgs[0].getClass());
  96. assertEquals(RefPtg.class, ptgs[1].getClass());
  97. assertEquals(FuncVarPtg.class, ptgs[2].getClass());
  98. assertEquals("ISEVEN", ptgs[0].toFormulaString());
  99. assertEquals("A1", ptgs[1].toFormulaString());
  100. assertEquals("#external#", ptgs[2].toFormulaString());
  101. // Formula referencing an area
  102. ptgs = parse(fpb, "SUM(A1:B3)");
  103. assertEquals(2, ptgs.length);
  104. assertEquals(AreaPtg.class, ptgs[0].getClass());
  105. assertEquals(AttrPtg.class, ptgs[1].getClass());
  106. assertEquals("A1:B3", ptgs[0].toFormulaString());
  107. assertEquals("SUM", ptgs[1].toFormulaString());
  108. // Formula referencing one cell in a different sheet
  109. ptgs = parse(fpb, "SUM(Sheet1!A1)");
  110. assertEquals(2, ptgs.length);
  111. assertEquals(Ref3DPxg.class, ptgs[0].getClass());
  112. assertEquals(AttrPtg.class, ptgs[1].getClass());
  113. assertEquals("Sheet1!A1", ptgs[0].toFormulaString());
  114. assertEquals("SUM", ptgs[1].toFormulaString());
  115. // Formula referencing an area in a different sheet
  116. ptgs = parse(fpb, "SUM(Sheet1!A1:B3)");
  117. assertEquals(2, ptgs.length);
  118. assertEquals(Area3DPxg.class,ptgs[0].getClass());
  119. assertEquals(AttrPtg.class, ptgs[1].getClass());
  120. assertEquals("Sheet1!A1:B3", ptgs[0].toFormulaString());
  121. assertEquals("SUM", ptgs[1].toFormulaString());
  122. wb.close();
  123. }
  124. @Test
  125. void builtInFormulas() throws IOException {
  126. XSSFWorkbook wb = new XSSFWorkbook();
  127. XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(wb);
  128. Ptg[] ptgs;
  129. ptgs = parse(fpb, "LOG10");
  130. assertEquals(1, ptgs.length);
  131. assertTrue(ptgs[0] instanceof RefPtg);
  132. ptgs = parse(fpb, "LOG10(100)");
  133. assertEquals(2, ptgs.length);
  134. assertTrue(ptgs[0] instanceof IntPtg);
  135. assertTrue(ptgs[1] instanceof FuncPtg);
  136. wb.close();
  137. }
  138. @Test
  139. void formulaReferencesSameWorkbook() throws IOException {
  140. // Use a test file with "other workbook" style references
  141. // to itself
  142. XSSFWorkbook wb = XSSFTestDataSamples.openSampleWorkbook("56737.xlsx");
  143. XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(wb);
  144. Ptg[] ptgs;
  145. // Reference to a named range in our own workbook, as if it
  146. // were defined in a different workbook
  147. ptgs = parse(fpb, "[0]!NR_Global_B2");
  148. assertEquals(1, ptgs.length);
  149. assertEquals(NameXPxg.class, ptgs[0].getClass());
  150. assertEquals(0, ((NameXPxg)ptgs[0]).getExternalWorkbookNumber());
  151. assertNull(((NameXPxg) ptgs[0]).getSheetName());
  152. assertEquals("NR_Global_B2",((NameXPxg)ptgs[0]).getNameName());
  153. assertEquals("[0]!NR_Global_B2", ptgs[0].toFormulaString());
  154. wb.close();
  155. }
  156. @Test
  157. void formulaReferencesOtherSheets() throws IOException {
  158. // Use a test file with the named ranges in place
  159. XSSFWorkbook wb = XSSFTestDataSamples.openSampleWorkbook("56737.xlsx");
  160. XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(wb);
  161. Ptg[] ptgs;
  162. // Reference to a single cell in a different sheet
  163. ptgs = parse(fpb, "Uses!A1");
  164. assertEquals(1, ptgs.length);
  165. assertEquals(Ref3DPxg.class, ptgs[0].getClass());
  166. assertEquals(-1, ((Ref3DPxg)ptgs[0]).getExternalWorkbookNumber());
  167. assertEquals("A1", ((Ref3DPxg)ptgs[0]).format2DRefAsString());
  168. assertEquals("Uses!A1", ptgs[0].toFormulaString());
  169. // Reference to a single cell in a different sheet, which needs quoting
  170. ptgs = parse(fpb, "'Testing 47100'!A1");
  171. assertEquals(1, ptgs.length);
  172. assertEquals(Ref3DPxg.class, ptgs[0].getClass());
  173. assertEquals(-1, ((Ref3DPxg)ptgs[0]).getExternalWorkbookNumber());
  174. assertEquals("Testing 47100", ((Ref3DPxg)ptgs[0]).getSheetName());
  175. assertEquals("A1", ((Ref3DPxg)ptgs[0]).format2DRefAsString());
  176. assertEquals("'Testing 47100'!A1", ptgs[0].toFormulaString());
  177. // Reference to a sheet scoped named range from another sheet
  178. ptgs = parse(fpb, "Defines!NR_To_A1");
  179. assertEquals(1, ptgs.length);
  180. assertEquals(NameXPxg.class, ptgs[0].getClass());
  181. assertEquals(-1, ((NameXPxg)ptgs[0]).getExternalWorkbookNumber());
  182. assertEquals("Defines", ((NameXPxg)ptgs[0]).getSheetName());
  183. assertEquals("NR_To_A1",((NameXPxg)ptgs[0]).getNameName());
  184. assertEquals("Defines!NR_To_A1", ptgs[0].toFormulaString());
  185. // Reference to a workbook scoped named range
  186. ptgs = parse(fpb, "NR_Global_B2");
  187. assertEquals(1, ptgs.length);
  188. assertEquals(NamePtg.class, ptgs[0].getClass());
  189. assertEquals("NR_Global_B2",((NamePtg)ptgs[0]).toFormulaString(fpb));
  190. wb.close();
  191. }
  192. @Test
  193. void formulaReferencesOtherWorkbook() throws IOException {
  194. // Use a test file with the external linked table in place
  195. XSSFWorkbook wb = XSSFTestDataSamples.openSampleWorkbook("ref-56737.xlsx");
  196. XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(wb);
  197. Ptg[] ptgs;
  198. // Reference to a single cell in a different workbook
  199. ptgs = parse(fpb, "[1]Uses!$A$1");
  200. assertEquals(1, ptgs.length);
  201. assertEquals(Ref3DPxg.class, ptgs[0].getClass());
  202. assertEquals(1, ((Ref3DPxg)ptgs[0]).getExternalWorkbookNumber());
  203. assertEquals("Uses",((Ref3DPxg)ptgs[0]).getSheetName());
  204. assertEquals("$A$1",((Ref3DPxg)ptgs[0]).format2DRefAsString());
  205. assertEquals("[1]Uses!$A$1", ptgs[0].toFormulaString());
  206. // Reference to a sheet-scoped named range in a different workbook
  207. ptgs = parse(fpb, "[1]Defines!NR_To_A1");
  208. assertEquals(1, ptgs.length);
  209. assertEquals(NameXPxg.class, ptgs[0].getClass());
  210. assertEquals(1, ((NameXPxg)ptgs[0]).getExternalWorkbookNumber());
  211. assertEquals("Defines", ((NameXPxg)ptgs[0]).getSheetName());
  212. assertEquals("NR_To_A1",((NameXPxg)ptgs[0]).getNameName());
  213. assertEquals("[1]Defines!NR_To_A1", ptgs[0].toFormulaString());
  214. // Reference to a global named range in a different workbook
  215. ptgs = parse(fpb, "[1]!NR_Global_B2");
  216. assertEquals(1, ptgs.length);
  217. assertEquals(NameXPxg.class, ptgs[0].getClass());
  218. assertEquals(1, ((NameXPxg)ptgs[0]).getExternalWorkbookNumber());
  219. assertNull(((NameXPxg) ptgs[0]).getSheetName());
  220. assertEquals("NR_Global_B2",((NameXPxg)ptgs[0]).getNameName());
  221. assertEquals("[1]!NR_Global_B2", ptgs[0].toFormulaString());
  222. wb.close();
  223. }
  224. /**
  225. * A handful of functions (such as SUM, COUNTA, MIN) support
  226. * multi-sheet references (eg Sheet1:Sheet3!A1 = Cell A1 from
  227. * Sheets 1 through Sheet 3) and multi-sheet area references
  228. * (eg Sheet1:Sheet3!A1:B2 = Cells A1 through B2 from Sheets
  229. * 1 through Sheet 3).
  230. * This test, based on common test files for HSSF and XSSF, checks
  231. * that we can read and parse these kinds of references
  232. * (but not evaluate - that's elsewhere in the test suite)
  233. */
  234. @Test
  235. void multiSheetReferencesHSSFandXSSF() throws IOException {
  236. Workbook[] wbs = new Workbook[] {
  237. HSSFTestDataSamples.openSampleWorkbook("55906-MultiSheetRefs.xls"),
  238. XSSFTestDataSamples.openSampleWorkbook("55906-MultiSheetRefs.xlsx")
  239. };
  240. for (Workbook wb : wbs) {
  241. Sheet s1 = wb.getSheetAt(0);
  242. Ptg[] ptgs;
  243. // Check the contents
  244. Cell sumF = s1.getRow(2).getCell(0);
  245. assertNotNull(sumF);
  246. assertEquals("SUM(Sheet1:Sheet3!A1)", sumF.getCellFormula());
  247. Cell avgF = s1.getRow(2).getCell(1);
  248. assertNotNull(avgF);
  249. assertEquals("AVERAGE(Sheet1:Sheet3!A1)", avgF.getCellFormula());
  250. Cell countAF = s1.getRow(2).getCell(2);
  251. assertNotNull(countAF);
  252. assertEquals("COUNTA(Sheet1:Sheet3!C1)", countAF.getCellFormula());
  253. Cell maxF = s1.getRow(4).getCell(1);
  254. assertNotNull(maxF);
  255. assertEquals("MAX(Sheet1:Sheet3!A$1)", maxF.getCellFormula());
  256. Cell sumFA = s1.getRow(2).getCell(7);
  257. assertNotNull(sumFA);
  258. assertEquals("SUM(Sheet1:Sheet3!A1:B2)", sumFA.getCellFormula());
  259. Cell avgFA = s1.getRow(2).getCell(8);
  260. assertNotNull(avgFA);
  261. assertEquals("AVERAGE(Sheet1:Sheet3!A1:B2)", avgFA.getCellFormula());
  262. Cell maxFA = s1.getRow(4).getCell(8);
  263. assertNotNull(maxFA);
  264. assertEquals("MAX(Sheet1:Sheet3!A$1:B$2)", maxFA.getCellFormula());
  265. Cell countFA = s1.getRow(5).getCell(8);
  266. assertNotNull(countFA);
  267. assertEquals("COUNT(Sheet1:Sheet3!$A$1:$B$2)", countFA.getCellFormula());
  268. // Create a formula parser
  269. final FormulaParsingWorkbook fpb;
  270. if (wb instanceof HSSFWorkbook)
  271. fpb = HSSFEvaluationWorkbook.create((HSSFWorkbook)wb);
  272. else
  273. fpb = XSSFEvaluationWorkbook.create((XSSFWorkbook)wb);
  274. // Check things parse as expected:
  275. // SUM to one cell over 3 workbooks, relative reference
  276. ptgs = parse(fpb, "SUM(Sheet1:Sheet3!A1)");
  277. assertEquals(2, ptgs.length);
  278. if (wb instanceof HSSFWorkbook) {
  279. assertEquals(Ref3DPtg.class, ptgs[0].getClass());
  280. } else {
  281. assertEquals(Ref3DPxg.class, ptgs[0].getClass());
  282. }
  283. assertEquals("Sheet1:Sheet3!A1", toFormulaString(ptgs[0], fpb));
  284. assertEquals(AttrPtg.class, ptgs[1].getClass());
  285. assertEquals("SUM", toFormulaString(ptgs[1], fpb));
  286. // MAX to one cell over 3 workbooks, absolute row reference
  287. ptgs = parse(fpb, "MAX(Sheet1:Sheet3!A$1)");
  288. assertEquals(2, ptgs.length);
  289. if (wb instanceof HSSFWorkbook) {
  290. assertEquals(Ref3DPtg.class, ptgs[0].getClass());
  291. } else {
  292. assertEquals(Ref3DPxg.class, ptgs[0].getClass());
  293. }
  294. assertEquals("Sheet1:Sheet3!A$1", toFormulaString(ptgs[0], fpb));
  295. assertEquals(FuncVarPtg.class, ptgs[1].getClass());
  296. assertEquals("MAX", toFormulaString(ptgs[1], fpb));
  297. // MIN to one cell over 3 workbooks, absolute reference
  298. ptgs = parse(fpb, "MIN(Sheet1:Sheet3!$A$1)");
  299. assertEquals(2, ptgs.length);
  300. if (wb instanceof HSSFWorkbook) {
  301. assertEquals(Ref3DPtg.class, ptgs[0].getClass());
  302. } else {
  303. assertEquals(Ref3DPxg.class, ptgs[0].getClass());
  304. }
  305. assertEquals("Sheet1:Sheet3!$A$1", toFormulaString(ptgs[0], fpb));
  306. assertEquals(FuncVarPtg.class, ptgs[1].getClass());
  307. assertEquals("MIN", toFormulaString(ptgs[1], fpb));
  308. // SUM to a range of cells over 3 workbooks
  309. ptgs = parse(fpb, "SUM(Sheet1:Sheet3!A1:B2)");
  310. assertEquals(2, ptgs.length);
  311. if (wb instanceof HSSFWorkbook) {
  312. assertEquals(Area3DPtg.class, ptgs[0].getClass());
  313. } else {
  314. assertEquals(Area3DPxg.class, ptgs[0].getClass());
  315. }
  316. assertEquals("Sheet1:Sheet3!A1:B2", toFormulaString(ptgs[0], fpb));
  317. assertEquals(AttrPtg.class, ptgs[1].getClass());
  318. assertEquals("SUM", toFormulaString(ptgs[1], fpb));
  319. // MIN to a range of cells over 3 workbooks, absolute reference
  320. ptgs = parse(fpb, "MIN(Sheet1:Sheet3!$A$1:$B$2)");
  321. assertEquals(2, ptgs.length);
  322. if (wb instanceof HSSFWorkbook) {
  323. assertEquals(Area3DPtg.class, ptgs[0].getClass());
  324. } else {
  325. assertEquals(Area3DPxg.class, ptgs[0].getClass());
  326. }
  327. assertEquals("Sheet1:Sheet3!$A$1:$B$2", toFormulaString(ptgs[0], fpb));
  328. assertEquals(FuncVarPtg.class, ptgs[1].getClass());
  329. assertEquals("MIN", toFormulaString(ptgs[1], fpb));
  330. // Check we can round-trip - try to set a new one to a new single cell
  331. Cell newF = s1.getRow(0).createCell(10, CellType.FORMULA);
  332. newF.setCellFormula("SUM(Sheet2:Sheet3!A1)");
  333. assertEquals("SUM(Sheet2:Sheet3!A1)", newF.getCellFormula());
  334. // Check we can round-trip - try to set a new one to a cell range
  335. newF = s1.getRow(0).createCell(11, CellType.FORMULA);
  336. newF.setCellFormula("MIN(Sheet1:Sheet2!A1:B2)");
  337. assertEquals("MIN(Sheet1:Sheet2!A1:B2)", newF.getCellFormula());
  338. wb.close();
  339. }
  340. }
  341. @Test
  342. void testQuotedSheetNamesReference() throws IOException {
  343. // quoted sheet names bug fix
  344. // see TestHSSFFormulaEvaluator equivalent which behaves a little differently
  345. try (XSSFWorkbook wb = new XSSFWorkbook()) {
  346. Sheet sheet1 = wb.createSheet("Sheet1");
  347. Sheet sheet2 = wb.createSheet("Sheet2");
  348. Sheet sheet3 = wb.createSheet("Sheet 3");
  349. Sheet sheet4 = wb.createSheet("Sheet4>");
  350. Row tempRow = sheet1.createRow(0);
  351. tempRow.createCell(0).setCellValue(1);
  352. tempRow.createCell(1).setCellValue(2);
  353. tempRow = sheet2.createRow(0);
  354. tempRow.createCell(0).setCellValue(3);
  355. tempRow.createCell(1).setCellValue(4);
  356. tempRow = sheet3.createRow(0);
  357. tempRow.createCell(0).setCellValue(5);
  358. tempRow.createCell(1).setCellValue(6);
  359. tempRow = sheet4.createRow(0);
  360. tempRow.createCell(0).setCellValue(5);
  361. tempRow.createCell(1).setCellValue(6);
  362. Cell cell = tempRow.createCell(2);
  363. // unquoted sheet names
  364. String formula = "SUM(Sheet1:Sheet2!A1:B1)";
  365. cell.setCellFormula(formula);
  366. String cellFormula = cell.getCellFormula();
  367. assertEquals(formula, cellFormula);
  368. // quoted sheet names with no space
  369. cell = tempRow.createCell(3);
  370. formula = "SUM('Sheet1:Sheet2'!A1:B1)";
  371. cell.setCellFormula(formula);
  372. cellFormula = cell.getCellFormula();
  373. assertEquals("SUM('Sheet1:Sheet2'!A1:B1)", cellFormula);
  374. // quoted sheet names with space
  375. cell = tempRow.createCell(4);
  376. formula = "SUM('Sheet1:Sheet 3'!A1:B1)";
  377. cell.setCellFormula(formula);
  378. cellFormula = cell.getCellFormula();
  379. assertEquals(formula, cellFormula);
  380. // quoted sheet names with special character
  381. cell = tempRow.createCell(5);
  382. formula = "SUM('Sheet1:Sheet4>'!A1:B1)";
  383. cell.setCellFormula(formula);
  384. cellFormula = cell.getCellFormula();
  385. assertEquals(formula, cellFormula);
  386. // quoted sheet names with special character #2
  387. // cell = tempRow.createCell(6);
  388. // formula = "SUM('Sheet 3:Sheet4>'!A1:B1)";
  389. // cell.setCellFormula(formula);
  390. // cellFormula = cell.getCellFormula();
  391. // assertEquals(formula, cellFormula);
  392. }
  393. }
  394. private static String toFormulaString(Ptg ptg, FormulaParsingWorkbook wb) {
  395. if (ptg instanceof WorkbookDependentFormula) {
  396. return ((WorkbookDependentFormula)ptg).toFormulaString((FormulaRenderingWorkbook)wb);
  397. }
  398. return ptg.toFormulaString();
  399. }
  400. @Test
  401. void test58648Single() throws IOException {
  402. try (XSSFWorkbook wb = new XSSFWorkbook()) {
  403. XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(wb);
  404. Ptg[] ptgs;
  405. ptgs = parse(fpb, "(ABC10 )");
  406. assertEquals(2, ptgs.length, "Had: " + Arrays.toString(ptgs));
  407. assertTrue(ptgs[0] instanceof RefPtg, "Had " + Arrays.toString(ptgs));
  408. assertTrue(ptgs[1] instanceof ParenthesisPtg, "Had " + Arrays.toString(ptgs));
  409. }
  410. }
  411. @Test
  412. void test58648Basic() throws IOException {
  413. XSSFWorkbook wb = new XSSFWorkbook();
  414. XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(wb);
  415. Ptg[] ptgs;
  416. // verify whitespaces in different places
  417. ptgs = parse(fpb, "(ABC10)");
  418. assertEquals(2, ptgs.length);
  419. assertTrue(ptgs[0] instanceof RefPtg);
  420. assertTrue(ptgs[1] instanceof ParenthesisPtg);
  421. ptgs = parse(fpb, "( ABC10)");
  422. assertEquals(2, ptgs.length);
  423. assertTrue(ptgs[0] instanceof RefPtg);
  424. assertTrue(ptgs[1] instanceof ParenthesisPtg);
  425. ptgs = parse(fpb, "(ABC10 )");
  426. assertEquals(2, ptgs.length);
  427. assertTrue(ptgs[0] instanceof RefPtg);
  428. assertTrue(ptgs[1] instanceof ParenthesisPtg);
  429. ptgs = parse(fpb, "((ABC10))");
  430. assertEquals(3, ptgs.length);
  431. assertTrue(ptgs[0] instanceof RefPtg);
  432. assertTrue(ptgs[1] instanceof ParenthesisPtg);
  433. assertTrue(ptgs[2] instanceof ParenthesisPtg);
  434. ptgs = parse(fpb, "((ABC10) )");
  435. assertEquals(3, ptgs.length);
  436. assertTrue(ptgs[0] instanceof RefPtg);
  437. assertTrue(ptgs[1] instanceof ParenthesisPtg);
  438. assertTrue(ptgs[2] instanceof ParenthesisPtg);
  439. ptgs = parse(fpb, "( (ABC10))");
  440. assertEquals(3, ptgs.length);
  441. assertTrue(ptgs[0] instanceof RefPtg);
  442. assertTrue(ptgs[1] instanceof ParenthesisPtg);
  443. assertTrue(ptgs[2] instanceof ParenthesisPtg);
  444. wb.close();
  445. }
  446. @Test
  447. void test58648FormulaParsing() throws IOException {
  448. Workbook wb = XSSFTestDataSamples.openSampleWorkbook("58648.xlsx");
  449. FormulaEvaluator evaluator = wb.getCreationHelper().createFormulaEvaluator();
  450. for (int i = 0; i < wb.getNumberOfSheets(); i++) {
  451. Sheet xsheet = wb.getSheetAt(i);
  452. for (Row row : xsheet) {
  453. for (Cell cell : row) {
  454. if (cell.getCellType() == CellType.FORMULA) {
  455. try {
  456. evaluator.evaluateFormulaCell(cell);
  457. } catch (Exception e) {
  458. CellReference cellRef = new CellReference(cell.getRowIndex(), cell.getColumnIndex());
  459. throw new RuntimeException("error at: " + cellRef, e);
  460. }
  461. }
  462. }
  463. }
  464. }
  465. Sheet sheet = wb.getSheet("my-sheet");
  466. Cell cell = sheet.getRow(1).getCell(4);
  467. assertEquals(CellType.FORMULA, cell.getCellType(),
  468. "Had: " + cell);
  469. assertEquals(CellType.NUMERIC, cell.getCachedFormulaResultType(),
  470. "Had: " + cell + " and " + cell.getCachedFormulaResultType());
  471. assertEquals(5d, cell.getNumericCellValue(), 0d);
  472. wb.close();
  473. }
  474. @Test
  475. void testWhitespaceInFormula() throws IOException {
  476. XSSFWorkbook wb = new XSSFWorkbook();
  477. XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(wb);
  478. Ptg[] ptgs;
  479. // verify whitespaces in different places
  480. ptgs = parse(fpb, "INTERCEPT(A2:A5, B2:B5)");
  481. assertEquals(3, ptgs.length);
  482. assertTrue(ptgs[0] instanceof AreaPtg);
  483. assertTrue(ptgs[1] instanceof AreaPtg);
  484. assertTrue(ptgs[2] instanceof FuncPtg);
  485. ptgs = parse(fpb, " INTERCEPT ( \t \r A2 : \nA5 , B2 : B5 ) \t");
  486. assertEquals(3, ptgs.length);
  487. assertTrue(ptgs[0] instanceof AreaPtg);
  488. assertTrue(ptgs[1] instanceof AreaPtg);
  489. assertTrue(ptgs[2] instanceof FuncPtg);
  490. ptgs = parse(fpb, "(VLOOKUP(\"item1\", A2:B3, 2, FALSE) - VLOOKUP(\"item2\", A2:B3, 2, FALSE) )");
  491. assertEquals(12, ptgs.length);
  492. assertTrue(ptgs[0] instanceof StringPtg);
  493. assertTrue(ptgs[1] instanceof AreaPtg);
  494. assertTrue(ptgs[2] instanceof IntPtg);
  495. ptgs = parse(fpb, "A1:B1 B1:B2");
  496. assertEquals(4, ptgs.length);
  497. assertTrue(ptgs[0] instanceof MemAreaPtg);
  498. assertTrue(ptgs[1] instanceof AreaPtg);
  499. assertTrue(ptgs[2] instanceof AreaPtg);
  500. assertTrue(ptgs[3] instanceof IntersectionPtg);
  501. ptgs = parse(fpb, "A1:B1 B1:B2");
  502. assertEquals(4, ptgs.length);
  503. assertTrue(ptgs[0] instanceof MemAreaPtg);
  504. assertTrue(ptgs[1] instanceof AreaPtg);
  505. assertTrue(ptgs[2] instanceof AreaPtg);
  506. assertTrue(ptgs[3] instanceof IntersectionPtg);
  507. wb.close();
  508. }
  509. @Test
  510. void testWhitespaceInComplexFormula() throws IOException {
  511. XSSFWorkbook wb = new XSSFWorkbook();
  512. XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(wb);
  513. Ptg[] ptgs;
  514. // verify whitespaces in different places
  515. ptgs = parse(fpb, "SUM(A1:INDEX(1:1048576,MAX(IFERROR(MATCH(99^99,B:B,1),0),IFERROR(MATCH(\"zzzz\",B:B,1),0)),MAX(IFERROR(MATCH(99^99,1:1,1),0),IFERROR(MATCH(\"zzzz\",1:1,1),0))))");
  516. assertEquals(40, ptgs.length);
  517. assertTrue(ptgs[0] instanceof MemFuncPtg);
  518. assertTrue(ptgs[1] instanceof RefPtg);
  519. assertTrue(ptgs[2] instanceof AreaPtg);
  520. assertTrue(ptgs[3] instanceof NameXPxg);
  521. ptgs = parse(fpb, "SUM ( A1 : INDEX( 1 : 1048576 , MAX( IFERROR ( MATCH ( 99 ^ 99 , B : B , 1 ) , 0 ) , IFERROR ( MATCH ( \"zzzz\" , B:B , 1 ) , 0 ) ) , MAX ( IFERROR ( MATCH ( 99 ^ 99 , 1 : 1 , 1 ) , 0 ) , IFERROR ( MATCH ( \"zzzz\" , 1 : 1 , 1 ) , 0 ) ) ) )");
  522. assertEquals(40, ptgs.length);
  523. assertTrue(ptgs[0] instanceof MemFuncPtg);
  524. assertTrue(ptgs[1] instanceof RefPtg);
  525. assertTrue(ptgs[2] instanceof AreaPtg);
  526. assertTrue(ptgs[3] instanceof NameXPxg);
  527. wb.close();
  528. }
  529. @Test
  530. void parseStructuredReferences() throws IOException {
  531. XSSFWorkbook wb = XSSFTestDataSamples.openSampleWorkbook("StructuredReferences.xlsx");
  532. XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(wb);
  533. Ptg[] ptgs;
  534. /*
  535. The following cases are tested (copied from FormulaParser.parseStructuredReference)
  536. 1 Table1[col]
  537. 2 Table1[[#Totals],[col]]
  538. 3 Table1[#Totals]
  539. 4 Table1[#All]
  540. 5 Table1[#Data]
  541. 6 Table1[#Headers]
  542. 7 Table1[#Totals]
  543. 8 Table1[#This Row]
  544. 9 Table1[[#All],[col]]
  545. 10 Table1[[#Headers],[col]]
  546. 11 Table1[[#Totals],[col]]
  547. 12 Table1[[#All],[col1]:[col2]]
  548. 13 Table1[[#Data],[col1]:[col2]]
  549. 14 Table1[[#Headers],[col1]:[col2]]
  550. 15 Table1[[#Totals],[col1]:[col2]]
  551. 16 Table1[[#Headers],[#Data],[col2]]
  552. 17 Table1[[#This Row], [col1]]
  553. 18 Table1[ [col1]:[col2] ]
  554. 19 Table1[]
  555. */
  556. final String tbl = "\\_Prime.1";
  557. final String noTotalsRowReason = ": Tables without a Totals row should return #REF! on [#Totals]";
  558. ////// Case 1: Evaluate Table1[col] with apostrophe-escaped #-signs ////////
  559. ptgs = parse(fpb, "SUM("+tbl+"[calc='#*'#])");
  560. assertEquals(2, ptgs.length);
  561. // Area3DPxg [sheet=Table ! A2:A7]
  562. assertTrue(ptgs[0] instanceof Area3DPxg);
  563. Area3DPxg ptg0 = (Area3DPxg) ptgs[0];
  564. assertEquals("Table", ptg0.getSheetName());
  565. assertEquals("A2:A7", ptg0.format2DRefAsString());
  566. // Note: structured references are evaluated and resolved to regular 3D area references.
  567. assertEquals("Table!A2:A7", ptg0.toFormulaString());
  568. // AttrPtg [sum ]
  569. assertTrue(ptgs[1] instanceof AttrPtg);
  570. AttrPtg ptg1 = (AttrPtg) ptgs[1];
  571. assertTrue(ptg1.isSum());
  572. ////// Case 1: Evaluate "Table1[col]" ////////
  573. ptgs = parse(fpb, tbl+"[Name]");
  574. assertEquals(1, ptgs.length);
  575. assertEquals("Table!B2:B7", ptgs[0].toFormulaString(), "Table1[col]");
  576. ////// Case 2: Evaluate "Table1[[#Totals],[col]]" ////////
  577. ptgs = parse(fpb, tbl+"[[#Totals],[col]]");
  578. assertEquals(1, ptgs.length);
  579. assertEquals(ErrPtg.REF_INVALID, ptgs[0], "Table1[[#Totals],[col]]" + noTotalsRowReason);
  580. ////// Case 3: Evaluate "Table1[#Totals]" ////////
  581. ptgs = parse(fpb, tbl+"[#Totals]");
  582. assertEquals(1, ptgs.length);
  583. assertEquals(ErrPtg.REF_INVALID, ptgs[0], "Table1[#Totals]" + noTotalsRowReason);
  584. ////// Case 4: Evaluate "Table1[#All]" ////////
  585. ptgs = parse(fpb, tbl+"[#All]");
  586. assertEquals(1, ptgs.length);
  587. assertEquals("Table!A1:C7", ptgs[0].toFormulaString(), "Table1[#All]");
  588. ////// Case 5: Evaluate "Table1[#Data]" (excludes Header and Total rows) ////////
  589. ptgs = parse(fpb, tbl+"[#Data]");
  590. assertEquals(1, ptgs.length);
  591. assertEquals("Table!A2:C7", ptgs[0].toFormulaString(), "Table1[#Data]");
  592. ////// Case 6: Evaluate "Table1[#Headers]" ////////
  593. ptgs = parse(fpb, tbl+"[#Headers]");
  594. assertEquals(1, ptgs.length);
  595. assertEquals("Table!A1:C1", ptgs[0].toFormulaString(), "Table1[#Headers]");
  596. ////// Case 7: Evaluate "Table1[#Totals]" ////////
  597. ptgs = parse(fpb, tbl+"[#Totals]");
  598. assertEquals(1, ptgs.length);
  599. assertEquals(ErrPtg.REF_INVALID, ptgs[0], "Table1[#Totals]" + noTotalsRowReason);
  600. ////// Case 8: Evaluate "Table1[#This Row]" ////////
  601. ptgs = parse(fpb, tbl+"[#This Row]", 2);
  602. assertEquals(1, ptgs.length);
  603. assertEquals("Table!A3:C3", ptgs[0].toFormulaString(), "Table1[#This Row]");
  604. ////// Evaluate "Table1[@]" (equivalent to "Table1[#This Row]") ////////
  605. ptgs = parse(fpb, tbl+"[@]", 2);
  606. assertEquals(1, ptgs.length);
  607. assertEquals("Table!A3:C3", ptgs[0].toFormulaString());
  608. ////// Evaluate "Table1[#This Row]" when rowIndex is outside Table ////////
  609. ptgs = parse(fpb, tbl+"[#This Row]", 10);
  610. assertEquals(1, ptgs.length);
  611. assertEquals(ErrPtg.VALUE_INVALID, ptgs[0], "Table1[#This Row]");
  612. ////// Evaluate "Table1[@]" when rowIndex is outside Table ////////
  613. ptgs = parse(fpb, tbl+"[@]", 10);
  614. assertEquals(1, ptgs.length);
  615. assertEquals(ErrPtg.VALUE_INVALID, ptgs[0], "Table1[@]");
  616. ////// Evaluate "Table1[[#Data],[col]]" ////////
  617. ptgs = parse(fpb, tbl+"[[#Data], [Number]]");
  618. assertEquals(1, ptgs.length);
  619. assertEquals("Table!C2:C7", ptgs[0].toFormulaString(), "Table1[[#Data],[col]]");
  620. ////// Case 9: Evaluate "Table1[[#All],[col]]" ////////
  621. ptgs = parse(fpb, tbl+"[[#All], [Number]]");
  622. assertEquals(1, ptgs.length);
  623. assertEquals("Table!C1:C7", ptgs[0].toFormulaString(), "Table1[[#All],[col]]");
  624. ////// Case 10: Evaluate "Table1[[#Headers],[col]]" ////////
  625. ptgs = parse(fpb, tbl+"[[#Headers], [Number]]");
  626. assertEquals(1, ptgs.length);
  627. // also acceptable: Table1!B1
  628. assertEquals("Table!C1:C1", ptgs[0].toFormulaString(), "Table1[[#Headers],[col]]");
  629. ////// Case 11: Evaluate "Table1[[#Totals],[col]]" ////////
  630. ptgs = parse(fpb, tbl+"[[#Totals],[Name]]");
  631. assertEquals(1, ptgs.length);
  632. assertEquals(ErrPtg.REF_INVALID, ptgs[0], "Table1[[#Totals],[col]]" + noTotalsRowReason);
  633. ////// Case 12: Evaluate "Table1[[#All],[col1]:[col2]]" ////////
  634. ptgs = parse(fpb, tbl+"[[#All], [Name]:[Number]]");
  635. assertEquals(1, ptgs.length);
  636. assertEquals("Table!B1:C7", ptgs[0].toFormulaString(), "Table1[[#All],[col1]:[col2]]");
  637. ////// Case 13: Evaluate "Table1[[#Data],[col]:[col2]]" ////////
  638. ptgs = parse(fpb, tbl+"[[#Data], [Name]:[Number]]");
  639. assertEquals(1, ptgs.length);
  640. assertEquals("Table!B2:C7", ptgs[0].toFormulaString(), "Table1[[#Data],[col]:[col2]]");
  641. ////// Case 14: Evaluate "Table1[[#Headers],[col1]:[col2]]" ////////
  642. ptgs = parse(fpb, tbl+"[[#Headers], [Name]:[Number]]");
  643. assertEquals(1, ptgs.length);
  644. assertEquals("Table!B1:C1", ptgs[0].toFormulaString(), "Table1[[#Headers],[col1]:[col2]]");
  645. ////// Case 15: Evaluate "Table1[[#Totals],[col]:[col2]]" ////////
  646. ptgs = parse(fpb, tbl+"[[#Totals], [Name]:[Number]]");
  647. assertEquals(1, ptgs.length);
  648. assertEquals(ErrPtg.REF_INVALID, ptgs[0], "Table1[[#Totals],[col]:[col2]]" + noTotalsRowReason);
  649. ////// Case 16: Evaluate "Table1[[#Headers],[#Data],[col]]" ////////
  650. ptgs = parse(fpb, tbl+"[[#Headers],[#Data],[Number]]");
  651. assertEquals(1, ptgs.length);
  652. assertEquals("Table!C1:C7", ptgs[0].toFormulaString(), "Table1[[#Headers],[#Data],[col]]");
  653. ////// Case 17: Evaluate "Table1[[#This Row], [col1]]" ////////
  654. ptgs = parse(fpb, tbl+"[[#This Row], [Number]]", 2);
  655. assertEquals(1, ptgs.length);
  656. // also acceptable: Table!C3
  657. assertEquals("Table!C3:C3", ptgs[0].toFormulaString(), "Table1[[#This Row], [col1]]");
  658. ////// Case 18: Evaluate "Table1[[col]:[col2]]" ////////
  659. ptgs = parse(fpb, tbl+"[[Name]:[Number]]");
  660. assertEquals(1, ptgs.length);
  661. assertEquals("Table!B2:C7", ptgs[0].toFormulaString(), "Table1[[col]:[col2]]");
  662. ////// Case 19: Evaluate "Table1[]" ////////
  663. // Excludes Header and Total rows, equivalent to Table1[#Data] (see case 5).
  664. // This is the only case where [] is allowed.
  665. ptgs = parse(fpb, tbl+"[]");
  666. assertEquals(1, ptgs.length);
  667. assertEquals("Table!A2:C7", ptgs[0].toFormulaString(), "Table1[]");
  668. wb.close();
  669. }
  670. }