Bugzilla 46973 - fixed defined names to behave better when refersToFormula is unset

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@762479 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Josh Micich 2009-04-06 19:57:21 +00:00
parent 6ceeb39a45
commit 60fd0a0fae
7 changed files with 108 additions and 59 deletions

View File

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

View File

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

View File

@ -321,4 +321,32 @@ public abstract class Ptg implements Cloneable {
* @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;
}
}

View File

@ -21,8 +21,8 @@ import org.apache.poi.hssf.model.HSSFFormulaParser;
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,
@ -160,46 +160,26 @@ public final class HSSFName implements 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);
}
/**

View File

@ -118,7 +118,7 @@ public interface Name {
/**
* 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();
@ -134,10 +134,10 @@ public interface Name {
* <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
@ -149,7 +149,7 @@ public interface 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();

View File

@ -181,25 +181,18 @@ public final class XSSFName implements Name {
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);
@ -209,14 +202,14 @@ public final class XSSFName implements Name {
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);
}
/**

View File

@ -17,13 +17,13 @@
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}.
@ -395,7 +395,7 @@ public abstract class BaseTestNamedRange extends TestCase {
* 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");
@ -498,4 +498,50 @@ public abstract class BaseTestNamedRange extends TestCase {
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");
}
}
}