<changes>
<release version="3.7-SNAPSHOT" date="2010-??-??">
+ <action dev="POI-DEVELOPERS" type="add">48996 - initial support for External Name References in HSSF formula evaluation</action>
<action dev="POI-DEVELOPERS" type="fix">46664 - fix up Tab IDs when adding new sheets, so that print areas don't end up invalid</action>
<action dev="POI-DEVELOPERS" type="fix">45269 - improve replaceText on HWPF ranges</action>
<action dev="POI-DEVELOPERS" type="fix">47815 - correct documentation on what happens when you request a String from a non-string Formula cell</action>
case ExtSSTRecord.sid: return new ExtSSTRecord(in);
case ExtendedFormatRecord.sid: return new ExtendedFormatRecord(in);
case ExternSheetRecord.sid: return new ExternSheetRecord(in);
+ case ExternalNameRecord.sid: return new ExternalNameRecord(in);
case FeatRecord.sid: return new FeatRecord(in);
case FeatHdrRecord.sid: return new FeatHdrRecord(in);
case FilePassRecord.sid: return new FilePassRecord(in);
import org.apache.poi.hssf.record.formula.FormulaShifter;
import org.apache.poi.hssf.record.formula.Ptg;
import org.apache.poi.hssf.util.HSSFColor;
+import org.apache.poi.ss.formula.EvaluationWorkbook.ExternalName;
import org.apache.poi.ss.formula.EvaluationWorkbook.ExternalSheet;
import org.apache.poi.util.Internal;
import org.apache.poi.util.POILogFactory;
}
return new ExternalSheet(extNames[0], extNames[1]);
}
+ public ExternalName getExternalName(int externSheetIndex, int externNameIndex) {
+ String nameName = linkTable.resolveNameXText(externSheetIndex, externNameIndex);
+ if(nameName == null) {
+ return null;
+ }
+ int ix = linkTable.resolveNameXIx(externSheetIndex, externNameIndex);
+ return new ExternalName(nameName, externNameIndex, ix);
+ }
/**
* Finds the sheet index for a particular external sheet number.
public String getNameText(int definedNameIndex) {
return _externalNameRecords[definedNameIndex].getText();
}
+
+ public int getNameIx(int definedNameIndex) {
+ return _externalNameRecords[definedNameIndex].getIx();
+ }
/**
* Performs case-insensitive search
if (!ebr.isExternalReferences()) {
return null;
}
+ // Sheet name only applies if not a global reference
int shIx = _externSheetRecord.getFirstSheetIndexFromRefIndex(extRefIndex);
- String usSheetName = ebr.getSheetNames()[shIx];
+ String usSheetName = null;
+ if(shIx >= 0) {
+ usSheetName = ebr.getSheetNames()[shIx];
+ }
return new String[] {
ebr.getURL(),
usSheetName,
int extBookIndex = _externSheetRecord.getExtbookIndexFromRefIndex(refIndex);
return _externalBookBlocks[extBookIndex].getNameText(definedNameIndex);
}
+ public int resolveNameXIx(int refIndex, int definedNameIndex) {
+ int extBookIndex = _externSheetRecord.getExtbookIndexFromRefIndex(refIndex);
+ return _externalBookBlocks[extBookIndex].getNameIx(definedNameIndex);
+ }
public NameXPtg getNameXPtg(String name) {
// first find any external book block that contains the name:
private short field_1_option_flag;
- private int field_2_not_used;
+ private short field_2_ixals;
+ private short field_3_not_used;
private String field_4_name;
private Formula field_5_name_definition;
public String getText() {
return field_4_name;
}
+
+ /**
+ * If this is a local name, then this is the (1 based)
+ * index of the name of the Sheet this refers to, as
+ * defined in the preceeding {@link SupBookRecord}.
+ * If it isn't a local name, then it must be zero.
+ */
+ public short getIx() {
+ return field_2_ixals;
+ }
protected int getDataSize(){
int result = 2 + 4; // short and int
public void serialize(LittleEndianOutput out) {
out.writeShort(field_1_option_flag);
- out.writeInt(field_2_not_used);
+ out.writeShort(field_2_ixals);
+ out.writeShort(field_3_not_used);
- out.writeByte(field_4_name.length());
- StringUtil.writeUnicodeStringFlagAndData(out, field_4_name);
+ out.writeByte(field_4_name.length());
+ StringUtil.writeUnicodeStringFlagAndData(out, field_4_name);
if(!isOLELink() && !isStdDocumentNameIdentifier()){
if(isAutomaticLink()){
public ExternalNameRecord(RecordInputStream in) {
field_1_option_flag = in.readShort();
- field_2_not_used = in.readInt();
+ field_2_ixals = in.readShort();
+ field_3_not_used = in.readShort();
int numChars = in.readUByte();
field_4_name = StringUtil.readUnicodeString(in, numChars);
public String toString() {
StringBuffer sb = new StringBuffer();
- sb.append(getClass().getName()).append(" [EXTERNALNAME ");
- sb.append(" ").append(field_4_name);
- sb.append(" ix=").append(field_2_not_used);
- sb.append("]");
+ sb.append("[EXTERNALNAME]\n");
+ sb.append(" .ix = ").append(field_2_ixals).append("\n");
+ sb.append(" .name = ").append(field_4_name).append("\n");
+ if(field_5_name_definition != null) {
+ sb.append(" .formula = ").append(field_5_name_definition).append("\n");
+ }
+ sb.append("[/EXTERNALNAME]\n");
return sb.toString();
}
}
public String toFormulaString() {
throw new RuntimeException("3D references need a workbook to determine formula text");
}
-
+
+ public String toString(){
+ String retValue = "NameXPtg:[sheetRefIndex:" + _sheetRefIndex +
+ " , nameNumber:" + _nameNumber + "]" ;
+ return retValue;
+ }
+
public byte getDefaultOperandClass() {
return Ptg.CLASS_VALUE;
}
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.EvaluationWorkbook.ExternalName;
/**
* Internal POI use only
public ExternalSheet getExternalSheet(int externSheetIndex) {
return _iBook.getExternalSheet(externSheetIndex);
}
+
+ public ExternalName getExternalName(int externSheetIndex, int externNameIndex) {
+ return _iBook.getExternalName(externSheetIndex, externNameIndex);
+ }
public String resolveNameXText(NameXPtg n) {
return _iBook.resolveNameXText(n.getSheetRefIndex(), n.getNameIndex());
*/
ExternalSheet getExternalSheet(int externSheetIndex);
int convertFromExternSheetIndex(int externSheetIndex);
+ ExternalName getExternalName(int externSheetIndex, int externNameIndex);
EvaluationName getName(NamePtg namePtg);
String resolveNameXText(NameXPtg ptg);
Ptg[] getFormulaTokens(EvaluationCell cell);
return _sheetName;
}
}
+ class ExternalName {
+ private final String _nameName;
+ private final int _nameNumber;
+ private final int _ix;
+
+ public ExternalName(String nameName, int nameNumber, int ix) {
+ _nameName = nameName;
+ _nameNumber = nameNumber;
+ _ix = ix;
+ }
+ public String getName() {
+ return _nameName;
+ }
+ public int getNumber() {
+ return _nameNumber;
+ }
+ public int getIx() {
+ return _ix;
+ }
+ }
}
package org.apache.poi.ss.formula;
+import org.apache.poi.hssf.record.formula.Area3DPtg;
+import org.apache.poi.hssf.record.formula.NameXPtg;
+import org.apache.poi.hssf.record.formula.Ptg;
+import org.apache.poi.hssf.record.formula.Ref3DPtg;
import org.apache.poi.hssf.record.formula.eval.*;
import org.apache.poi.hssf.record.formula.functions.FreeRefFunction;
import org.apache.poi.ss.SpreadsheetVersion;
import org.apache.poi.ss.formula.CollaboratingWorkbooksEnvironment.WorkbookNotFoundException;
+import org.apache.poi.ss.formula.EvaluationWorkbook.ExternalName;
import org.apache.poi.ss.formula.EvaluationWorkbook.ExternalSheet;
import org.apache.poi.ss.util.CellReference;
import org.apache.poi.ss.util.CellReference.NameType;
SheetRefEvaluator sre = createExternSheetRefEvaluator(extSheetIndex);
return new LazyAreaEval(firstRowIndex, firstColumnIndex, lastRowIndex, lastColumnIndex, sre);
}
+ public ValueEval getNameXEval(NameXPtg nameXPtg) {
+ ExternalSheet externSheet = _workbook.getExternalSheet(nameXPtg.getSheetRefIndex());
+ if(externSheet == null)
+ return new NameXEval(nameXPtg);
+ String workbookName = externSheet.getWorkbookName();
+ ExternalName externName = _workbook.getExternalName(
+ nameXPtg.getSheetRefIndex(),
+ nameXPtg.getNameIndex()
+ );
+ try{
+ WorkbookEvaluator refWorkbookEvaluator = _bookEvaluator.getOtherWorkbookEvaluator(workbookName);
+ EvaluationName evaluationName = refWorkbookEvaluator.getName(externName.getName(),externName.getIx()-1);
+ if(evaluationName != null && evaluationName.hasFormula()){
+ if (evaluationName.getNameDefinition().length > 1) {
+ throw new RuntimeException("Complex name formulas not supported yet");
+ }
+ Ptg ptg = evaluationName.getNameDefinition()[0];
+ if(ptg instanceof Ref3DPtg){
+ Ref3DPtg ref3D = (Ref3DPtg)ptg;
+ int sheetIndex = refWorkbookEvaluator.getSheetIndexByExternIndex(ref3D.getExternSheetIndex());
+ String sheetName = refWorkbookEvaluator.getSheetName(sheetIndex);
+ SheetRefEvaluator sre = createExternSheetRefEvaluator(workbookName, sheetName);
+ return new LazyRefEval(ref3D.getRow(), ref3D.getColumn(), sre);
+ }else if(ptg instanceof Area3DPtg){
+ Area3DPtg area3D = (Area3DPtg)ptg;
+ int sheetIndex = refWorkbookEvaluator.getSheetIndexByExternIndex(area3D.getExternSheetIndex());
+ String sheetName = refWorkbookEvaluator.getSheetName(sheetIndex);
+ SheetRefEvaluator sre = createExternSheetRefEvaluator(workbookName, sheetName);
+ return new LazyAreaEval(area3D.getFirstRow(), area3D.getFirstColumn(), area3D.getLastRow(), area3D.getLastColumn(), sre);
+ }
+ }
+ return ErrorEval.REF_INVALID;
+ }catch(WorkbookNotFoundException wnfe){
+ return ErrorEval.REF_INVALID;
+ }
+ }
}
import org.apache.poi.hssf.record.formula.functions.FreeRefFunction;
import org.apache.poi.hssf.record.formula.functions.IfFunc;
import org.apache.poi.hssf.record.formula.udf.UDFFinder;
+import org.apache.poi.hssf.usermodel.HSSFEvaluationWorkbook;
import org.apache.poi.hssf.util.CellReference;
import org.apache.poi.ss.formula.CollaboratingWorkbooksEnvironment.WorkbookNotFoundException;
import org.apache.poi.ss.formula.eval.NotImplementedException;
/* package */ EvaluationSheet getSheet(int sheetIndex) {
return _workbook.getSheet(sheetIndex);
}
+
+ /* package */ EvaluationName getName(String name, int sheetIndex) {
+ NamePtg namePtg = null;
+ if(_workbook instanceof HSSFEvaluationWorkbook){
+ namePtg =((HSSFEvaluationWorkbook)_workbook).getName(name, sheetIndex).createPtg();
+ }
+
+ if(namePtg == null) {
+ return null;
+ } else {
+ return _workbook.getName(namePtg);
+ }
+ }
private static boolean isDebugLogEnabled() {
return false;
}
return result.intValue();
}
+
+ /* package */ int getSheetIndexByExternIndex(int externSheetIndex) {
+ return _workbook.convertFromExternSheetIndex(externSheetIndex);
+ }
/**
throw new RuntimeException("Don't now how to evalate name '" + nameRecord.getNameText() + "'");
}
if (ptg instanceof NameXPtg) {
- return new NameXEval(((NameXPtg) ptg));
+ return ec.getNameXEval(((NameXPtg) ptg));
}
if (ptg instanceof IntPtg) {
public EvaluationSheet getSheet(int sheetIndex) {
return getSharedSheet(getSheetName(sheetIndex));
}
+
+ public ExternalName getExternalName(int externSheetIndex, int externNameIndex) {
+ return _masterBook.getExternalName(externSheetIndex, externNameIndex);
+ }
public int getSheetIndex(EvaluationSheet sheet) {
if (sheet instanceof ForkedEvaluationSheet) {
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.EvaluationWorkbook.ExternalName;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTDefinedName;
/**
public String getSheetName(int sheetIndex) {
return _uBook.getSheetName(sheetIndex);
}
+
+ public ExternalName getExternalName(int externSheetIndex, int externNameIndex) {
+ throw new RuntimeException("Not implemented yet");
+ }
public NameXPtg getNameXPtg(String name) {
// may require to return null to make tests pass
--- /dev/null
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hssf.record.formula;
+
+
+import junit.framework.TestCase;
+
+import org.apache.poi.hssf.HSSFTestDataSamples;
+import org.apache.poi.hssf.usermodel.HSSFCell;
+import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator;
+import org.apache.poi.hssf.usermodel.HSSFWorkbook;
+import org.apache.poi.hssf.util.CellReference;
+/**
+ * Tests for proper calculation of named ranges from external workbooks.
+ *
+ *
+ * @author Stephen Wolke (smwolke at geistig.com)
+ */
+public final class TestExternalNameReference extends TestCase {
+ double MARKUP_COST = 1.9d;
+ double MARKUP_COST_1 = 1.8d;
+ double MARKUP_COST_2 = 1.5d;
+ double PART_COST = 12.3d;
+ double NEW_QUANT = 7.0d;
+ double NEW_PART_COST = 15.3d;
+ /**
+ * tests <tt>NameXPtg for external cell reference by name</tt> and logic in Workbook below that
+ */
+ public void testReadCalcSheet() {
+ try{
+ HSSFWorkbook wb = HSSFTestDataSamples.openSampleWorkbook("XRefCalc.xls");
+ assertEquals("Sheet1!$A$2", wb.getName("QUANT").getRefersToFormula());
+ assertEquals("Sheet1!$B$2", wb.getName("PART").getRefersToFormula());
+ assertEquals("x123",wb.getSheet("Sheet1").getRow(1).getCell(1).getStringCellValue());
+ assertEquals("Sheet1!$C$2", wb.getName("UNITCOST").getRefersToFormula());
+ CellReference cellRef = new CellReference(wb.getName("UNITCOST").getRefersToFormula());
+ HSSFCell cell = wb.getSheet(cellRef.getSheetName()).getRow(cellRef.getRow()).getCell((int)cellRef.getCol());
+ assertEquals("VLOOKUP(PART,COSTS,2,FALSE)",cell.getCellFormula());
+ assertEquals("Sheet1!$D$2", wb.getName("COST").getRefersToFormula());
+ cellRef = new CellReference(wb.getName("COST").getRefersToFormula());
+ cell = wb.getSheet(cellRef.getSheetName()).getRow(cellRef.getRow()).getCell((int)cellRef.getCol());
+ assertEquals("UNITCOST*Quant",cell.getCellFormula());
+ assertEquals("Sheet1!$E$2", wb.getName("TOTALCOST").getRefersToFormula());
+ cellRef = new CellReference(wb.getName("TOTALCOST").getRefersToFormula());
+ cell = wb.getSheet(cellRef.getSheetName()).getRow(cellRef.getRow()).getCell((int)cellRef.getCol());
+ assertEquals("Cost*Markup_Cost",cell.getCellFormula());
+ }catch(Exception e){
+ fail();
+ }
+ }
+
+ public void testReadReferencedSheet() {
+ try{
+ HSSFWorkbook wb = HSSFTestDataSamples.openSampleWorkbook("XRefCalcData.xls");
+ assertEquals("CostSheet!$A$2:$B$3", wb.getName("COSTS").getRefersToFormula());
+ assertEquals("x123",wb.getSheet("CostSheet").getRow(1).getCell(0).getStringCellValue());
+ assertEquals(PART_COST,wb.getSheet("CostSheet").getRow(1).getCell(1).getNumericCellValue());
+ assertEquals("MarkupSheet!$B$1", wb.getName("Markup_Cost").getRefersToFormula());
+ assertEquals(MARKUP_COST_1,wb.getSheet("MarkupSheet").getRow(0).getCell(1).getNumericCellValue());
+ }catch(Exception e){
+ fail();
+ }
+ }
+
+ public void testEvaluate() throws Exception {
+ HSSFWorkbook wb = HSSFTestDataSamples.openSampleWorkbook("XRefCalc.xls");
+ HSSFWorkbook wb2 = HSSFTestDataSamples.openSampleWorkbook("XRefCalcData.xls");
+ CellReference cellRef = new CellReference(wb.getName("QUANT").getRefersToFormula());
+ HSSFCell cell = wb.getSheet(cellRef.getSheetName()).getRow(cellRef.getRow()).getCell((int)cellRef.getCol());
+ cell.setCellValue(NEW_QUANT);
+ cell = wb2.getSheet("CostSheet").getRow(1).getCell(1);
+ cell.setCellValue(NEW_PART_COST);
+ HSSFFormulaEvaluator evaluator = new HSSFFormulaEvaluator(wb);
+ HSSFFormulaEvaluator evaluatorCost = new HSSFFormulaEvaluator(wb2);
+ String[] bookNames = { "XRefCalc.xls", "XRefCalcData.xls" };
+ HSSFFormulaEvaluator[] evaluators = { evaluator, evaluatorCost, };
+ HSSFFormulaEvaluator.setupEnvironment(bookNames, evaluators);
+ cellRef = new CellReference(wb.getName("UNITCOST").getRefersToFormula());
+ HSSFCell uccell = wb.getSheet(cellRef.getSheetName()).getRow(cellRef.getRow()).getCell((int)cellRef.getCol());
+ cellRef = new CellReference(wb.getName("COST").getRefersToFormula());
+ HSSFCell ccell = wb.getSheet(cellRef.getSheetName()).getRow(cellRef.getRow()).getCell((int)cellRef.getCol());
+ cellRef = new CellReference(wb.getName("TOTALCOST").getRefersToFormula());
+ HSSFCell tccell = wb.getSheet(cellRef.getSheetName()).getRow(cellRef.getRow()).getCell((int)cellRef.getCol());
+ evaluator.evaluateFormulaCell(uccell);
+ evaluator.evaluateFormulaCell(ccell);
+ evaluator.evaluateFormulaCell(tccell);
+ assertEquals(NEW_PART_COST, uccell.getNumericCellValue());
+ assertEquals(NEW_PART_COST*NEW_QUANT, ccell.getNumericCellValue());
+ assertEquals(NEW_PART_COST*NEW_QUANT*MARKUP_COST_2, tccell.getNumericCellValue());
+ }
+}
assertEquals(3, evalCount);
assertEquals(2.0, ((NumberEval)ve).getNumberValue(), 0D);
}
+
+ /**
+ * Ensures that we can handle NameXPtgs in the formulas
+ * we parse.
+ */
+ public void testXRefs() throws Exception {
+ HSSFWorkbook wb = HSSFTestDataSamples.openSampleWorkbook("XRefCalc.xls");
+ HSSFWorkbook wbData = HSSFTestDataSamples.openSampleWorkbook("XRefCalcData.xls");
+ Cell cell;
+
+ // VLookup on a name in another file
+ cell = wb.getSheetAt(0).getRow(1).getCell(2);
+ assertEquals(Cell.CELL_TYPE_FORMULA, cell.getCellType());
+ assertEquals(Cell.CELL_TYPE_NUMERIC, cell.getCachedFormulaResultType());
+ assertEquals(12.30, cell.getNumericCellValue(), 0.0001);
+ // WARNING - this is wrong!
+ // The file name should be showing, but bug #45970 is fixed
+ // we seem to loose it
+ assertEquals("VLOOKUP(PART,COSTS,2,FALSE)", cell.getCellFormula());
+
+
+ // Simple reference to a name in another file
+ cell = wb.getSheetAt(0).getRow(1).getCell(4);
+ assertEquals(Cell.CELL_TYPE_FORMULA, cell.getCellType());
+ assertEquals(Cell.CELL_TYPE_NUMERIC, cell.getCachedFormulaResultType());
+ assertEquals(36.90, cell.getNumericCellValue(), 0.0001);
+ // WARNING - this is wrong!
+ // The file name should be showing, but bug #45970 is fixed
+ // we seem to loose it
+ assertEquals("Cost*Markup_Cost", cell.getCellFormula());
+
+
+ // Evaluate the cells
+ HSSFFormulaEvaluator eval = new HSSFFormulaEvaluator(wb);
+ HSSFFormulaEvaluator.setupEnvironment(
+ new String[] { "XRefCalc.xls", "XRefCalcData.xls" },
+ new HSSFFormulaEvaluator[] {
+ eval,
+ new HSSFFormulaEvaluator(wbData)
+ }
+ );
+ eval.evaluateFormulaCell(
+ wb.getSheetAt(0).getRow(1).getCell(2)
+ );
+ eval.evaluateFormulaCell(
+ wb.getSheetAt(0).getRow(1).getCell(4)
+ );
+
+
+ // Re-check VLOOKUP one
+ cell = wb.getSheetAt(0).getRow(1).getCell(2);
+ assertEquals(Cell.CELL_TYPE_FORMULA, cell.getCellType());
+ assertEquals(Cell.CELL_TYPE_NUMERIC, cell.getCachedFormulaResultType());
+ assertEquals(12.30, cell.getNumericCellValue(), 0.0001);
+
+ // Re-check ref one
+ cell = wb.getSheetAt(0).getRow(1).getCell(4);
+ assertEquals(Cell.CELL_TYPE_FORMULA, cell.getCellType());
+ assertEquals(Cell.CELL_TYPE_NUMERIC, cell.getCachedFormulaResultType());
+ assertEquals(36.90, cell.getNumericCellValue(), 0.0001);
+ }
}