Fix for bug 45334 - formula parser needs to handle dots in identifiers
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@673853 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
aceb436c93
commit
6fcff0c660
@ -37,6 +37,7 @@
|
|||||||
|
|
||||||
<!-- Don't forget to update status.xml too! -->
|
<!-- Don't forget to update status.xml too! -->
|
||||||
<release version="3.1.1-alpha1" date="2008-??-??">
|
<release version="3.1.1-alpha1" date="2008-??-??">
|
||||||
|
<action dev="POI-DEVELOPERS" type="fix">45334 - Fixed formula parser to handle dots in identifiers</action>
|
||||||
<action dev="POI-DEVELOPERS" type="fix">45252 - Improvement for HWPF Range.replaceText()</action>
|
<action dev="POI-DEVELOPERS" type="fix">45252 - Improvement for HWPF Range.replaceText()</action>
|
||||||
<action dev="POI-DEVELOPERS" type="fix">45001 - Further fix for HWPF Range.delete() and unicode characters</action>
|
<action dev="POI-DEVELOPERS" type="fix">45001 - Further fix for HWPF Range.delete() and unicode characters</action>
|
||||||
<action dev="POI-DEVELOPERS" type="add">45175 - Support for variable length operands in org.apache.poi.hwpf.sprm.SprmOperation</action>
|
<action dev="POI-DEVELOPERS" type="add">45175 - Support for variable length operands in org.apache.poi.hwpf.sprm.SprmOperation</action>
|
||||||
|
@ -34,6 +34,7 @@
|
|||||||
<!-- Don't forget to update changes.xml too! -->
|
<!-- Don't forget to update changes.xml too! -->
|
||||||
<changes>
|
<changes>
|
||||||
<release version="3.1.1-alpha1" date="2008-??-??">
|
<release version="3.1.1-alpha1" date="2008-??-??">
|
||||||
|
<action dev="POI-DEVELOPERS" type="fix">45334 - Fixed formula parser to handle dots in identifiers</action>
|
||||||
<action dev="POI-DEVELOPERS" type="fix">45252 - Improvement for HWPF Range.replaceText()</action>
|
<action dev="POI-DEVELOPERS" type="fix">45252 - Improvement for HWPF Range.replaceText()</action>
|
||||||
<action dev="POI-DEVELOPERS" type="fix">45001 - Further fix for HWPF Range.delete() and unicode characters</action>
|
<action dev="POI-DEVELOPERS" type="fix">45001 - Further fix for HWPF Range.delete() and unicode characters</action>
|
||||||
<action dev="POI-DEVELOPERS" type="add">45175 - Support for variable length operands in org.apache.poi.hwpf.sprm.SprmOperation</action>
|
<action dev="POI-DEVELOPERS" type="add">45175 - Support for variable length operands in org.apache.poi.hwpf.sprm.SprmOperation</action>
|
||||||
|
@ -22,11 +22,13 @@ import java.util.List;
|
|||||||
import java.util.Stack;
|
import java.util.Stack;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
//import PTG's .. since we need everything, import *
|
//import PTGs .. since we need everything, import *
|
||||||
import org.apache.poi.hssf.record.formula.*;
|
import org.apache.poi.hssf.record.formula.*;
|
||||||
import org.apache.poi.hssf.record.formula.function.FunctionMetadata;
|
import org.apache.poi.hssf.record.formula.function.FunctionMetadata;
|
||||||
import org.apache.poi.hssf.record.formula.function.FunctionMetadataRegistry;
|
import org.apache.poi.hssf.record.formula.function.FunctionMetadataRegistry;
|
||||||
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
|
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
|
||||||
|
import org.apache.poi.hssf.util.AreaReference;
|
||||||
|
import org.apache.poi.hssf.util.CellReference;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class parses a formula string into a List of tokens in RPN order.
|
* This class parses a formula string into a List of tokens in RPN order.
|
||||||
@ -178,8 +180,14 @@ public final class FormulaParser {
|
|||||||
GetChar();
|
GetChar();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get an Identifier */
|
/**
|
||||||
private String GetName() {
|
* Parses a sheet name, named range name, or simple cell reference.<br/>
|
||||||
|
* Note - identifiers in Excel can contain dots, so this method may return a String
|
||||||
|
* which may need to be converted to an area reference. For example, this method
|
||||||
|
* may return a value like "A1..B2", in which case the caller must convert it to
|
||||||
|
* an area reference like "A1:B2"
|
||||||
|
*/
|
||||||
|
private String parseIdentifier() {
|
||||||
StringBuffer Token = new StringBuffer();
|
StringBuffer Token = new StringBuffer();
|
||||||
if (!IsAlpha(look) && look != '\'') {
|
if (!IsAlpha(look) && look != '\'') {
|
||||||
throw expected("Name");
|
throw expected("Name");
|
||||||
@ -201,7 +209,9 @@ public final class FormulaParser {
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
while (IsAlNum(look)) {
|
// allow for any sequence of dots and identifier chars
|
||||||
|
// special case of two consecutive dots is best treated in the calling code
|
||||||
|
while (IsAlNum(look) || look == '.') {
|
||||||
Token.append(look);
|
Token.append(look);
|
||||||
GetChar();
|
GetChar();
|
||||||
}
|
}
|
||||||
@ -220,15 +230,22 @@ public final class FormulaParser {
|
|||||||
return value.length() == 0 ? null : value.toString();
|
return value.length() == 0 ? null : value.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
private ParseNode parseFunctionOrIdentifier() {
|
private ParseNode parseFunctionReferenceOrName() {
|
||||||
String name = GetName();
|
String name = parseIdentifier();
|
||||||
if (look == '('){
|
if (look == '('){
|
||||||
//This is a function
|
//This is a function
|
||||||
return function(name);
|
return function(name);
|
||||||
}
|
}
|
||||||
return new ParseNode(parseIdentifier(name));
|
return new ParseNode(parseNameOrReference(name));
|
||||||
}
|
}
|
||||||
private Ptg parseIdentifier(String name) {
|
|
||||||
|
private Ptg parseNameOrReference(String name) {
|
||||||
|
|
||||||
|
AreaReference areaRef = parseArea(name);
|
||||||
|
if (areaRef != null) {
|
||||||
|
// will happen if dots are used instead of colon
|
||||||
|
return new AreaPtg(areaRef.formatAsString());
|
||||||
|
}
|
||||||
|
|
||||||
if (look == ':' || look == '.') { // this is a AreaReference
|
if (look == ':' || look == '.') { // this is a AreaReference
|
||||||
GetChar();
|
GetChar();
|
||||||
@ -238,23 +255,28 @@ public final class FormulaParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String first = name;
|
String first = name;
|
||||||
String second = GetName();
|
String second = parseIdentifier();
|
||||||
return new AreaPtg(first+":"+second);
|
return new AreaPtg(first+":"+second);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (look == '!') {
|
if (look == '!') {
|
||||||
Match('!');
|
Match('!');
|
||||||
String sheetName = name;
|
String sheetName = name;
|
||||||
String first = GetName();
|
String first = parseIdentifier();
|
||||||
short externIdx = book.getExternalSheetIndex(book.getSheetIndex(sheetName));
|
short externIdx = book.getExternalSheetIndex(book.getSheetIndex(sheetName));
|
||||||
|
areaRef = parseArea(name);
|
||||||
|
if (areaRef != null) {
|
||||||
|
// will happen if dots are used instead of colon
|
||||||
|
return new Area3DPtg(areaRef.formatAsString(), externIdx);
|
||||||
|
}
|
||||||
if (look == ':') {
|
if (look == ':') {
|
||||||
Match(':');
|
Match(':');
|
||||||
String second=GetName();
|
String second=parseIdentifier();
|
||||||
if (look == '!') {
|
if (look == '!') {
|
||||||
//The sheet name was included in both of the areas. Only really
|
//The sheet name was included in both of the areas. Only really
|
||||||
//need it once
|
//need it once
|
||||||
Match('!');
|
Match('!');
|
||||||
String third=GetName();
|
String third=parseIdentifier();
|
||||||
|
|
||||||
if (!sheetName.equals(second))
|
if (!sheetName.equals(second))
|
||||||
throw new RuntimeException("Unhandled double sheet reference.");
|
throw new RuntimeException("Unhandled double sheet reference.");
|
||||||
@ -271,9 +293,7 @@ public final class FormulaParser {
|
|||||||
|
|
||||||
// This can be either a cell ref or a named range
|
// This can be either a cell ref or a named range
|
||||||
// Try to spot which it is
|
// Try to spot which it is
|
||||||
boolean cellRef = CELL_REFERENCE_PATTERN.matcher(name).matches();
|
if (isValidCellReference(name)) {
|
||||||
|
|
||||||
if (cellRef) {
|
|
||||||
return new RefPtg(name);
|
return new RefPtg(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -287,6 +307,41 @@ public final class FormulaParser {
|
|||||||
+ name + "\", but that named range wasn't defined!");
|
+ name + "\", but that named range wasn't defined!");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return <code>null</code> if name cannot be split at a dot
|
||||||
|
*/
|
||||||
|
private AreaReference parseArea(String name) {
|
||||||
|
int dotPos = name.indexOf('.');
|
||||||
|
if (dotPos < 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
int dotCount = 1;
|
||||||
|
while (dotCount<name.length() && name.charAt(dotPos+dotCount) == '.') {
|
||||||
|
dotCount++;
|
||||||
|
if (dotCount>3) {
|
||||||
|
// four or more consecutive dots does not convert to ':'
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
String partA = name.substring(0, dotPos);
|
||||||
|
if (!isValidCellReference(partA)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String partB = name.substring(dotPos+dotCount);
|
||||||
|
if (!isValidCellReference(partB)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
CellReference topLeft = new CellReference(partA);
|
||||||
|
CellReference bottomRight = new CellReference(partB);
|
||||||
|
return new AreaReference(topLeft, bottomRight);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isValidCellReference(String str) {
|
||||||
|
// TODO - exact rules for recognising cell references may be too complicated for regex
|
||||||
|
return CELL_REFERENCE_PATTERN.matcher(str).matches();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Note - Excel function names are 'case aware but not case sensitive'. This method may end
|
* Note - Excel function names are 'case aware but not case sensitive'. This method may end
|
||||||
* up creating a defined name record in the workbook if the specified name is not an internal
|
* up creating a defined name record in the workbook if the specified name is not an internal
|
||||||
@ -465,7 +520,7 @@ public final class FormulaParser {
|
|||||||
return new ParseNode(parseStringLiteral());
|
return new ParseNode(parseStringLiteral());
|
||||||
}
|
}
|
||||||
if (IsAlpha(look) || look == '\''){
|
if (IsAlpha(look) || look == '\''){
|
||||||
return parseFunctionOrIdentifier();
|
return parseFunctionReferenceOrName();
|
||||||
}
|
}
|
||||||
// else - assume number
|
// else - assume number
|
||||||
return new ParseNode(parseNumber());
|
return new ParseNode(parseNumber());
|
||||||
@ -510,7 +565,7 @@ public final class FormulaParser {
|
|||||||
|
|
||||||
private ErrPtg parseErrorLiteral() {
|
private ErrPtg parseErrorLiteral() {
|
||||||
Match('#');
|
Match('#');
|
||||||
String part1 = GetName().toUpperCase();
|
String part1 = parseIdentifier().toUpperCase();
|
||||||
|
|
||||||
switch(part1.charAt(0)) {
|
switch(part1.charAt(0)) {
|
||||||
case 'V':
|
case 'V':
|
||||||
|
@ -759,7 +759,7 @@ public final class TestFormulaParser extends TestCase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testParseErrorExpecteMsg() {
|
public void testParseErrorExpectedMsg() {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
parseFormula("round(3.14;2)");
|
parseFormula("round(3.14;2)");
|
||||||
@ -768,4 +768,27 @@ public final class TestFormulaParser extends TestCase {
|
|||||||
assertEquals("Parse error near char 10 ';' in specified formula 'round(3.14;2)'. Expected ',' or ')'", e.getMessage());
|
assertEquals("Parse error near char 10 ';' in specified formula 'round(3.14;2)'. Expected ',' or ')'", e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* this function name has a dot in it.
|
||||||
|
*/
|
||||||
|
public void testParseErrorTypeFunction() {
|
||||||
|
|
||||||
|
Ptg[] ptgs;
|
||||||
|
try {
|
||||||
|
ptgs = parseFormula("error.type(A1)");
|
||||||
|
|
||||||
|
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
if (e.getMessage().equals("Invalid Formula cell reference: 'error'")) {
|
||||||
|
throw new AssertionFailedError("Identified bug 45334");
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
assertEquals(2, ptgs.length);
|
||||||
|
assertEquals(FuncPtg.class, ptgs[1].getClass());
|
||||||
|
FuncPtg funcPtg = (FuncPtg) ptgs[1];
|
||||||
|
assertEquals("ERROR.TYPE", funcPtg.getName());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user