Apply, with a few tweaks, the patch from bug #48996 - initial support for External Name References in HSSF formula evaluation

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@953395 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Nick Burch 2010-06-10 17:07:06 +00:00
parent bf4e6ff464
commit e92c009814
16 changed files with 318 additions and 12 deletions

View File

@ -34,6 +34,7 @@
<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>

View File

@ -162,6 +162,7 @@ public final class BiffViewer {
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);

View File

@ -81,6 +81,7 @@ import org.apache.poi.hssf.record.formula.NameXPtg;
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;
@ -1771,6 +1772,14 @@ public final class InternalWorkbook {
}
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.

View File

@ -125,6 +125,10 @@ final class LinkTable {
return _externalNameRecords[definedNameIndex].getText();
}
public int getNameIx(int definedNameIndex) {
return _externalNameRecords[definedNameIndex].getIx();
}
/**
* Performs case-insensitive search
* @return -1 if not found
@ -316,8 +320,12 @@ final class LinkTable {
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,
@ -419,6 +427,10 @@ final class LinkTable {
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:

View File

@ -41,7 +41,8 @@ public final class ExternalNameRecord extends StandardRecord {
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;
@ -97,6 +98,16 @@ public final class ExternalNameRecord extends StandardRecord {
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
result += StringUtil.getEncodedSize(field_4_name) - 1; //size is byte, not short
@ -114,7 +125,8 @@ public final class ExternalNameRecord extends StandardRecord {
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);
@ -133,7 +145,8 @@ public final class ExternalNameRecord extends StandardRecord {
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);
@ -166,10 +179,13 @@ public final class ExternalNameRecord extends StandardRecord {
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();
}
}

View File

@ -74,6 +74,12 @@ public final class NameXPtg extends OperandPtg implements WorkbookDependentFormu
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;
}

View File

@ -33,6 +33,7 @@ import org.apache.poi.ss.formula.FormulaParseException;
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
@ -108,6 +109,10 @@ public final class HSSFEvaluationWorkbook implements FormulaRenderingWorkbook, E
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());
}

View File

@ -47,6 +47,7 @@ public interface EvaluationWorkbook {
*/
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);
@ -66,4 +67,24 @@ public interface EvaluationWorkbook {
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;
}
}
}

View File

@ -17,10 +17,15 @@
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;
@ -254,4 +259,40 @@ public final class OperationEvaluationContext {
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;
}
}
}

View File

@ -65,6 +65,7 @@ import org.apache.poi.hssf.record.formula.functions.Choose;
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;
@ -125,6 +126,19 @@ public final class WorkbookEvaluator {
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;
}
@ -224,6 +238,10 @@ public final class WorkbookEvaluator {
return result.intValue();
}
/* package */ int getSheetIndexByExternIndex(int externSheetIndex) {
return _workbook.convertFromExternSheetIndex(externSheetIndex);
}
/**
* @return never <code>null</code>, never {@link BlankEval}
@ -524,7 +542,7 @@ public final class WorkbookEvaluator {
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) {

View File

@ -107,6 +107,10 @@ final class ForkedEvaluationWorkbook implements EvaluationWorkbook {
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) {
ForkedEvaluationSheet mes = (ForkedEvaluationSheet) sheet;

View File

@ -29,6 +29,7 @@ import org.apache.poi.ss.formula.FormulaParser;
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;
/**
@ -95,6 +96,10 @@ public final class XSSFEvaluationWorkbook implements FormulaRenderingWorkbook, E
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
throw new RuntimeException("Not implemented yet");

View File

@ -0,0 +1,106 @@
/* ====================================================================
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());
}
}

View File

@ -215,4 +215,65 @@ public final class TestHSSFFormulaEvaluator extends TestCase {
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);
}
}

Binary file not shown.

Binary file not shown.