<!-- Don't forget to update status.xml too! -->
<release version="3.5-beta6" date="2009-??-??">
+ <action dev="POI-DEVELOPERS" type="fix">46973 - Fixed defined names to behave better when refersToFormula is unset</action>
<action dev="POI-DEVELOPERS" type="fix">46832 - Allow merged regions with columns greater than 255 or rows bigger than 65536 in XSSF</action>
<action dev="POI-DEVELOPERS" type="fix">46951 - Fixed formula parser to better handle range operators and whole row/column refs.</action>
<action dev="POI-DEVELOPERS" type="fix">46948 - Fixed evaluation of range operator to allow for area-ref operands</action>
<!-- Don't forget to update changes.xml too! -->
<changes>
<release version="3.5-beta6" date="2009-??-??">
+ <action dev="POI-DEVELOPERS" type="fix">46973 - Fixed defined names to behave better when refersToFormula is unset</action>
<action dev="POI-DEVELOPERS" type="fix">46832 - Allow merged regions with columns greater than 255 or rows bigger than 65536 in XSSF</action>
<action dev="POI-DEVELOPERS" type="fix">46951 - Fixed formula parser to better handle range operators and whole row/column refs.</action>
<action dev="POI-DEVELOPERS" type="fix">46948 - Fixed evaluation of range operator to allow for area-ref operands</action>
* @return <code>false</code> if this token is classified as 'reference', 'value', or 'array'
*/
public abstract boolean isBaseToken();
+
+ public static boolean doesFormulaReferToDeletedCell(Ptg[] ptgs) {
+ for (int i = 0; i < ptgs.length; i++) {
+ if (isDeletedCellRef(ptgs[i])) {
+ return true;
+ }
+ }
+ return false;
+ }
+ private static boolean isDeletedCellRef(Ptg ptg) {
+ if (ptg == ErrPtg.REF_INVALID) {
+ return true;
+ }
+ if (ptg instanceof DeletedArea3DPtg) {
+ return true;
+ }
+ if (ptg instanceof DeletedRef3DPtg) {
+ return true;
+ }
+ if (ptg instanceof AreaErrPtg) {
+ return true;
+ }
+ if (ptg instanceof RefErrorPtg) {
+ return true;
+ }
+ return false;
+ }
+
}
import org.apache.poi.hssf.model.Workbook;
import org.apache.poi.hssf.record.NameRecord;
import org.apache.poi.hssf.record.formula.Ptg;
-import org.apache.poi.ss.usermodel.Name;
import org.apache.poi.ss.formula.FormulaType;
+import org.apache.poi.ss.usermodel.Name;
/**
* High Level Representation of a 'defined name' which could be a 'built-in' name,
setRefersToFormula(ref);
}
- /**
- * Sets the formula that the name is defined to refer to. The following are representative examples:
- *
- * <ul>
- * <li><code>'My Sheet'!$A$3</code></li>
- * <li><code>8.3</code></li>
- * <li><code>HR!$A$1:$Z$345</code></li>
- * <li><code>SUM(Sheet1!A1,Sheet2!B2)</li>
- * <li><code>-PMT(Interest_Rate/12,Number_of_Payments,Loan_Amount)</li>
- * </ul>
- *
- * @param formulaText the reference for this name
- * @throws IllegalArgumentException if the specified reference is unparsable
- */
public void setRefersToFormula(String formulaText) {
Ptg[] ptgs = HSSFFormulaParser.parse(formulaText, _book, FormulaType.NAMEDRANGE, getSheetIndex());
_definedNameRec.setNameDefinition(ptgs);
}
- /**
- * Returns the formula that the name is defined to refer to. The following are representative examples:
- *
- * @return the reference for this name
- * @see #setRefersToFormula(String)
- */
public String getRefersToFormula() {
if (_definedNameRec.isFunctionName()) {
throw new IllegalStateException("Only applicable to named ranges");
}
- return HSSFFormulaParser.toFormulaString(_book, _definedNameRec.getNameDefinition());
+ Ptg[] ptgs = _definedNameRec.getNameDefinition();
+ if (ptgs.length < 1) {
+ // 'refersToFormula' has not been set yet
+ return null;
+ }
+ return HSSFFormulaParser.toFormulaString(_book, ptgs);
}
- /**
- * Tests if this name points to a cell that no longer exists
- *
- * @return true if the name refers to a deleted cell, false otherwise
- */
public boolean isDeleted(){
- String formulaText = getRefersToFormula();
- return formulaText.indexOf("#REF!") != -1;
+ Ptg[] ptgs = _definedNameRec.getNameDefinition();
+ return Ptg.doesFormulaReferToDeletedCell(ptgs);
}
/**
/**
* Returns the formula that the name is defined to refer to.
*
- * @return the reference for this name
+ * @return the reference for this name, <code>null</code> if it has not been set yet. Never empty string
* @see #setRefersToFormula(String)
*/
String getRefersToFormula();
* <li><code>-PMT(Interest_Rate/12,Number_of_Payments,Loan_Amount)</li>
* </ul>
*
- * @param ref the reference for this name
- * @throws IllegalArgumentException if the specified reference is unparsable
+ * @param formulaText the reference for this name
+ * @throws IllegalArgumentException if the specified formulaText is unparsable
*/
- void setRefersToFormula(String ref);
+ void setRefersToFormula(String formulaText);
/**
* Checks if this name is a function name
/**
* Checks if this name points to a cell that no longer exists
*
- * @return true if the name refers to a deleted cell, false otherwise
+ * @return <code>true</code> if the name refers to a deleted cell, <code>false</code> otherwise
*/
boolean isDeleted();
ctName.setName(name);
}
- /**
- * Returns the reference of this named range, such as Sales!C20:C30.
- *
- * @return the reference of this named range
- */
public String getRefersToFormula() {
- return ctName.getStringValue();
+ String result = ctName.getStringValue();
+ if (result == null || result.length() < 1) {
+ return null;
+ }
+ return result;
}
- /**
- * Sets the reference of this named range, such as Sales!C20:C30.
- *
- * @param formulaText the reference to set
- * @throws IllegalArgumentException if the specified reference is unparsable
- */
public void setRefersToFormula(String formulaText) {
XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(workbook);
try {
- Ptg[] ptgs = FormulaParser.parse(formulaText, fpb, FormulaType.NAMEDRANGE, getSheetIndex());
+ FormulaParser.parse(formulaText, fpb, FormulaType.NAMEDRANGE, getSheetIndex());
} catch (RuntimeException e) {
if (e.getClass().getName().startsWith(FormulaParser.class.getName())) {
throw new IllegalArgumentException("Unparsable formula '" + formulaText + "'", e);
ctName.setStringValue(formulaText);
}
- /**
- * Tests if this name points to a cell that no longer exists
- *
- * @return true if the name refers to a deleted cell, false otherwise
- */
public boolean isDeleted(){
- String ref = getRefersToFormula();
- return ref != null && ref.indexOf("#REF!") != -1;
+ String formulaText = getRefersToFormula();
+ if (formulaText == null) {
+ return false;
+ }
+ XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(workbook);
+ Ptg[] ptgs = FormulaParser.parse(formulaText, fpb, FormulaType.NAMEDRANGE, getSheetIndex());
+ return Ptg.doesFormulaReferToDeletedCell(ptgs);
}
/**
package org.apache.poi.ss.usermodel;
+import junit.framework.AssertionFailedError;
import junit.framework.TestCase;
+
+import org.apache.poi.hssf.usermodel.HSSFName;
import org.apache.poi.ss.ITestDataProvider;
-import org.apache.poi.ss.formula.FormulaParser;
-import org.apache.poi.ss.formula.FormulaType;
-import org.apache.poi.ss.util.CellReference;
import org.apache.poi.ss.util.AreaReference;
-import org.apache.poi.hssf.record.formula.Ptg;
+import org.apache.poi.ss.util.CellReference;
/**
* Tests of implementations of {@link org.apache.poi.ss.usermodel.Name}.
* Test that multiple named ranges can be added written and read
*/
public void testMultipleNamedWrite() {
- Workbook wb = getTestDataProvider().createWorkbook();
+ Workbook wb = getTestDataProvider().createWorkbook();
wb.createSheet("testSheet1");
assertEquals("Contents of cell retrieved by its named reference", contents, cvalue);
}
+
+ /**
+ * Bugzilla attachment 23444 (from bug 46973) has a NAME record with the following encoding:
+ * <pre>
+ * 00000000 | 18 00 17 00 00 00 00 08 00 00 00 00 00 00 00 00 | ................
+ * 00000010 | 00 00 00 55 50 53 53 74 61 74 65 | ...UPSState
+ * </pre>
+ *
+ * This caused trouble for anything that requires {@link HSSFName#getRefersToFormula()}
+ * It is easy enough to re-create the the same data (by not setting the formula). Excel
+ * seems to gracefully remove this uninitialized name record. It would be nice if POI
+ * could do the same, but that would involve adjusting subsequent name indexes across
+ * all formulas. <p/>
+ *
+ * For the moment, POI has been made to behave more sensibly with uninitialised name
+ * records.
+ */
+ public final void testUninitialisedNameGetRefersToFormula_bug46973() {
+ Workbook wb = getTestDataProvider().createWorkbook();
+ Name n = wb.createName();
+ n.setNameName("UPSState");
+ String formula;
+ try {
+ formula = n.getRefersToFormula();
+ } catch (IllegalArgumentException e) {
+ if (e.getMessage().equals("ptgs must not be null")) {
+ throw new AssertionFailedError("Identified bug 46973");
+ }
+ throw e;
+ }
+ assertNull(formula);
+ assertFalse(n.isDeleted()); // according to exact definition of isDeleted()
+ }
+
+ public void testDeletedCell() {
+ Workbook wb = getTestDataProvider().createWorkbook();
+ Name n = wb.createName();
+ n.setNameName("MyName");
+ // contrived example to expose bug:
+ n.setRefersToFormula("if(A1,\"#REF!\", \"\")");
+
+ if (n.isDeleted()) {
+ throw new AssertionFailedError("Identified bug in recoginising formulas referring to deleted cells");
+ }
+
+ }
}
\ No newline at end of file