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;
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;
}
/**
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/>
private static final class ExternalBookBlock {
private final SupBookRecord _externalBookRecord;
- private final ExternalNameRecord[] _externalNameRecords;
+ private ExternalNameRecord[] _externalNameRecords;
private final CRNBlock[] _crnBlocks;
public ExternalBookBlock(RecordStream rs) {
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;
}
}
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;
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);
}
}
* @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 {
}
/**
+ * 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) {
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;
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
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
}
}
-
- 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();
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();
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++ ) {
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);
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
}
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);
}
/**
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);
- }
}
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}
*
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()));
+
+ }
}
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.
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
+ }
}
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());
+ }
}