From c819babd6e089066fe9e81c96cc39f6a04571665 Mon Sep 17 00:00:00 2001 From: Josh Micich Date: Sun, 28 Sep 2008 02:04:31 +0000 Subject: [PATCH] Bug 45865 - modified Formula Parser/Evaluator to handle cross-worksheet formulas git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@699761 13f79535-47bb-0310-9956-ffa450edef68 --- src/documentation/content/xdocs/changes.xml | 1 + src/documentation/content/xdocs/status.xml | 1 + .../org/apache/poi/hssf/model/LinkTable.java | 91 ++++++++-- .../org/apache/poi/hssf/model/Workbook.java | 130 ++++++++------- .../poi/hssf/record/ExternSheetRecord.java | 5 +- .../apache/poi/hssf/record/SupBookRecord.java | 29 +++- .../poi/hssf/record/formula/Area3DPtg.java | 10 +- .../formula/ExternSheetNameResolver.java | 22 ++- .../poi/hssf/record/formula/Ref3DPtg.java | 9 +- .../record/formula/SheetNameFormatter.java | 16 ++ .../usermodel/HSSFEvaluationWorkbook.java | 12 +- .../hssf/usermodel/HSSFFormulaEvaluator.java | 16 ++ .../apache/poi/ss/formula/CellLocation.java | 23 ++- .../CollaboratingWorkbooksEnvironment.java | 155 ++++++++++++++++++ .../poi/ss/formula/EvaluationCache.java | 25 ++- .../poi/ss/formula/EvaluationWorkbook.java | 24 +++ .../ss/formula/ExternSheetReferenceToken.java | 29 ++++ .../apache/poi/ss/formula/FormulaParser.java | 19 ++- .../ss/formula/FormulaParsingWorkbook.java | 12 +- .../ss/formula/FormulaRenderingWorkbook.java | 5 + .../poi/ss/formula/WorkbookEvaluator.java | 58 +++++-- .../poi/hssf/data/multibookFormulaA.xls | Bin 0 -> 16896 bytes .../poi/hssf/data/multibookFormulaB.xls | Bin 0 -> 16896 bytes .../poi/hssf/model/TestFormulaParser.java | 31 ++++ .../poi/hssf/record/TestSupBookRecord.java | 2 +- .../poi/ss/formula/TestWorkbookEvaluator.java | 94 +++++++++-- 26 files changed, 680 insertions(+), 139 deletions(-) create mode 100644 src/java/org/apache/poi/ss/formula/CollaboratingWorkbooksEnvironment.java create mode 100644 src/java/org/apache/poi/ss/formula/ExternSheetReferenceToken.java create mode 100644 src/testcases/org/apache/poi/hssf/data/multibookFormulaA.xls create mode 100644 src/testcases/org/apache/poi/hssf/data/multibookFormulaB.xls diff --git a/src/documentation/content/xdocs/changes.xml b/src/documentation/content/xdocs/changes.xml index c054f65be1..ecf151967e 100644 --- a/src/documentation/content/xdocs/changes.xml +++ b/src/documentation/content/xdocs/changes.xml @@ -37,6 +37,7 @@ + 45865 modified Formula Parser/Evaluator to handle cross-worksheet formulas Optimised the FormulaEvaluator to take cell dependencies into account 16936 - Initial support for whole-row cell styling Update hssf.extractor.ExcelExtractor to optionally output blank cells too diff --git a/src/documentation/content/xdocs/status.xml b/src/documentation/content/xdocs/status.xml index b416d344fa..e3c9698600 100644 --- a/src/documentation/content/xdocs/status.xml +++ b/src/documentation/content/xdocs/status.xml @@ -34,6 +34,7 @@ + 45865 modified Formula Parser/Evaluator to handle cross-worksheet formulas Optimised the FormulaEvaluator to take cell dependencies into account 16936 - Initial support for whole-row cell styling Update hssf.extractor.ExcelExtractor to optionally output blank cells too diff --git a/src/java/org/apache/poi/hssf/model/LinkTable.java b/src/java/org/apache/poi/hssf/model/LinkTable.java index 2caab0fb56..998712e5e8 100755 --- a/src/java/org/apache/poi/hssf/model/LinkTable.java +++ b/src/java/org/apache/poi/hssf/model/LinkTable.java @@ -29,6 +29,7 @@ import org.apache.poi.hssf.record.ExternalNameRecord; import org.apache.poi.hssf.record.NameRecord; import org.apache.poi.hssf.record.Record; import org.apache.poi.hssf.record.SupBookRecord; +import org.apache.poi.hssf.record.UnicodeString; import org.apache.poi.hssf.record.formula.NameXPtg; /** @@ -109,8 +110,8 @@ final class LinkTable { temp.toArray(_crnBlocks); } - public ExternalBookBlock(short numberOfSheets) { - _externalBookRecord = SupBookRecord.createInternalReferences(numberOfSheets); + public ExternalBookBlock(int numberOfSheets) { + _externalBookRecord = SupBookRecord.createInternalReferences((short)numberOfSheets); _externalNameRecords = new ExternalNameRecord[0]; _crnBlocks = new CRNBlock[0]; } @@ -197,7 +198,7 @@ final class LinkTable { return ExternSheetRecord.combine(esrs); } - public LinkTable(short numberOfSheets, WorkbookRecordList workbookRecordList) { + public LinkTable(int numberOfSheets, WorkbookRecordList workbookRecordList) { _workbookRecordList = workbookRecordList; _definedNames = new ArrayList(); _externalBookBlocks = new ExternalBookBlock[] { @@ -303,8 +304,62 @@ final class LinkTable { return lastName.getSheetNumber() == firstName.getSheetNumber(); } - - public int getIndexToSheet(int extRefIndex) { + public String[] getExternalBookAndSheetName(int extRefIndex) { + int ebIx = _externSheetRecord.getExtbookIndexFromRefIndex(extRefIndex); + SupBookRecord ebr = _externalBookBlocks[ebIx].getExternalBookRecord(); + if (!ebr.isExternalReferences()) { + return null; + } + int shIx = _externSheetRecord.getFirstSheetIndexFromRefIndex(extRefIndex); + UnicodeString usSheetName = ebr.getSheetNames()[shIx]; + return new String[] { + ebr.getURL(), + usSheetName.getString(), + }; + } + + public int getExternalSheetIndex(String workbookName, String sheetName) { + SupBookRecord ebrTarget = null; + int externalBookIndex = -1; + for (int i=0; i<_externalBookBlocks.length; i++) { + SupBookRecord ebr = _externalBookBlocks[i].getExternalBookRecord(); + if (!ebr.isExternalReferences()) { + continue; + } + if (workbookName.equals(ebr.getURL())) { // not sure if 'equals()' works when url has a directory + ebrTarget = ebr; + externalBookIndex = i; + break; + } + } + if (ebrTarget == null) { + throw new RuntimeException("No external workbook with name '" + workbookName + "'"); + } + int sheetIndex = getSheetIndex(ebrTarget.getSheetNames(), sheetName); + + int result = _externSheetRecord.getRefIxForSheet(externalBookIndex, sheetIndex); + if (result < 0) { + throw new RuntimeException("ExternSheetRecord does not contain combination (" + + externalBookIndex + ", " + sheetIndex + ")"); + } + return result; + } + + private static int getSheetIndex(UnicodeString[] sheetNames, String sheetName) { + for (int i = 0; i < sheetNames.length; i++) { + if (sheetNames[i].getString().equals(sheetName)) { + return i; + } + + } + throw new RuntimeException("External workbook does not contain sheet '" + sheetName + "'"); + } + + /** + * @param extRefIndex as from a {@link Ref3DPtg} or {@link Area3DPtg} + * @return -1 if the reference is to an external book + */ + public int getIndexToInternalSheet(int extRefIndex) { return _externSheetRecord.getFirstSheetIndexFromRefIndex(extRefIndex); } @@ -315,20 +370,26 @@ final class LinkTable { return _externSheetRecord.getFirstSheetIndexFromRefIndex(extRefIndex); } - public int addSheetIndexToExternSheet(int sheetNumber) { - // TODO - what about the first parameter (extBookIndex)? - return _externSheetRecord.addRef(0, sheetNumber, sheetNumber); - } - - public short checkExternSheet(int sheetIndex) { + public int checkExternSheet(int sheetIndex) { + int thisWbIndex = -1; // this is probably always zero + for (int i=0; i<_externalBookBlocks.length; i++) { + SupBookRecord ebr = _externalBookBlocks[i].getExternalBookRecord(); + if (ebr.isInternalReferences()) { + thisWbIndex = i; + break; + } + } + if (thisWbIndex < 0) { + throw new RuntimeException("Could not find 'internal references' EXTERNALBOOK"); + } //Trying to find reference to this sheet - int i = _externSheetRecord.getRefIxForSheet(sheetIndex); + int i = _externSheetRecord.getRefIxForSheet(thisWbIndex, sheetIndex); if (i>=0) { - return (short)i; + return i; } - //We Haven't found reference to this sheet - return (short)addSheetIndexToExternSheet((short) sheetIndex); + //We haven't found reference to this sheet + return _externSheetRecord.addRef(thisWbIndex, sheetIndex, sheetIndex); } diff --git a/src/java/org/apache/poi/hssf/model/Workbook.java b/src/java/org/apache/poi/hssf/model/Workbook.java index dbf7ecf7e1..0728d0e29f 100644 --- a/src/java/org/apache/poi/hssf/model/Workbook.java +++ b/src/java/org/apache/poi/hssf/model/Workbook.java @@ -26,6 +26,7 @@ import org.apache.poi.ddf.*; import org.apache.poi.hssf.record.*; import org.apache.poi.hssf.record.formula.NameXPtg; import org.apache.poi.hssf.util.HSSFColor; +import org.apache.poi.ss.formula.EvaluationWorkbook.ExternalSheet; import org.apache.poi.util.POILogFactory; import org.apache.poi.util.POILogger; @@ -328,9 +329,9 @@ public final class Workbook implements Model { for ( int k = 0; k < nBoundSheets; k++ ) { BoundSheetRecord bsr = retval.createBoundSheet(k); - records.add(bsr); - retval.boundsheets.add(bsr); - retval.records.setBspos(records.size() - 1); + records.add(bsr); + retval.boundsheets.add(bsr); + retval.records.setBspos(records.size() - 1); } // retval.records.supbookpos = retval.records.bspos + 1; // retval.records.namepos = retval.records.supbookpos + 2; @@ -586,19 +587,19 @@ public final class Workbook implements Model { * @param hidden 0 for not hidden, 1 for hidden, 2 for very hidden */ public void setSheetHidden(int sheetnum, int hidden) { - BoundSheetRecord bsr = getBoundSheetRec(sheetnum); - boolean h = false; - boolean vh = false; - if(hidden == 0) { - } else if(hidden == 1) { - h = true; - } else if(hidden == 2) { - vh = true; - } else { - throw new IllegalArgumentException("Invalid hidden flag " + hidden + " given, must be 0, 1 or 2"); - } - bsr.setHidden(h); - bsr.setVeryHidden(vh); + BoundSheetRecord bsr = getBoundSheetRec(sheetnum); + boolean h = false; + boolean vh = false; + if(hidden == 0) { + } else if(hidden == 1) { + h = true; + } else if(hidden == 2) { + vh = true; + } else { + throw new IllegalArgumentException("Invalid hidden flag " + hidden + " given, must be 0, 1 or 2"); + } + bsr.setHidden(h); + bsr.setVeryHidden(vh); } @@ -761,23 +762,23 @@ public final class Workbook implements Model { * have a Style set. */ public StyleRecord getStyleRecord(int xfIndex) { - // Style records always follow after - // the ExtendedFormat records - boolean done = false; - for(int i=records.getXfpos(); i */ final class CellLocation { public static final CellLocation[] EMPTY_ARRAY = { }; + private final EvaluationWorkbook _book; private final int _sheetIndex; private final int _rowIndex; private final int _columnIndex; private final int _hashCode; - public CellLocation(int sheetIndex, int rowIndex, int columnIndex) { + public CellLocation(EvaluationWorkbook book, int sheetIndex, int rowIndex, int columnIndex) { if (sheetIndex < 0) { throw new IllegalArgumentException("sheetIndex must not be negative"); } + _book = book; _sheetIndex = sheetIndex; _rowIndex = rowIndex; _columnIndex = columnIndex; - _hashCode = sheetIndex + 17 * (rowIndex + 17 * columnIndex); + _hashCode = System.identityHashCode(book) + sheetIndex + 17 * (rowIndex + 17 * columnIndex); + } + public Object getBook() { + return _book; } public int getSheetIndex() { return _sheetIndex; @@ -49,15 +56,18 @@ final class CellLocation { public boolean equals(Object obj) { CellLocation other = (CellLocation) obj; - if (getSheetIndex() != other.getSheetIndex()) { - return false; - } if (getRowIndex() != other.getRowIndex()) { return false; } if (getColumnIndex() != other.getColumnIndex()) { return false; } + if (getSheetIndex() != other.getSheetIndex()) { + return false; + } + if (getBook() != other.getBook()) { + return false; + } return true; } public int hashCode() { @@ -68,7 +78,8 @@ final class CellLocation { * @return human readable string for debug purposes */ public String formatAsString() { - return "ShIx=" + getSheetIndex() + " R=" + getRowIndex() + " C=" + getColumnIndex(); + CellReference cr = new CellReference(_rowIndex, _columnIndex, false, false); + return "ShIx=" + getSheetIndex() + " " + cr.formatAsString(); } public String toString() { diff --git a/src/java/org/apache/poi/ss/formula/CollaboratingWorkbooksEnvironment.java b/src/java/org/apache/poi/ss/formula/CollaboratingWorkbooksEnvironment.java new file mode 100644 index 0000000000..c62d2f182d --- /dev/null +++ b/src/java/org/apache/poi/ss/formula/CollaboratingWorkbooksEnvironment.java @@ -0,0 +1,155 @@ +/* ==================================================================== + 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 java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.IdentityHashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + + +/** + * Manages a collection of {@link WorkbookEvaluator}s, in order to support evaluation of formulas + * across spreadsheets.

+ * + * For POI internal use only + * + * @author Josh Micich + */ +public final class CollaboratingWorkbooksEnvironment { + + public static final CollaboratingWorkbooksEnvironment EMPTY = new CollaboratingWorkbooksEnvironment(); + + private final Map _evaluatorsByName; + private final WorkbookEvaluator[] _evaluators; + + private boolean _unhooked; + private CollaboratingWorkbooksEnvironment() { + _evaluatorsByName = Collections.EMPTY_MAP; + _evaluators = new WorkbookEvaluator[0]; + } + public static void setup(String[] workbookNames, WorkbookEvaluator[] evaluators) { + int nItems = workbookNames.length; + if (evaluators.length != nItems) { + throw new IllegalArgumentException("Number of workbook names is " + nItems + + " but number of evaluators is " + evaluators.length); + } + if (nItems < 1) { + throw new IllegalArgumentException("Must provide at least one collaborating worbook"); + } + new CollaboratingWorkbooksEnvironment(workbookNames, evaluators, nItems); + } + + private CollaboratingWorkbooksEnvironment(String[] workbookNames, WorkbookEvaluator[] evaluators, int nItems) { + Map m = new HashMap(nItems * 3 / 2); + IdentityHashMap uniqueEvals = new IdentityHashMap(nItems * 3 / 2); + for(int i=0; i0) { + sb.append(", "); + } + sb.append("'").append(i.next()).append("'"); + } + sb.append(")"); + } + throw new RuntimeException(sb.toString()); + } + return result; + } +} diff --git a/src/java/org/apache/poi/ss/formula/EvaluationCache.java b/src/java/org/apache/poi/ss/formula/EvaluationCache.java index fdc933f6fa..b0c34fd78a 100644 --- a/src/java/org/apache/poi/ss/formula/EvaluationCache.java +++ b/src/java/org/apache/poi/ss/formula/EvaluationCache.java @@ -81,13 +81,7 @@ final class EvaluationCache { + cellLoc.formatAsString()); } } - if (_evaluationListener == null) { - // optimisation - don't bother sorting if there is no listener. - } else { - // for testing - // make order of callbacks to listener more deterministic - Arrays.sort(usedCells, CellLocationComparator); - } + sortCellLocationsForLogging(usedCells); CellCacheEntry entry = getEntry(cellLoc); CellLocation[] consumingFormulaCells = entry.getConsumingCells(); CellLocation[] prevUsedCells = entry.getUsedCells(); @@ -110,6 +104,18 @@ final class EvaluationCache { recurseClearCachedFormulaResults(consumingFormulaCells, 0); } + /** + * This method sorts the supplied cellLocs so that the order of call-backs to the evaluation + * listener is more deterministic + */ + private void sortCellLocationsForLogging(CellLocation[] cellLocs) { + if (_evaluationListener == null) { + // optimisation - don't bother sorting if there is no listener. + } else { + Arrays.sort(cellLocs, CellLocationComparator); + } + } + private void unlinkConsumingCells(CellLocation[] prevUsedCells, CellLocation[] usedCells, CellLocation cellLoc) { if (prevUsedCells == null) { @@ -149,6 +155,7 @@ final class EvaluationCache { * @param formulaCells */ private void recurseClearCachedFormulaResults(CellLocation[] formulaCells, int depth) { + sortCellLocationsForLogging(formulaCells); int nextDepth = depth+1; for (int i = 0; i < formulaCells.length; i++) { CellLocation fc = formulaCells[i]; @@ -196,6 +203,10 @@ final class EvaluationCache { CellLocation clB = (CellLocation) b; int cmp; + cmp = System.identityHashCode(clA.getBook()) - System.identityHashCode(clB.getBook()); + if (cmp != 0) { + return cmp; + } cmp = clA.getSheetIndex() - clB.getSheetIndex(); if (cmp != 0) { return cmp; diff --git a/src/java/org/apache/poi/ss/formula/EvaluationWorkbook.java b/src/java/org/apache/poi/ss/formula/EvaluationWorkbook.java index 664df2c6ab..41678f1920 100644 --- a/src/java/org/apache/poi/ss/formula/EvaluationWorkbook.java +++ b/src/java/org/apache/poi/ss/formula/EvaluationWorkbook.java @@ -31,12 +31,36 @@ import org.apache.poi.hssf.usermodel.HSSFSheet; */ public interface EvaluationWorkbook { String getSheetName(int sheetIndex); + /** + * @return -1 if the specified sheet is from a different book + */ int getSheetIndex(HSSFSheet sheet); + int getSheetIndex(String sheetName); HSSFSheet getSheet(int sheetIndex); + /** + * @return null if externSheetIndex refers to a sheet inside the current workbook + */ + ExternalSheet getExternalSheet(int externSheetIndex); int convertFromExternSheetIndex(int externSheetIndex); EvaluationName getName(NamePtg namePtg); String resolveNameXText(NameXPtg ptg); Ptg[] getFormulaTokens(HSSFCell cell); + + class ExternalSheet { + private final String _workbookName; + private final String _sheetName; + + public ExternalSheet(String workbookName, String sheetName) { + _workbookName = workbookName; + _sheetName = sheetName; + } + public String getWorkbookName() { + return _workbookName; + } + public String getSheetName() { + return _sheetName; + } + } } diff --git a/src/java/org/apache/poi/ss/formula/ExternSheetReferenceToken.java b/src/java/org/apache/poi/ss/formula/ExternSheetReferenceToken.java new file mode 100644 index 0000000000..09262a1320 --- /dev/null +++ b/src/java/org/apache/poi/ss/formula/ExternSheetReferenceToken.java @@ -0,0 +1,29 @@ +/* ==================================================================== + 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; + +/** + * Should be implemented by any {@link Ptg} subclass that needs has an extern sheet index
+ * + * For POI internal use only + * + * @author Josh Micich + */ +public interface ExternSheetReferenceToken { + int getExternSheetIndex(); +} diff --git a/src/java/org/apache/poi/ss/formula/FormulaParser.java b/src/java/org/apache/poi/ss/formula/FormulaParser.java index dd7325a078..0b4b435dff 100644 --- a/src/java/org/apache/poi/ss/formula/FormulaParser.java +++ b/src/java/org/apache/poi/ss/formula/FormulaParser.java @@ -257,7 +257,7 @@ public final class FormulaParser { */ private Identifier parseIdentifier() { StringBuffer sb = new StringBuffer(); - if (!IsAlpha(look) && look != '\'') { + if (!IsAlpha(look) && look != '\'' && look != '[') { throw expected("Name"); } boolean isQuoted = look == '\''; @@ -276,7 +276,7 @@ public final class FormulaParser { } else { // allow for any sequence of dots and identifier chars // special case of two consecutive dots is best treated in the calling code - while (IsAlNum(look) || look == '.') { + while (IsAlNum(look) || look == '.' || look == '[' || look == ']') { sb.append(look); GetChar(); } @@ -368,7 +368,7 @@ public final class FormulaParser { // 3-D ref // this code assumes iden is a sheetName // TODO - handle ! - int externIdx = book.getExternalSheetIndex(iden.getName()); + int externIdx = getExternalSheetIndex(iden.getName()); String secondIden = parseUnquotedIdentifier(); AreaReference areaRef = parseArea(secondIden); if (areaRef == null) { @@ -418,6 +418,17 @@ public final class FormulaParser { + name + "' is not a range as expected"); } + private int getExternalSheetIndex(String name) { + if (name.charAt(0) == '[') { + // we have a sheet name qualified with workbook name e.g. '[MyData.xls]Sheet1' + int pos = name.lastIndexOf(']'); // safe because sheet names never have ']' + String wbName = name.substring(1, pos); + String sheetName = name.substring(pos+1); + return book.getExternalSheetIndex(wbName, sheetName); + } + return book.getExternalSheetIndex(name); + } + /** * @param name an 'identifier' like string (i.e. contains alphanums, and dots) * @return null if name cannot be split at a dot @@ -656,7 +667,7 @@ public final class FormulaParser { Match('}'); return arrayNode; } - if (IsAlpha(look) || look == '\''){ + if (IsAlpha(look) || look == '\'' || look == '['){ return parseFunctionReferenceOrName(); } // else - assume number diff --git a/src/java/org/apache/poi/ss/formula/FormulaParsingWorkbook.java b/src/java/org/apache/poi/ss/formula/FormulaParsingWorkbook.java index 69431c2c22..e9be7d1d39 100644 --- a/src/java/org/apache/poi/ss/formula/FormulaParsingWorkbook.java +++ b/src/java/org/apache/poi/ss/formula/FormulaParsingWorkbook.java @@ -32,6 +32,16 @@ public interface FormulaParsingWorkbook { */ EvaluationName getName(String name); - int getExternalSheetIndex(String sheetName); NameXPtg getNameXPtg(String name); + + /** + * gets the externSheet index for a sheet from this workbook + */ + int getExternalSheetIndex(String sheetName); + /** + * gets the externSheet index for a sheet from an external workbook + * @param workbookName e.g. "Budget.xls" + * @param sheetName a name of a sheet in that workbook + */ + int getExternalSheetIndex(String workbookName, String sheetName); } diff --git a/src/java/org/apache/poi/ss/formula/FormulaRenderingWorkbook.java b/src/java/org/apache/poi/ss/formula/FormulaRenderingWorkbook.java index ac95f4da0f..c9b95f6b1c 100644 --- a/src/java/org/apache/poi/ss/formula/FormulaRenderingWorkbook.java +++ b/src/java/org/apache/poi/ss/formula/FormulaRenderingWorkbook.java @@ -19,6 +19,7 @@ package org.apache.poi.ss.formula; import org.apache.poi.hssf.record.formula.NamePtg; import org.apache.poi.hssf.record.formula.NameXPtg; +import org.apache.poi.ss.formula.EvaluationWorkbook.ExternalSheet; /** * Abstracts a workbook for the purpose of converting formula to text.
@@ -29,6 +30,10 @@ import org.apache.poi.hssf.record.formula.NameXPtg; */ public interface FormulaRenderingWorkbook { + /** + * @return null if externSheetIndex refers to a sheet inside the current workbook + */ + ExternalSheet getExternalSheet(int externSheetIndex); String getSheetNameByExternSheet(int externSheetIndex); String resolveNameXText(NameXPtg nameXPtg); String getNameText(NamePtg namePtg); diff --git a/src/java/org/apache/poi/ss/formula/WorkbookEvaluator.java b/src/java/org/apache/poi/ss/formula/WorkbookEvaluator.java index e958649695..724cf2cf2b 100644 --- a/src/java/org/apache/poi/ss/formula/WorkbookEvaluator.java +++ b/src/java/org/apache/poi/ss/formula/WorkbookEvaluator.java @@ -63,6 +63,7 @@ import org.apache.poi.hssf.usermodel.HSSFCell; import org.apache.poi.hssf.usermodel.HSSFRow; import org.apache.poi.hssf.usermodel.HSSFSheet; import org.apache.poi.hssf.util.CellReference; +import org.apache.poi.ss.formula.EvaluationWorkbook.ExternalSheet; /** * Evaluates formula cells.

@@ -75,13 +76,14 @@ import org.apache.poi.hssf.util.CellReference; * * @author Josh Micich */ -public class WorkbookEvaluator { +public final class WorkbookEvaluator { private final EvaluationWorkbook _workbook; - private final EvaluationCache _cache; + private EvaluationCache _cache; private final IEvaluationListener _evaluationListener; private final Map _sheetIndexesBySheet; + private CollaboratingWorkbooksEnvironment _collaboratingWorkbookEnvironment; public WorkbookEvaluator(EvaluationWorkbook workbook) { this (workbook, null); @@ -91,6 +93,7 @@ public class WorkbookEvaluator { _evaluationListener = evaluationListener; _cache = new EvaluationCache(evaluationListener); _sheetIndexesBySheet = new IdentityHashMap(); + _collaboratingWorkbookEnvironment = CollaboratingWorkbooksEnvironment.EMPTY; } /** @@ -108,7 +111,22 @@ public class WorkbookEvaluator { System.out.println(s); } } + /* package */ void attachToEnvironment(CollaboratingWorkbooksEnvironment collaboratingWorkbooksEnvironment, EvaluationCache cache) { + _collaboratingWorkbookEnvironment = collaboratingWorkbooksEnvironment; + _cache = cache; + } + /* package */ CollaboratingWorkbooksEnvironment getEnvironment() { + return _collaboratingWorkbookEnvironment; + } + /* package */ void detachFromEnvironment() { + _collaboratingWorkbookEnvironment = CollaboratingWorkbooksEnvironment.EMPTY; + _cache = new EvaluationCache(_evaluationListener); + } + /* package */ IEvaluationListener getEvaluationListener() { + return _evaluationListener; + } + /** * Should be called whenever there are changes to input cells in the evaluated workbook. * Failure to call this method after changing cell values will cause incorrect behaviour @@ -130,7 +148,7 @@ public class WorkbookEvaluator { throw new IllegalArgumentException("value must not be null"); } int sheetIndex = getSheetIndex(sheet); - _cache.setValue(new CellLocation(sheetIndex, rowIndex, columnIndex), true, CellLocation.EMPTY_ARRAY, value); + _cache.setValue(new CellLocation(_workbook, sheetIndex, rowIndex, columnIndex), true, CellLocation.EMPTY_ARRAY, value); } /** @@ -139,13 +157,17 @@ public class WorkbookEvaluator { */ public void notifySetFormula(HSSFSheet sheet, int rowIndex, int columnIndex) { int sheetIndex = getSheetIndex(sheet); - _cache.setValue(new CellLocation(sheetIndex, rowIndex, columnIndex), false, CellLocation.EMPTY_ARRAY, null); + _cache.setValue(new CellLocation(_workbook, sheetIndex, rowIndex, columnIndex), false, CellLocation.EMPTY_ARRAY, null); } private int getSheetIndex(HSSFSheet sheet) { Integer result = (Integer) _sheetIndexesBySheet.get(sheet); if (result == null) { - result = new Integer(_workbook.getSheetIndex(sheet)); + int sheetIndex = _workbook.getSheetIndex(sheet); + if (sheetIndex < 0) { + throw new RuntimeException("Specified sheet from a different book"); + } + result = new Integer(sheetIndex); _sheetIndexesBySheet.put(sheet, result); } return result.intValue(); @@ -153,7 +175,7 @@ public class WorkbookEvaluator { public ValueEval evaluate(HSSFCell srcCell) { int sheetIndex = getSheetIndex(srcCell.getSheet()); - CellLocation cellLoc = new CellLocation(sheetIndex, srcCell.getRowIndex(), srcCell.getCellNum()); + CellLocation cellLoc = new CellLocation(_workbook, sheetIndex, srcCell.getRowIndex(), srcCell.getCellNum()); return internalEvaluate(srcCell, cellLoc, new EvaluationTracker(_cache)); } @@ -342,6 +364,20 @@ public class WorkbookEvaluator { } return operation.evaluate(ops, srcRowNum, (short)srcColNum); } + private SheetRefEvaluator createExternSheetRefEvaluator(EvaluationTracker tracker, + ExternSheetReferenceToken ptg) { + int externSheetIndex = ptg.getExternSheetIndex(); + ExternalSheet externalSheet = _workbook.getExternalSheet(externSheetIndex); + if (externalSheet != null) { + WorkbookEvaluator otherEvaluator = _collaboratingWorkbookEnvironment.getWorkbookEvaluator(externalSheet.getWorkbookName()); + EvaluationWorkbook otherBook = otherEvaluator._workbook; + int otherSheetIndex = otherBook.getSheetIndex(externalSheet.getSheetName()); + return new SheetRefEvaluator(otherEvaluator, tracker, otherBook, otherSheetIndex); + } + int otherSheetIndex = _workbook.convertFromExternSheetIndex(externSheetIndex); + return new SheetRefEvaluator(this, tracker, _workbook, otherSheetIndex); + + } /** * returns an appropriate Eval impl instance for the Ptg. The Ptg must be @@ -350,6 +386,8 @@ public class WorkbookEvaluator { * passed here! */ private Eval getEvalForPtg(Ptg ptg, int sheetIndex, EvaluationTracker tracker) { + // consider converting all these (ptg instanceof XxxPtg) expressions to (ptg.getClass() == XxxPtg.class) + if (ptg instanceof NamePtg) { // named ranges, macro functions NamePtg namePtg = (NamePtg) ptg; @@ -388,14 +426,12 @@ public class WorkbookEvaluator { } if (ptg instanceof Ref3DPtg) { Ref3DPtg refPtg = (Ref3DPtg) ptg; - int otherSheetIndex = _workbook.convertFromExternSheetIndex(refPtg.getExternSheetIndex()); - SheetRefEvaluator sre = new SheetRefEvaluator(this, tracker, _workbook, otherSheetIndex); + SheetRefEvaluator sre = createExternSheetRefEvaluator(tracker, refPtg); return new LazyRefEval(refPtg, sre); } if (ptg instanceof Area3DPtg) { Area3DPtg aptg = (Area3DPtg) ptg; - int otherSheetIndex = _workbook.convertFromExternSheetIndex(aptg.getExternSheetIndex()); - SheetRefEvaluator sre = new SheetRefEvaluator(this, tracker, _workbook, otherSheetIndex); + SheetRefEvaluator sre = createExternSheetRefEvaluator(tracker, aptg); return new LazyAreaEval(aptg, sre); } SheetRefEvaluator sre = new SheetRefEvaluator(this, tracker, _workbook, sheetIndex); @@ -435,7 +471,7 @@ public class WorkbookEvaluator { } else { cell = row.getCell(columnIndex); } - CellLocation cellLoc = new CellLocation(sheetIndex, rowIndex, columnIndex); + CellLocation cellLoc = new CellLocation(_workbook, sheetIndex, rowIndex, columnIndex); tracker.acceptDependency(cellLoc); return internalEvaluate(cell, cellLoc, tracker); } diff --git a/src/testcases/org/apache/poi/hssf/data/multibookFormulaA.xls b/src/testcases/org/apache/poi/hssf/data/multibookFormulaA.xls new file mode 100644 index 0000000000000000000000000000000000000000..b844fc1dba0318dfbe99d9db84d7f532b1d97bd4 GIT binary patch literal 16896 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 N{+Y;fqdlAD{~O(|ILQD2 literal 0 HcmV?d00001 diff --git a/src/testcases/org/apache/poi/hssf/data/multibookFormulaB.xls b/src/testcases/org/apache/poi/hssf/data/multibookFormulaB.xls new file mode 100644 index 0000000000000000000000000000000000000000..3e4708d6c9b523851cade333ac9d40d34623b231 GIT binary patch literal 16896 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 literal 0 HcmV?d00001 diff --git a/src/testcases/org/apache/poi/hssf/model/TestFormulaParser.java b/src/testcases/org/apache/poi/hssf/model/TestFormulaParser.java index 29de45405f..53061a228b 100644 --- a/src/testcases/org/apache/poi/hssf/model/TestFormulaParser.java +++ b/src/testcases/org/apache/poi/hssf/model/TestFormulaParser.java @@ -43,11 +43,13 @@ import org.apache.poi.hssf.record.formula.NumberPtg; import org.apache.poi.hssf.record.formula.PercentPtg; import org.apache.poi.hssf.record.formula.PowerPtg; import org.apache.poi.hssf.record.formula.Ptg; +import org.apache.poi.hssf.record.formula.Ref3DPtg; import org.apache.poi.hssf.record.formula.RefPtg; import org.apache.poi.hssf.record.formula.StringPtg; import org.apache.poi.hssf.record.formula.SubtractPtg; import org.apache.poi.hssf.record.formula.UnaryMinusPtg; import org.apache.poi.hssf.record.formula.UnaryPlusPtg; +import org.apache.poi.hssf.usermodel.FormulaExtractor; import org.apache.poi.hssf.usermodel.HSSFCell; import org.apache.poi.hssf.usermodel.HSSFErrorConstants; import org.apache.poi.hssf.usermodel.HSSFEvaluationWorkbook; @@ -913,4 +915,33 @@ public final class TestFormulaParser extends TestCase { assertEquals("'true'!B2", cell.getCellFormula()); } + + public void testParseExternalWorkbookReference() { + HSSFWorkbook wbA = HSSFTestDataSamples.openSampleWorkbook("multibookFormulaA.xls"); + HSSFCell cell = wbA.getSheetAt(0).getRow(0).getCell(0); + + // make sure formula in sample is as expected + assertEquals("[multibookFormulaB.xls]BSheet1!B1", cell.getCellFormula()); + Ptg[] expectedPtgs = FormulaExtractor.getPtgs(cell); + confirmSingle3DRef(expectedPtgs, 1); + + // now try (re-)parsing the formula + Ptg[] actualPtgs = HSSFFormulaParser.parse("[multibookFormulaB.xls]BSheet1!B1", wbA); + confirmSingle3DRef(actualPtgs, 1); // externalSheetIndex 1 -> BSheet1 + + // try parsing a formula pointing to a different external sheet + Ptg[] otherPtgs = HSSFFormulaParser.parse("[multibookFormulaB.xls]AnotherSheet!B1", wbA); + confirmSingle3DRef(otherPtgs, 0); // externalSheetIndex 0 -> AnotherSheet + + // try setting the same formula in a cell + cell.setCellFormula("[multibookFormulaB.xls]AnotherSheet!B1"); + assertEquals("[multibookFormulaB.xls]AnotherSheet!B1", cell.getCellFormula()); + } + private static void confirmSingle3DRef(Ptg[] ptgs, int expectedExternSheetIndex) { + assertEquals(1, ptgs.length); + Ptg ptg0 = ptgs[0]; + assertEquals(Ref3DPtg.class, ptg0.getClass()); + assertEquals(expectedExternSheetIndex, ((Ref3DPtg)ptg0).getExternSheetIndex()); + } + } diff --git a/src/testcases/org/apache/poi/hssf/record/TestSupBookRecord.java b/src/testcases/org/apache/poi/hssf/record/TestSupBookRecord.java index 3727989efb..e2a61dde79 100644 --- a/src/testcases/org/apache/poi/hssf/record/TestSupBookRecord.java +++ b/src/testcases/org/apache/poi/hssf/record/TestSupBookRecord.java @@ -78,7 +78,7 @@ public final class TestSupBookRecord extends TestCase { assertEquals( 34, record.getRecordSize() ); //sid+size+data - assertEquals("testURL", record.getURL().getString()); + assertEquals("testURL", record.getURL()); UnicodeString[] sheetNames = record.getSheetNames(); assertEquals(2, sheetNames.length); assertEquals("Sheet1", sheetNames[0].getString()); diff --git a/src/testcases/org/apache/poi/ss/formula/TestWorkbookEvaluator.java b/src/testcases/org/apache/poi/ss/formula/TestWorkbookEvaluator.java index 20cc9c1787..0d3e93ee7a 100644 --- a/src/testcases/org/apache/poi/ss/formula/TestWorkbookEvaluator.java +++ b/src/testcases/org/apache/poi/ss/formula/TestWorkbookEvaluator.java @@ -19,6 +19,7 @@ package org.apache.poi.ss.formula; import junit.framework.TestCase; +import org.apache.poi.hssf.HSSFTestDataSamples; import org.apache.poi.hssf.record.formula.AreaErrPtg; import org.apache.poi.hssf.record.formula.AttrPtg; import org.apache.poi.hssf.record.formula.DeletedArea3DPtg; @@ -29,36 +30,40 @@ import org.apache.poi.hssf.record.formula.RefErrorPtg; import org.apache.poi.hssf.record.formula.eval.ErrorEval; import org.apache.poi.hssf.record.formula.eval.NumberEval; import org.apache.poi.hssf.record.formula.eval.ValueEval; +import org.apache.poi.hssf.usermodel.HSSFCell; +import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator; +import org.apache.poi.hssf.usermodel.HSSFSheet; +import org.apache.poi.hssf.usermodel.HSSFWorkbook; /** - * Tests {@link WorkbookEvaluator}. + * Tests {@link WorkbookEvaluator}. * * @author Josh Micich */ public class TestWorkbookEvaluator extends TestCase { - + /** * Make sure that the evaluator can directly handle tAttrSum (instead of relying on re-parsing - * the whole formula which converts tAttrSum to tFuncVar("SUM") ) + * the whole formula which converts tAttrSum to tFuncVar("SUM") ) */ public void testAttrSum() { - + Ptg[] ptgs = { new IntPtg(42), AttrPtg.SUM, }; - + ValueEval result = new WorkbookEvaluator(null).evaluateFormula(0, 0, 0, ptgs, null); assertEquals(42, ((NumberEval)result).getNumberValue(), 0.0); } - + /** * Make sure that the evaluator can directly handle (deleted) ref error tokens - * (instead of relying on re-parsing the whole formula which converts these - * to the error constant #REF! ) + * (instead of relying on re-parsing the whole formula which converts these + * to the error constant #REF! ) */ public void testRefErr() { - + confirmRefErr(new RefErrorPtg()); confirmRefErr(new AreaErrPtg()); confirmRefErr(new DeletedRef3DPtg(0)); @@ -68,25 +73,82 @@ public class TestWorkbookEvaluator extends TestCase { Ptg[] ptgs = { ptg, }; - + ValueEval result = new WorkbookEvaluator(null).evaluateFormula(0, 0, 0, ptgs, null); assertEquals(ErrorEval.REF_INVALID, result); } - + /** * Make sure that the evaluator can directly handle tAttrSum (instead of relying on re-parsing - * the whole formula which converts tAttrSum to tFuncVar("SUM") ) + * the whole formula which converts tAttrSum to tFuncVar("SUM") ) */ public void testMemFunc() { - + Ptg[] ptgs = { new IntPtg(42), AttrPtg.SUM, }; - + ValueEval result = new WorkbookEvaluator(null).evaluateFormula(0, 0, 0, ptgs, null); assertEquals(42, ((NumberEval)result).getNumberValue(), 0.0); } - - + + + public void testEvaluateMultipleWorkbooks() { + HSSFWorkbook wbA = HSSFTestDataSamples.openSampleWorkbook("multibookFormulaA.xls"); + HSSFWorkbook wbB = HSSFTestDataSamples.openSampleWorkbook("multibookFormulaB.xls"); + + HSSFFormulaEvaluator evaluatorA = new HSSFFormulaEvaluator(wbA); + HSSFFormulaEvaluator evaluatorB = new HSSFFormulaEvaluator(wbB); + + // Hook up the workbook evaluators to enable evaluation of formulas across books + String[] bookNames = { "multibookFormulaA.xls", "multibookFormulaB.xls", }; + HSSFFormulaEvaluator[] evaluators = { evaluatorA, evaluatorB, }; + HSSFFormulaEvaluator.setupEnvironment(bookNames, evaluators); + + HSSFCell cell; + + HSSFSheet aSheet1 = wbA.getSheetAt(0); + HSSFSheet bSheet1 = wbB.getSheetAt(0); + + // Simple case - single link from wbA to wbB + confirmFormula(wbA, 0, 0, 0, "[multibookFormulaB.xls]BSheet1!B1"); + cell = aSheet1.getRow(0).getCell(0); + confirmEvaluation(35, evaluatorA, cell); + + + // more complex case - back link into wbA + // [wbA]ASheet1!A2 references (among other things) [wbB]BSheet1!B2 + confirmFormula(wbA, 0, 1, 0, "[multibookFormulaB.xls]BSheet1!$B$2+2*A3"); + // [wbB]BSheet1!B2 references (among other things) [wbA]AnotherSheet!A1:B2 + confirmFormula(wbB, 0, 1, 1, "SUM([multibookFormulaA.xls]AnotherSheet!$A$1:$B$2)+B3"); + + cell = aSheet1.getRow(1).getCell(0); + confirmEvaluation(264, evaluatorA, cell); + + // change [wbB]BSheet1!B3 (from 50 to 60) + bSheet1.getRow(2).getCell(1).setCellValue(60); + evaluatorB.setCachedPlainValue(bSheet1, 2, 1, new NumberEval(60)); + confirmEvaluation(274, evaluatorA, cell); + + // change [wbA]ASheet1!A3 (from 100 to 80) + aSheet1.getRow(2).getCell(0).setCellValue(80); + evaluatorA.setCachedPlainValue(aSheet1, 2, 0, new NumberEval(80)); + confirmEvaluation(234, evaluatorA, cell); + + // change [wbA]AnotherSheet!A1 (from 2 to 3) + wbA.getSheetAt(1).getRow(0).getCell(0).setCellValue(3); + evaluatorA.setCachedPlainValue(wbA.getSheetAt(1), 0, 0, new NumberEval(3)); + confirmEvaluation(235, evaluatorA, cell); + } + + private static void confirmEvaluation(double expectedValue, HSSFFormulaEvaluator fe, HSSFCell cell) { + assertEquals(expectedValue, fe.evaluate(cell).getNumberValue(), 0.0); + } + + private static void confirmFormula(HSSFWorkbook wb, int sheetIndex, int rowIndex, int columnIndex, + String expectedFormula) { + HSSFCell cell = wb.getSheetAt(sheetIndex).getRow(rowIndex).getCell(columnIndex); + assertEquals(expectedFormula, cell.getCellFormula()); + } } -- 2.39.5