From 2e2559d80a048be860b4fda958db05caba9882c6 Mon Sep 17 00:00:00 2001 From: Yegor Kozlov Date: Mon, 6 Feb 2012 07:37:11 +0000 Subject: [PATCH] bugzilla 52575: added an option to ignore missing workbook references in formula evaluator git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1240903 13f79535-47bb-0310-9956-ffa450edef68 --- src/documentation/content/xdocs/status.xml | 1 + .../hssf/usermodel/HSSFEvaluationCell.java | 11 +- .../apache/poi/ss/formula/EvaluationCell.java | 10 +- .../formula/OperationEvaluationContext.java | 14 +-- .../poi/ss/formula/WorkbookEvaluator.java | 57 ++++++++- .../eval/forked/ForkedEvaluationCell.java | 4 + .../xssf/usermodel/XSSFEvaluationCell.java | 11 +- .../poi/ss/formula/TestMissingWorkbook.java | 113 ++++++++++++++++++ test-data/spreadsheet/52575_main.xls | Bin 0 -> 23040 bytes test-data/spreadsheet/52575_source.xls | Bin 0 -> 23040 bytes 10 files changed, 197 insertions(+), 24 deletions(-) create mode 100644 src/testcases/org/apache/poi/ss/formula/TestMissingWorkbook.java create mode 100644 test-data/spreadsheet/52575_main.xls create mode 100644 test-data/spreadsheet/52575_source.xls diff --git a/src/documentation/content/xdocs/status.xml b/src/documentation/content/xdocs/status.xml index c2a3693f6..b3f0f1182 100644 --- a/src/documentation/content/xdocs/status.xml +++ b/src/documentation/content/xdocs/status.xml @@ -34,6 +34,7 @@ + 52575 - added an option to ignore missing workbook references in formula evaluator Validate address of hyperlinks in XSSF 52540 - Relax the M4.1 constraint on reading OOXML files, as some Office produced ones do have 2 Core Properties, despite the specification explicitly forbidding this 52462 - Added implementation for SUMIFS() diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFEvaluationCell.java b/src/java/org/apache/poi/hssf/usermodel/HSSFEvaluationCell.java index 66069f8cb..462d92a56 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFEvaluationCell.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFEvaluationCell.java @@ -66,7 +66,10 @@ final class HSSFEvaluationCell implements EvaluationCell { public EvaluationSheet getSheet() { return _evalSheet; } - public String getStringCellValue() { - return _cell.getRichStringCellValue().getString(); - } -} + public String getStringCellValue() { + return _cell.getRichStringCellValue().getString(); + } + public int getCachedFormulaResultType() { + return _cell.getCachedFormulaResultType(); + } +} diff --git a/src/java/org/apache/poi/ss/formula/EvaluationCell.java b/src/java/org/apache/poi/ss/formula/EvaluationCell.java index eef4da18d..a3de9e894 100644 --- a/src/java/org/apache/poi/ss/formula/EvaluationCell.java +++ b/src/java/org/apache/poi/ss/formula/EvaluationCell.java @@ -38,7 +38,9 @@ public interface EvaluationCell { int getCellType(); double getNumericCellValue(); - String getStringCellValue(); - boolean getBooleanCellValue(); - int getErrorCellValue(); -} + String getStringCellValue(); + boolean getBooleanCellValue(); + int getErrorCellValue(); + + int getCachedFormulaResultType(); +} diff --git a/src/java/org/apache/poi/ss/formula/OperationEvaluationContext.java b/src/java/org/apache/poi/ss/formula/OperationEvaluationContext.java index a15408925..87018e6ab 100644 --- a/src/java/org/apache/poi/ss/formula/OperationEvaluationContext.java +++ b/src/java/org/apache/poi/ss/formula/OperationEvaluationContext.java @@ -83,13 +83,13 @@ public final class OperationEvaluationContext { } 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()); - } - otherSheetIndex = targetEvaluator.getSheetIndex(externalSheet.getSheetName()); - if (otherSheetIndex < 0) { + try { + targetEvaluator = _bookEvaluator.getOtherWorkbookEvaluator(workbookName); + } catch (WorkbookNotFoundException e) { + throw new RuntimeException(e.getMessage(), e); + } + otherSheetIndex = targetEvaluator.getSheetIndex(externalSheet.getSheetName()); + if (otherSheetIndex < 0) { throw new RuntimeException("Invalid sheet name '" + externalSheet.getSheetName() + "' in bool '" + workbookName + "'."); } diff --git a/src/java/org/apache/poi/ss/formula/WorkbookEvaluator.java b/src/java/org/apache/poi/ss/formula/WorkbookEvaluator.java index 1328c850b..3807c9c29 100644 --- a/src/java/org/apache/poi/ss/formula/WorkbookEvaluator.java +++ b/src/java/org/apache/poi/ss/formula/WorkbookEvaluator.java @@ -67,6 +67,8 @@ import org.apache.poi.ss.util.CellReference; import org.apache.poi.ss.formula.CollaboratingWorkbooksEnvironment.WorkbookNotFoundException; import org.apache.poi.ss.formula.eval.NotImplementedException; import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.util.POILogFactory; +import org.apache.poi.util.POILogger; /** * Evaluates formula cells.

@@ -80,8 +82,16 @@ import org.apache.poi.ss.usermodel.Cell; * @author Josh Micich */ public final class WorkbookEvaluator { + + private static final POILogger LOG = POILogFactory.getLogger(WorkbookEvaluator.class); - private final EvaluationWorkbook _workbook; + /** + * Whether to use cached formula results if external workbook references in a formula is not available. + * See Bugzilla 52575 for details. + */ + private static final String IGNORE_MISSING_WORKBOOKS = WorkbookEvaluator.class.getName() + ".IGNORE_MISSING_WORKBOOKS"; + + private final EvaluationWorkbook _workbook; private EvaluationCache _cache; /** part of cache entry key (useful when evaluating multiple workbooks) */ private int _workbookIx; @@ -144,11 +154,19 @@ public final class WorkbookEvaluator { } private static boolean isDebugLogEnabled() { - return false; + return LOG.check(POILogger.DEBUG); + } + private static boolean isInfoLogEnabled() { + return LOG.check(POILogger.INFO); } private static void logDebug(String s) { if (isDebugLogEnabled()) { - System.out.println(s); + 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) { @@ -288,9 +306,38 @@ public final class WorkbookEvaluator { } tracker.updateCacheResult(result); - } catch (NotImplementedException e) { + } + catch (NotImplementedException e) { throw addExceptionInfo(e, sheetIndex, rowIndex, columnIndex); - } finally { + } catch (RuntimeException re) { + if (re.getCause() instanceof WorkbookNotFoundException + //To be replaced by configuration infrastructure + && Boolean.valueOf(System.getProperty(IGNORE_MISSING_WORKBOOKS))) { + 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 { diff --git a/src/java/org/apache/poi/ss/formula/eval/forked/ForkedEvaluationCell.java b/src/java/org/apache/poi/ss/formula/eval/forked/ForkedEvaluationCell.java index 8e8690257..2c169e648 100644 --- a/src/java/org/apache/poi/ss/formula/eval/forked/ForkedEvaluationCell.java +++ b/src/java/org/apache/poi/ss/formula/eval/forked/ForkedEvaluationCell.java @@ -128,4 +128,8 @@ final class ForkedEvaluationCell implements EvaluationCell { public int getColumnIndex() { return _masterCell.getColumnIndex(); } + public int getCachedFormulaResultType() { + return _masterCell.getCachedFormulaResultType(); + } + } diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFEvaluationCell.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFEvaluationCell.java index 127e4da86..46f10dc26 100644 --- a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFEvaluationCell.java +++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFEvaluationCell.java @@ -69,7 +69,10 @@ final class XSSFEvaluationCell implements EvaluationCell { public EvaluationSheet getSheet() { return _evalSheet; } - public String getStringCellValue() { - return _cell.getRichStringCellValue().getString(); - } -} + public String getStringCellValue() { + return _cell.getRichStringCellValue().getString(); + } + public int getCachedFormulaResultType() { + return _cell.getCachedFormulaResultType(); + } +} diff --git a/src/testcases/org/apache/poi/ss/formula/TestMissingWorkbook.java b/src/testcases/org/apache/poi/ss/formula/TestMissingWorkbook.java new file mode 100644 index 000000000..7b639f996 --- /dev/null +++ b/src/testcases/org/apache/poi/ss/formula/TestMissingWorkbook.java @@ -0,0 +1,113 @@ +/* + * ==================================================================== + * 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.ss.formula; + +import junit.framework.TestCase; +import org.apache.poi.hssf.HSSFTestDataSamples; +import org.apache.poi.hssf.usermodel.*; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.FormulaEvaluator; + +import java.io.IOException; + +public class TestMissingWorkbook extends TestCase { + private static final String MAIN_WORKBOOK_FILENAME = "52575_main.xls"; + private static final String SOURCE_DUMMY_WORKBOOK_FILENAME = "source_dummy.xls"; + private static final String SOURCE_WORKBOOK_FILENAME = "52575_source.xls"; + + private static final String propertyKey = WorkbookEvaluator.class.getName() + ".IGNORE_MISSING_WORKBOOKS"; + + private HSSFWorkbook mainWorkbook; + private HSSFWorkbook sourceWorkbook; + + @Override + protected void setUp() throws Exception { + mainWorkbook = HSSFTestDataSamples.openSampleWorkbook(MAIN_WORKBOOK_FILENAME); + sourceWorkbook = HSSFTestDataSamples.openSampleWorkbook(SOURCE_WORKBOOK_FILENAME); + + assertNotNull(mainWorkbook); + assertNotNull(sourceWorkbook); + } + + public void testMissingWorkbookMissing() throws IOException { + FormulaEvaluator evaluator = mainWorkbook.getCreationHelper().createFormulaEvaluator(); + + HSSFSheet lSheet = mainWorkbook.getSheetAt(0); + HSSFRow lARow = lSheet.getRow(0); + HSSFCell lA1Cell = lARow.getCell(0); + + assertEquals(Cell.CELL_TYPE_FORMULA, lA1Cell.getCellType()); + try { + evaluator.evaluateFormulaCell(lA1Cell); + fail("Missing external workbook reference exception expected!"); + }catch(RuntimeException re) { + assertTrue("Unexpected exception: " + re, re.getMessage().indexOf(SOURCE_DUMMY_WORKBOOK_FILENAME) != -1); + } + } + + public void testMissingWorkbookMissingOverride() throws IOException { + HSSFSheet lSheet = mainWorkbook.getSheetAt(0); + HSSFCell lA1Cell = lSheet.getRow(0).getCell(0); + HSSFCell lB1Cell = lSheet.getRow(1).getCell(0); + HSSFCell lC1Cell = lSheet.getRow(2).getCell(0); + + assertEquals(Cell.CELL_TYPE_FORMULA, lA1Cell.getCellType()); + assertEquals(Cell.CELL_TYPE_FORMULA, lB1Cell.getCellType()); + assertEquals(Cell.CELL_TYPE_FORMULA, lC1Cell.getCellType()); + + FormulaEvaluator evaluator = mainWorkbook.getCreationHelper().createFormulaEvaluator(); + + System.setProperty(propertyKey, Boolean.toString(true)); + assertEquals(Cell.CELL_TYPE_NUMERIC, evaluator.evaluateFormulaCell(lA1Cell)); + assertEquals(Cell.CELL_TYPE_STRING, evaluator.evaluateFormulaCell(lB1Cell)); + assertEquals(Cell.CELL_TYPE_BOOLEAN, evaluator.evaluateFormulaCell(lC1Cell)); + + assertEquals(10.0d, lA1Cell.getNumericCellValue(), 0.00001d); + assertEquals("POI rocks!", lB1Cell.getStringCellValue()); + assertEquals(true, lC1Cell.getBooleanCellValue()); + } + + + public void testExistingWorkbook() throws IOException { + HSSFSheet lSheet = mainWorkbook.getSheetAt(0); + HSSFCell lA1Cell = lSheet.getRow(0).getCell(0); + HSSFCell lB1Cell = lSheet.getRow(1).getCell(0); + HSSFCell lC1Cell = lSheet.getRow(2).getCell(0); + + assertEquals(Cell.CELL_TYPE_FORMULA, lA1Cell.getCellType()); + assertEquals(Cell.CELL_TYPE_FORMULA, lB1Cell.getCellType()); + assertEquals(Cell.CELL_TYPE_FORMULA, lC1Cell.getCellType()); + + HSSFFormulaEvaluator lMainWorkbookEvaluator = new HSSFFormulaEvaluator(mainWorkbook); + HSSFFormulaEvaluator lSourceEvaluator = new HSSFFormulaEvaluator(sourceWorkbook); + HSSFFormulaEvaluator.setupEnvironment( + new String[]{MAIN_WORKBOOK_FILENAME, SOURCE_DUMMY_WORKBOOK_FILENAME}, + new HSSFFormulaEvaluator[] {lMainWorkbookEvaluator, lSourceEvaluator}); + + assertEquals(Cell.CELL_TYPE_NUMERIC, lMainWorkbookEvaluator.evaluateFormulaCell(lA1Cell)); + assertEquals(Cell.CELL_TYPE_STRING, lMainWorkbookEvaluator.evaluateFormulaCell(lB1Cell)); + assertEquals(Cell.CELL_TYPE_BOOLEAN, lMainWorkbookEvaluator.evaluateFormulaCell(lC1Cell)); + + assertEquals(20.0d, lA1Cell.getNumericCellValue(), 0.00001d); + assertEquals("Apache rocks!", lB1Cell.getStringCellValue()); + assertEquals(false, lC1Cell.getBooleanCellValue()); + } + +} diff --git a/test-data/spreadsheet/52575_main.xls b/test-data/spreadsheet/52575_main.xls new file mode 100644 index 0000000000000000000000000000000000000000..fbcf0dd6585cc7de0dc897958f9218efde101ca7 GIT binary patch literal 23040 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