git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1039621 13f79535-47bb-0310-9956-ffa450edef68tags/REL_3_8_BETA1
@@ -83,6 +83,7 @@ import org.apache.poi.hssf.record.WriteProtectRecord; | |||
import org.apache.poi.hssf.record.common.UnicodeString; | |||
import org.apache.poi.ss.formula.ptg.NameXPtg; | |||
import org.apache.poi.ss.formula.FormulaShifter; | |||
import org.apache.poi.ss.formula.udf.UDFFinder; | |||
import org.apache.poi.ss.formula.ptg.Ptg; | |||
import org.apache.poi.hssf.util.HSSFColor; | |||
import org.apache.poi.ss.formula.EvaluationWorkbook.ExternalName; | |||
@@ -2300,8 +2301,22 @@ public final class InternalWorkbook { | |||
return linkTable.resolveNameXText(refIndex, definedNameIndex); | |||
} | |||
public NameXPtg getNameXPtg(String name) { | |||
return getOrCreateLinkTable().getNameXPtg(name); | |||
/** | |||
* | |||
* @param name the name of an external function, typically a name of a UDF | |||
* @param udf locator of user-defiend functions to resolve names of VBA and Add-In functions | |||
* @return the external name or null | |||
*/ | |||
public NameXPtg getNameXPtg(String name, UDFFinder udf) { | |||
LinkTable lnk = getOrCreateLinkTable(); | |||
NameXPtg xptg = lnk.getNameXPtg(name); | |||
if(xptg == null && udf.findFunction(name) != null) { | |||
// the name was not found in the list of external names | |||
// check if the Workbook's UDFFinder is aware about it and register the name if it is | |||
xptg = lnk.addNameXPtg(name); | |||
} | |||
return xptg; | |||
} | |||
/** |
@@ -31,9 +31,7 @@ import org.apache.poi.hssf.record.NameCommentRecord; | |||
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.ss.formula.ptg.Area3DPtg; | |||
import org.apache.poi.ss.formula.ptg.NameXPtg; | |||
import org.apache.poi.ss.formula.ptg.Ref3DPtg; | |||
import org.apache.poi.ss.formula.ptg.*; | |||
/** | |||
* Link Table (OOO pdf reference: 4.10.3 ) <p/> | |||
@@ -92,7 +90,7 @@ final class LinkTable { | |||
private static final class ExternalBookBlock { | |||
private final SupBookRecord _externalBookRecord; | |||
private final ExternalNameRecord[] _externalNameRecords; | |||
private ExternalNameRecord[] _externalNameRecords; | |||
private final CRNBlock[] _crnBlocks; | |||
public ExternalBookBlock(RecordStream rs) { | |||
@@ -113,12 +111,28 @@ final class LinkTable { | |||
temp.toArray(_crnBlocks); | |||
} | |||
public ExternalBookBlock(int numberOfSheets) { | |||
/** | |||
* Create a new block for internal references. It is called when constructing a new LinkTable. | |||
* | |||
* @see org.apache.poi.hssf.model.LinkTable#LinkTable(int, WorkbookRecordList) | |||
*/ | |||
public ExternalBookBlock(int numberOfSheets) { | |||
_externalBookRecord = SupBookRecord.createInternalReferences((short)numberOfSheets); | |||
_externalNameRecords = new ExternalNameRecord[0]; | |||
_crnBlocks = new CRNBlock[0]; | |||
} | |||
/** | |||
* Create a new block for registering add-in functions | |||
* | |||
* @see org.apache.poi.hssf.model.LinkTable#addNameXPtg(String) | |||
*/ | |||
public ExternalBookBlock() { | |||
_externalBookRecord = SupBookRecord.createAddInFunctions(); | |||
_externalNameRecords = new ExternalNameRecord[0]; | |||
_crnBlocks = new CRNBlock[0]; | |||
} | |||
public SupBookRecord getExternalBookRecord() { | |||
return _externalBookRecord; | |||
} | |||
@@ -143,9 +157,21 @@ final class LinkTable { | |||
} | |||
return -1; | |||
} | |||
} | |||
private final ExternalBookBlock[] _externalBookBlocks; | |||
public int getNumberOfNames() { | |||
return _externalNameRecords.length; | |||
} | |||
public int addExternalName(ExternalNameRecord rec){ | |||
ExternalNameRecord[] tmp = new ExternalNameRecord[_externalNameRecords.length + 1]; | |||
System.arraycopy(_externalNameRecords, 0, tmp, 0, _externalNameRecords.length); | |||
tmp[tmp.length - 1] = rec; | |||
_externalNameRecords = tmp; | |||
return _externalNameRecords.length - 1; | |||
} | |||
} | |||
private ExternalBookBlock[] _externalBookBlocks; | |||
private final ExternSheetRecord _externSheetRecord; | |||
private final List<NameRecord> _definedNames; | |||
private final int _recordCount; | |||
@@ -461,7 +487,69 @@ final class LinkTable { | |||
return null; | |||
} | |||
private int findRefIndexFromExtBookIndex(int extBookIndex) { | |||
/** | |||
* Register an external name in this workbook | |||
* | |||
* @param name the name to register | |||
* @return a NameXPtg describing this name | |||
*/ | |||
public NameXPtg addNameXPtg(String name) { | |||
int extBlockIndex = -1; | |||
ExternalBookBlock extBlock = null; | |||
// find ExternalBlock for Add-In functions and remember its index | |||
for (int i = 0; i < _externalBookBlocks.length; i++) { | |||
SupBookRecord ebr = _externalBookBlocks[i].getExternalBookRecord(); | |||
if (ebr.isAddInFunctions()) { | |||
extBlock = _externalBookBlocks[i]; | |||
extBlockIndex = i; | |||
break; | |||
} | |||
} | |||
// An ExternalBlock for Add-In functions was not found. Create a new one. | |||
if (extBlock == null) { | |||
extBlock = new ExternalBookBlock(); | |||
ExternalBookBlock[] tmp = new ExternalBookBlock[_externalBookBlocks.length + 1]; | |||
System.arraycopy(_externalBookBlocks, 0, tmp, 0, _externalBookBlocks.length); | |||
tmp[tmp.length - 1] = extBlock; | |||
_externalBookBlocks = tmp; | |||
extBlockIndex = _externalBookBlocks.length - 1; | |||
// add the created SupBookRecord before ExternSheetRecord | |||
int idx = findFirstRecordLocBySid(ExternSheetRecord.sid); | |||
_workbookRecordList.add(idx, extBlock.getExternalBookRecord()); | |||
// register the SupBookRecord in the ExternSheetRecord | |||
// -2 means that the scope of this name is Workbook and the reference applies to the entire workbook. | |||
_externSheetRecord.addRef(_externalBookBlocks.length - 1, -2, -2); | |||
} | |||
// create a ExternalNameRecord that will describe this name | |||
ExternalNameRecord extNameRecord = new ExternalNameRecord(); | |||
extNameRecord.setText(name); | |||
// The docs don't explain why Excel set the formula to #REF! | |||
extNameRecord.setParsedExpression(new Ptg[]{ErrPtg.REF_INVALID}); | |||
int nameIndex = extBlock.addExternalName(extNameRecord); | |||
int supLinkIndex = 0; | |||
// find the posistion of the Add-In SupBookRecord in the workbook stream, | |||
// the created ExternalNameRecord will be appended to it | |||
for (Iterator iterator = _workbookRecordList.iterator(); iterator.hasNext(); supLinkIndex++) { | |||
Record record = (Record) iterator.next(); | |||
if (record instanceof SupBookRecord) { | |||
if (((SupBookRecord) record).isAddInFunctions()) break; | |||
} | |||
} | |||
int numberOfNames = extBlock.getNumberOfNames(); | |||
// a new name is inserted in the end of the SupBookRecord, after the last name | |||
_workbookRecordList.add(supLinkIndex + numberOfNames, extNameRecord); | |||
int ix = _externSheetRecord.getRefIxForSheet(extBlockIndex, -2 /* the scope is workbook*/); | |||
return new NameXPtg(ix, nameIndex); | |||
} | |||
private int findRefIndexFromExtBookIndex(int extBookIndex) { | |||
return _externSheetRecord.findRefIndexFromExtBookIndex(extBookIndex); | |||
} | |||
} |
@@ -29,7 +29,8 @@ import org.apache.poi.util.LittleEndianOutput; | |||
* @author Libin Roman (Vista Portal LDT. Developer) | |||
*/ | |||
public class ExternSheetRecord extends StandardRecord { | |||
public final static short sid = 0x0017; | |||
public final static short sid = 0x0017; | |||
private List<RefSubRecord> _list; | |||
private static final class RefSubRecord { | |||
@@ -184,6 +185,33 @@ public class ExternSheetRecord extends StandardRecord { | |||
} | |||
/** | |||
* Add a zero-based reference to a {@link org.apache.poi.hssf.record.SupBookRecord}. | |||
* <p> | |||
* If the type of the SupBook record is same-sheet referencing, Add-In referencing, | |||
* DDE data source referencing, or OLE data source referencing, | |||
* then no scope is specified and this value <em>MUST</em> be -2. Otherwise, | |||
* the scope must be set as follows: | |||
* <ol> | |||
* <li><code>-2</code> Workbook-level reference that applies to the entire workbook.</li> | |||
* <li><code>-1</code> Sheet-level reference. </li> | |||
* <li><code>>=0</code> Sheet-level reference. This specifies the first sheet in the reference. | |||
* <p> | |||
* If the SupBook type is unused or external workbook referencing, | |||
* then this value specifies the zero-based index of an external sheet name, | |||
* see {@link org.apache.poi.hssf.record.SupBookRecord#getSheetNames()}. | |||
* This referenced string specifies the name of the first sheet within the external workbook that is in scope. | |||
* This sheet MUST be a worksheet or macro sheet. | |||
* <p> | |||
* <p> | |||
* If the supporting link type is self-referencing, then this value specifies the zero-based index of a | |||
* {@link org.apache.poi.hssf.record.BoundSheetRecord} record in the workbook stream that specifies | |||
* the first sheet within the scope of this reference. This sheet MUST be a worksheet or a macro sheet. | |||
* </p> | |||
* </li> | |||
* </ol> | |||
* | |||
* @param firstSheetIndex the scope, must be -2 for add-in references | |||
* @param lastSheetIndex the scope, must be -2 for add-in references | |||
* @return index of newly added ref | |||
*/ | |||
public int addRef(int extBookIndex, int firstSheetIndex, int lastSheetIndex) { |
@@ -19,6 +19,7 @@ package org.apache.poi.hssf.record; | |||
import org.apache.poi.ss.formula.constant.ConstantValueParser; | |||
import org.apache.poi.ss.formula.Formula; | |||
import org.apache.poi.ss.formula.ptg.Ptg; | |||
import org.apache.poi.util.LittleEndianOutput; | |||
import org.apache.poi.util.StringUtil; | |||
@@ -98,6 +99,10 @@ public final class ExternalNameRecord extends StandardRecord { | |||
return field_4_name; | |||
} | |||
public void setText(String str) { | |||
field_4_name = str; | |||
} | |||
/** | |||
* If this is a local name, then this is the (1 based) | |||
* index of the name of the Sheet this refers to, as | |||
@@ -107,6 +112,17 @@ public final class ExternalNameRecord extends StandardRecord { | |||
public short getIx() { | |||
return field_2_ixals; | |||
} | |||
public void setIx(short ix) { | |||
field_2_ixals = ix; | |||
} | |||
public Ptg[] getParsedExpression() { | |||
return Formula.getTokens(field_5_name_definition); | |||
} | |||
public void setParsedExpression(Ptg[] ptgs) { | |||
field_5_name_definition = Formula.create(ptgs); | |||
} | |||
protected int getDataSize(){ | |||
int result = 2 + 4; // short and int | |||
@@ -142,8 +158,11 @@ public final class ExternalNameRecord extends StandardRecord { | |||
} | |||
} | |||
public ExternalNameRecord(RecordInputStream in) { | |||
public ExternalNameRecord() { | |||
field_2_ixals = 0; | |||
} | |||
public ExternalNameRecord(RecordInputStream in) { | |||
field_1_option_flag = in.readShort(); | |||
field_2_ixals = in.readShort(); | |||
field_3_not_used = in.readShort(); | |||
@@ -180,10 +199,15 @@ public final class ExternalNameRecord extends StandardRecord { | |||
public String toString() { | |||
StringBuffer sb = new StringBuffer(); | |||
sb.append("[EXTERNALNAME]\n"); | |||
sb.append(" .options = ").append(field_1_option_flag).append("\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"); | |||
Ptg[] ptgs = field_5_name_definition.getTokens(); | |||
for (int i = 0; i < ptgs.length; i++) { | |||
Ptg ptg = ptgs[i]; | |||
sb.append(ptg.toString()).append(ptg.getRVAType()).append("\n"); | |||
} | |||
} | |||
sb.append("[/EXTERNALNAME]\n"); | |||
return sb.toString(); |
@@ -359,7 +359,7 @@ public final class FormulaRecord extends CellRecord { | |||
sb.append(" .alwaysCalc= ").append(isAlwaysCalc()).append("\n"); | |||
sb.append(" .calcOnLoad= ").append(isCalcOnLoad()).append("\n"); | |||
sb.append(" .shared = ").append(isSharedFormula()).append("\n"); | |||
sb.append(" .zero = ").append(HexDump.intToHex(field_6_zero)); | |||
sb.append(" .zero = ").append(HexDump.intToHex(field_6_zero)).append("\n"); | |||
Ptg[] ptgs = field_8_parsed_expr.getTokens(); | |||
for (int k = 0; k < ptgs.length; k++ ) { |
@@ -47,7 +47,7 @@ public final class SupBookRecord extends StandardRecord { | |||
return new SupBookRecord(false, numberOfSheets); | |||
} | |||
public static SupBookRecord createAddInFunctions() { | |||
return new SupBookRecord(true, (short)0); | |||
return new SupBookRecord(true, (short)1 /* this field MUST be 0x0001 for add-in referencing */); | |||
} | |||
public static SupBookRecord createExternalReferences(String url, String[] sheetNames) { | |||
return new SupBookRecord(url, sheetNames); |
@@ -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.udf.UDFFinder; | |||
/** | |||
* Internal POI use only | |||
@@ -65,7 +66,9 @@ public final class HSSFEvaluationWorkbook implements FormulaRenderingWorkbook, E | |||
} | |||
public NameXPtg getNameXPtg(String name) { | |||
return _iBook.getNameXPtg(name); | |||
// TODO YK: passing UDFFinder.DEFAULT is temporary, | |||
// a proper design should take it from the parent HSSFWorkbook | |||
return _iBook.getNameXPtg(name, UDFFinder.DEFAULT); | |||
} | |||
/** |
@@ -1679,12 +1679,4 @@ public final class HSSFWorkbook extends POIDocument implements org.apache.poi.ss | |||
private static byte[] newUID() { | |||
return new byte[16]; | |||
} | |||
/** | |||
* Note - This method should only used by POI internally. | |||
* It may get deleted or change definition in future POI versions | |||
*/ | |||
public NameXPtg getNameXPtg(String name) { | |||
return workbook.getNameXPtg(name); | |||
} | |||
} |
@@ -27,13 +27,12 @@ import junit.framework.AssertionFailedError; | |||
import junit.framework.TestCase; | |||
import org.apache.poi.hssf.HSSFTestDataSamples; | |||
import org.apache.poi.hssf.record.NameCommentRecord; | |||
import org.apache.poi.hssf.record.NameRecord; | |||
import org.apache.poi.hssf.record.Record; | |||
import org.apache.poi.hssf.record.SSTRecord; | |||
import org.apache.poi.hssf.record.SupBookRecord; | |||
import org.apache.poi.hssf.record.*; | |||
import org.apache.poi.hssf.usermodel.HSSFCell; | |||
import org.apache.poi.hssf.usermodel.HSSFWorkbook; | |||
import org.apache.poi.hssf.usermodel.TestHSSFWorkbook; | |||
import org.apache.poi.ss.formula.ptg.NameXPtg; | |||
/** | |||
* Tests for {@link LinkTable} | |||
* | |||
@@ -179,4 +178,86 @@ public final class TestLinkTable extends TestCase { | |||
assertEquals(2, lt.getNumNames()); | |||
} | |||
public void testAddNameX(){ | |||
WorkbookRecordList wrl = new WorkbookRecordList(); | |||
wrl.add(0, new BOFRecord()); | |||
wrl.add(1, new CountryRecord()); | |||
wrl.add(2, EOFRecord.instance); | |||
int numberOfSheets = 3; | |||
LinkTable tbl = new LinkTable(numberOfSheets, wrl); | |||
// creation of a new LinkTable insert two new records: SupBookRecord followed by ExternSheetRecord | |||
// assure they are in place: | |||
// [BOFRecord] | |||
// [CountryRecord] | |||
// [SUPBOOK Internal References nSheets= 3] | |||
// [EXTERNSHEET] | |||
// [EOFRecord] | |||
assertEquals(5, wrl.getRecords().size()); | |||
assertTrue(wrl.get(2) instanceof SupBookRecord); | |||
SupBookRecord sup1 = (SupBookRecord)wrl.get(2); | |||
assertEquals(numberOfSheets, sup1.getNumberOfSheets()); | |||
assertTrue(wrl.get(3) instanceof ExternSheetRecord); | |||
ExternSheetRecord extSheet = (ExternSheetRecord)wrl.get(3); | |||
assertEquals(0, extSheet.getNumOfRefs()); | |||
assertNull(tbl.getNameXPtg("ISODD")); | |||
assertEquals(5, wrl.getRecords().size()); //still have five records | |||
NameXPtg namex1 = tbl.addNameXPtg("ISODD"); // adds two new rercords | |||
assertEquals(0, namex1.getSheetRefIndex()); | |||
assertEquals(0, namex1.getNameIndex()); | |||
assertEquals(namex1.toString(), tbl.getNameXPtg("ISODD").toString()); | |||
// assure they are in place: | |||
// [BOFRecord] | |||
// [CountryRecord] | |||
// [SUPBOOK Internal References nSheets= 3] | |||
// [SUPBOOK Add-In Functions nSheets= 1] | |||
// [EXTERNALNAME .name = ISODD] | |||
// [EXTERNSHEET] | |||
// [EOFRecord] | |||
assertEquals(7, wrl.getRecords().size()); | |||
assertTrue(wrl.get(3) instanceof SupBookRecord); | |||
SupBookRecord sup2 = (SupBookRecord)wrl.get(3); | |||
assertTrue(sup2.isAddInFunctions()); | |||
assertTrue(wrl.get(4) instanceof ExternalNameRecord); | |||
ExternalNameRecord ext1 = (ExternalNameRecord)wrl.get(4); | |||
assertEquals("ISODD", ext1.getText()); | |||
assertTrue(wrl.get(5) instanceof ExternSheetRecord); | |||
assertEquals(1, extSheet.getNumOfRefs()); | |||
//check that | |||
assertEquals(0, tbl.resolveNameXIx(namex1.getSheetRefIndex(), namex1.getNameIndex())); | |||
assertEquals("ISODD", tbl.resolveNameXText(namex1.getSheetRefIndex(), namex1.getNameIndex())); | |||
assertNull(tbl.getNameXPtg("ISEVEN")); | |||
NameXPtg namex2 = tbl.addNameXPtg("ISEVEN"); // adds two new rercords | |||
assertEquals(0, namex2.getSheetRefIndex()); | |||
assertEquals(1, namex2.getNameIndex()); // name index increased by one | |||
assertEquals(namex2.toString(), tbl.getNameXPtg("ISEVEN").toString()); | |||
assertEquals(8, wrl.getRecords().size()); | |||
// assure they are in place: | |||
// [BOFRecord] | |||
// [CountryRecord] | |||
// [SUPBOOK Internal References nSheets= 3] | |||
// [SUPBOOK Add-In Functions nSheets= 1] | |||
// [EXTERNALNAME .name = ISODD] | |||
// [EXTERNALNAME .name = ISEVEN] | |||
// [EXTERNSHEET] | |||
// [EOFRecord] | |||
assertTrue(wrl.get(3) instanceof SupBookRecord); | |||
assertTrue(wrl.get(4) instanceof ExternalNameRecord); | |||
assertTrue(wrl.get(5) instanceof ExternalNameRecord); | |||
assertEquals("ISODD", ((ExternalNameRecord)wrl.get(4)).getText()); | |||
assertEquals("ISEVEN", ((ExternalNameRecord)wrl.get(5)).getText()); | |||
assertTrue(wrl.get(6) instanceof ExternSheetRecord); | |||
assertTrue(wrl.get(7) instanceof EOFRecord); | |||
assertEquals(0, tbl.resolveNameXIx(namex2.getSheetRefIndex(), namex2.getNameIndex())); | |||
assertEquals("ISEVEN", tbl.resolveNameXText(namex2.getSheetRefIndex(), namex2.getNameIndex())); | |||
} | |||
} |
@@ -22,6 +22,13 @@ import junit.framework.TestCase; | |||
import org.apache.poi.hssf.record.FontRecord; | |||
import org.apache.poi.hssf.usermodel.HSSFWorkbook; | |||
import org.apache.poi.hssf.usermodel.TestHSSFWorkbook; | |||
import org.apache.poi.ss.formula.udf.UDFFinder; | |||
import org.apache.poi.ss.formula.udf.DefaultUDFFinder; | |||
import org.apache.poi.ss.formula.udf.AggregatingUDFFinder; | |||
import org.apache.poi.ss.formula.functions.FreeRefFunction; | |||
import org.apache.poi.ss.formula.eval.ValueEval; | |||
import org.apache.poi.ss.formula.eval.NotImplementedException; | |||
import org.apache.poi.ss.formula.OperationEvaluationContext; | |||
/** | |||
* Unit test for the Workbook class. | |||
@@ -83,4 +90,28 @@ public final class TestWorkbook extends TestCase { | |||
assertEquals(6, wb.getFontIndex(n7)); | |||
assertEquals(n7, wb.getFontRecordAt(6)); | |||
} | |||
public void testAddNameX(){ | |||
InternalWorkbook wb = TestHSSFWorkbook.getInternalWorkbook(new HSSFWorkbook()); | |||
assertNotNull(wb.getNameXPtg("ISODD", UDFFinder.DEFAULT)); | |||
FreeRefFunction NotImplemented = new FreeRefFunction() { | |||
public ValueEval evaluate(ValueEval[] args, OperationEvaluationContext ec) { | |||
throw new RuntimeException("not implemented"); | |||
} | |||
}; | |||
/** | |||
* register the two test UDFs in a UDF finder, to be passed to the evaluator | |||
*/ | |||
UDFFinder udff1 = new DefaultUDFFinder(new String[] { "myFunc", }, | |||
new FreeRefFunction[] { NotImplemented }); | |||
UDFFinder udff2 = new DefaultUDFFinder(new String[] { "myFunc2", }, | |||
new FreeRefFunction[] { NotImplemented }); | |||
UDFFinder udff = new AggregatingUDFFinder(udff1, udff2); | |||
assertNotNull(wb.getNameXPtg("myFunc", udff)); | |||
assertNotNull(wb.getNameXPtg("myFunc2", udff)); | |||
assertNull(wb.getNameXPtg("myFunc3", udff)); // myFunc3 is unknown | |||
} | |||
} |
@@ -102,5 +102,12 @@ public final class TestSupBookRecord extends TestCase { | |||
SupBookRecord record = SupBookRecord.createExternalReferences(url, sheetNames); | |||
TestcaseRecordInputStream.confirmRecordEncoding(0x01AE, dataER, record.serialize()); | |||
} | |||
} | |||
public void testStoreAIF() { | |||
SupBookRecord record = SupBookRecord.createAddInFunctions(); | |||
assertEquals(1, record.getNumberOfSheets()); | |||
assertTrue(record.isAddInFunctions()); | |||
TestcaseRecordInputStream.confirmRecordEncoding(0x01AE, dataAIF, record.serialize()); | |||
} | |||
} |