Follow-on from 28754 - StringPtg.toFormulaString() should escape double quotes

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@653608 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Josh Micich 2008-05-05 21:38:07 +00:00
parent 573429d804
commit 31ccbe6fc9
4 changed files with 140 additions and 121 deletions

View File

@ -37,6 +37,7 @@
<!-- Don't forget to update status.xml too! --> <!-- Don't forget to update status.xml too! -->
<release version="3.1-beta2" date="2008-05-??"> <release version="3.1-beta2" date="2008-05-??">
<action dev="POI-DEVELOPERS" type="fix">Follow-on from 28754 - StringPtg.toFormulaString() should escape double quotes</action>
<action dev="POI-DEVELOPERS" type="fix">44929 - Improved error handling in HSSFWorkbook when attempting to read a BIFF5 file</action> <action dev="POI-DEVELOPERS" type="fix">44929 - Improved error handling in HSSFWorkbook when attempting to read a BIFF5 file</action>
<action dev="POI-DEVELOPERS" type="fix">44675 - Parameter operand classes (function metadata) required to encode SUM() etc properly. Added parse validation for number of parameters</action> <action dev="POI-DEVELOPERS" type="fix">44675 - Parameter operand classes (function metadata) required to encode SUM() etc properly. Added parse validation for number of parameters</action>
<action dev="POI-DEVELOPERS" type="fix">44921 - allow Ptg.writeBytes() to be called on relative ref Ptgs (RefN* and AreaN*)</action> <action dev="POI-DEVELOPERS" type="fix">44921 - allow Ptg.writeBytes() to be called on relative ref Ptgs (RefN* and AreaN*)</action>

View File

@ -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-beta2" date="2008-05-??"> <release version="3.1-beta2" date="2008-05-??">
<action dev="POI-DEVELOPERS" type="fix">Follow-on from 28754 - StringPtg.toFormulaString() should escape double quotes</action>
<action dev="POI-DEVELOPERS" type="fix">44929 - Improved error handling in HSSFWorkbook when attempting to read a BIFF5 file</action> <action dev="POI-DEVELOPERS" type="fix">44929 - Improved error handling in HSSFWorkbook when attempting to read a BIFF5 file</action>
<action dev="POI-DEVELOPERS" type="fix">44675 - Parameter operand classes (function metadata) required to encode SUM() etc properly. Added parse validation for number of parameters</action> <action dev="POI-DEVELOPERS" type="fix">44675 - Parameter operand classes (function metadata) required to encode SUM() etc properly. Added parse validation for number of parameters</action>
<action dev="POI-DEVELOPERS" type="fix">44921 - allow Ptg.writeBytes() to be called on relative ref Ptgs (RefN* and AreaN*)</action> <action dev="POI-DEVELOPERS" type="fix">44921 - allow Ptg.writeBytes() to be called on relative ref Ptgs (RefN* and AreaN*)</action>

View File

@ -24,23 +24,26 @@ import org.apache.poi.util.StringUtil;
import org.apache.poi.hssf.record.RecordInputStream; import org.apache.poi.hssf.record.RecordInputStream;
/** /**
* Number * String Stores a String value in a formula value stored in the format
* Stores a String value in a formula value stored in the format &lt;length 2 bytes&gt;char[] * &lt;length 2 bytes&gt;char[]
*
* @author Werner Froidevaux * @author Werner Froidevaux
* @author Jason Height (jheight at chariot dot net dot au) * @author Jason Height (jheight at chariot dot net dot au)
* @author Bernard Chesnoy * @author Bernard Chesnoy
*/ */
public final class StringPtg extends Ptg {
public class StringPtg
extends Ptg
{
public final static int SIZE = 9; public final static int SIZE = 9;
public final static byte sid = 0x17; public final static byte sid = 0x17;
//NOTE: OO doc says 16bit lenght, but BiffViewer says 8 private static final BitField fHighByte = BitFieldFactory.getInstance(0x01);
// Book says something totally different, so dont look there! /** the character (")used in formulas to delimit string literals */
int field_1_length; private static final char FORMULA_DELIMITER = '"';
byte field_2_options;
BitField fHighByte = BitFieldFactory.getInstance(0x01); /**
* NOTE: OO doc says 16bit length, but BiffViewer says 8 Book says something
* totally different, so don't look there!
*/
private int field_1_length;
private byte field_2_options;
private String field_3_string; private String field_3_string;
private StringPtg() { private StringPtg() {
@ -48,9 +51,8 @@ public class StringPtg
} }
/** Create a StringPtg from a byte array read from disk */ /** Create a StringPtg from a byte array read from disk */
public StringPtg(RecordInputStream in) public StringPtg(RecordInputStream in) {
{ field_1_length = in.readUByte();
field_1_length = in.readByte() & 0xFF;
field_2_options = in.readByte(); field_2_options = in.readByte();
if (fHighByte.isSet(field_2_options)) { if (fHighByte.isSet(field_2_options)) {
field_3_string = in.readUnicodeLEString(field_1_length); field_3_string = in.readUnicodeLEString(field_1_length);
@ -61,35 +63,31 @@ public class StringPtg
// setValue(new String(data, offset+3, data[offset+1] + 256*data[offset+2])); // setValue(new String(data, offset+3, data[offset+1] + 256*data[offset+2]));
} }
/** Create a StringPtg from a string representation of the number /**
* Number format is not checked, it is expected to be validated in the parser * Create a StringPtg from a string representation of the number Number
* that calls this method. * format is not checked, it is expected to be validated in the parser that
* @param value : String representation of a floating point number * calls this method.
*
* @param value :
* String representation of a floating point number
*/ */
public StringPtg(String value) { public StringPtg(String value) {
if (value.length() > 255) { if (value.length() > 255) {
throw new IllegalArgumentException("String literals in formulas cant be bigger than 255 characters ASCII"); throw new IllegalArgumentException(
"String literals in formulas can't be bigger than 255 characters ASCII");
} }
this.field_2_options=0; field_2_options = 0;
field_2_options = (byte)this.fHighByte.setBoolean(field_2_options, StringUtil.hasMultibyte(value)); field_2_options = (byte) fHighByte.setBoolean(field_2_options, StringUtil
this.field_3_string=value; .hasMultibyte(value));
this.field_1_length=value.length(); //for the moment, we support only ASCII strings in formulas we create field_3_string = value;
field_1_length = value.length(); // for the moment, we support only ASCII strings in formulas we create
} }
/* public String getValue() {
public void setValue(String value)
{
field_1_value = value;
}*/
public String getValue()
{
return field_3_string; return field_3_string;
} }
public void writeBytes(byte [] array, int offset) public void writeBytes(byte[] array, int offset) {
{
array[offset + 0] = sid; array[offset + 0] = sid;
array[offset + 1] = (byte) field_1_length; array[offset + 1] = (byte) field_1_length;
array[offset + 2] = field_2_options; array[offset + 2] = field_2_options;
@ -100,8 +98,7 @@ public class StringPtg
} }
} }
public int getSize() public int getSize() {
{
if (fHighByte.isSet(field_2_options)) { if (fHighByte.isSet(field_2_options)) {
return 2 * field_1_length + 3; return 2 * field_1_length + 3;
} else { } else {
@ -109,10 +106,24 @@ public class StringPtg
} }
} }
public String toFormulaString(HSSFWorkbook book) public String toFormulaString(HSSFWorkbook book) {
{ String value = field_3_string;
return "\""+getValue()+"\""; int len = value.length();
StringBuffer sb = new StringBuffer(len + 4);
sb.append(FORMULA_DELIMITER);
for (int i = 0; i < len; i++) {
char c = value.charAt(i);
if (c == FORMULA_DELIMITER) {
sb.append(FORMULA_DELIMITER);
} }
sb.append(c);
}
sb.append(FORMULA_DELIMITER);
return sb.toString();
}
public byte getDefaultOperandClass() { public byte getDefaultOperandClass() {
return Ptg.CLASS_VALUE; return Ptg.CLASS_VALUE;
} }
@ -125,5 +136,11 @@ public class StringPtg
return ptg; return ptg;
} }
public String toString() {
StringBuffer sb = new StringBuffer(64);
sb.append(getClass().getName()).append(" [");
sb.append(field_3_string);
sb.append("]");
return sb.toString();
}
} }

View File

@ -65,6 +65,7 @@ public final class TestFormulaParser extends TestCase {
* @return parsed token array already confirmed not <code>null</code> * @return parsed token array already confirmed not <code>null</code>
*/ */
private static Ptg[] parseFormula(String s) { private static Ptg[] parseFormula(String s) {
// TODO - replace multiple copies of this code with calls to this method
FormulaParser fp = new FormulaParser(s, null); FormulaParser fp = new FormulaParser(s, null);
fp.parse(); fp.parse();
Ptg[] result = fp.getRPNPtg(); Ptg[] result = fp.getRPNPtg();
@ -86,7 +87,6 @@ public final class TestFormulaParser extends TestCase {
assertTrue("",(ptgs[0] instanceof IntPtg)); assertTrue("",(ptgs[0] instanceof IntPtg));
assertTrue("",(ptgs[1] instanceof IntPtg)); assertTrue("",(ptgs[1] instanceof IntPtg));
assertTrue("",(ptgs[2] instanceof AddPtg)); assertTrue("",(ptgs[2] instanceof AddPtg));
} }
public void testFormulaWithSpace2() { public void testFormulaWithSpace2() {
@ -169,8 +169,6 @@ public final class TestFormulaParser extends TestCase {
assertEquals("If FALSE offset", (short)7, ifPtg.getData()); assertEquals("If FALSE offset", (short)7, ifPtg.getData());
FuncVarPtg funcPtg = (FuncVarPtg)asts[8]; FuncVarPtg funcPtg = (FuncVarPtg)asts[8];
} }
/** /**
@ -190,8 +188,6 @@ public final class TestFormulaParser extends TestCase {
assertTrue("It is not an if", ifFunc.isOptimizedIf()); assertTrue("It is not an if", ifFunc.isOptimizedIf());
assertTrue("Average Function set correctly", (asts[5] instanceof FuncVarPtg)); assertTrue("Average Function set correctly", (asts[5] instanceof FuncVarPtg));
} }
public void testIfSingleCondition(){ public void testIfSingleCondition(){
@ -213,8 +209,6 @@ public final class TestFormulaParser extends TestCase {
assertTrue("Ptg is not a Variable Function", (asts[6] instanceof FuncVarPtg)); assertTrue("Ptg is not a Variable Function", (asts[6] instanceof FuncVarPtg));
FuncVarPtg funcPtg = (FuncVarPtg)asts[6]; FuncVarPtg funcPtg = (FuncVarPtg)asts[6];
assertEquals("Arguments", 2, funcPtg.getNumberOfOperands()); assertEquals("Arguments", 2, funcPtg.getNumberOfOperands());
} }
public void testSumIf() { public void testSumIf() {
@ -223,7 +217,6 @@ public final class TestFormulaParser extends TestCase {
fp.parse(); fp.parse();
Ptg[] asts = fp.getRPNPtg(); Ptg[] asts = fp.getRPNPtg();
assertEquals("4 Ptgs expected", 4, asts.length); assertEquals("4 Ptgs expected", 4, asts.length);
} }
/** /**
@ -235,51 +228,35 @@ public final class TestFormulaParser extends TestCase {
String currencyCell = "F3"; String currencyCell = "F3";
String function="\"TOTAL[\"&"+currencyCell+"&\"]\""; String function="\"TOTAL[\"&"+currencyCell+"&\"]\"";
FormulaParser fp = new FormulaParser(function, null); Ptg[] asts = parseFormula(function);
fp.parse();
Ptg[] asts = fp.getRPNPtg();
assertEquals("5 ptgs expected", 5, asts.length); assertEquals("5 ptgs expected", 5, asts.length);
assertTrue ("Ptg[0] is a string", (asts[0] instanceof StringPtg)); assertTrue ("Ptg[0] is a string", (asts[0] instanceof StringPtg));
StringPtg firstString = (StringPtg)asts[0]; StringPtg firstString = (StringPtg)asts[0];
assertEquals("TOTAL[", firstString.getValue()); assertEquals("TOTAL[", firstString.getValue());
//the PTG order isn't 100% correct but it still works - dmui //the PTG order isn't 100% correct but it still works - dmui
} }
public void testSimpleLogical() { public void testSimpleLogical() {
FormulaParser fp=new FormulaParser("IF(A1<A2,B1,B2)",null); Ptg[] ptgs = parseFormula("IF(A1<A2,B1,B2)");
fp.parse();
Ptg[] ptgs = fp.getRPNPtg();
assertTrue("Ptg array should not be null", ptgs !=null);
assertEquals("Ptg array length", 9, ptgs.length); assertEquals("Ptg array length", 9, ptgs.length);
assertEquals("3rd Ptg is less than", LessThanPtg.class, ptgs[2].getClass()); assertEquals("3rd Ptg is less than", LessThanPtg.class, ptgs[2].getClass());
} }
public void testParenIf() { public void testParenIf() {
FormulaParser fp=new FormulaParser("IF((A1+A2)<=3,\"yes\",\"no\")",null); Ptg[] ptgs = parseFormula("IF((A1+A2)<=3,\"yes\",\"no\")");
fp.parse();
Ptg[] ptgs = fp.getRPNPtg();
assertTrue("Ptg array should not be null", ptgs !=null);
assertEquals("Ptg array length", 12, ptgs.length); assertEquals("Ptg array length", 12, ptgs.length);
assertEquals("6th Ptg is less than equal",LessEqualPtg.class,ptgs[5].getClass()); assertEquals("6th Ptg is less than equal",LessEqualPtg.class,ptgs[5].getClass());
assertEquals("11th Ptg is not a goto (Attr) ptg",AttrPtg.class,ptgs[10].getClass()); assertEquals("11th Ptg is not a goto (Attr) ptg",AttrPtg.class,ptgs[10].getClass());
} }
public void testEmbeddedIf() { public void testEmbeddedIf() {
FormulaParser fp=new FormulaParser("IF(3>=1,\"*\",IF(4<>1,\"first\",\"second\"))",null); Ptg[] ptgs = parseFormula("IF(3>=1,\"*\",IF(4<>1,\"first\",\"second\"))");
fp.parse();
Ptg[] ptgs = fp.getRPNPtg();
assertTrue("Ptg array should not be null", ptgs !=null);
assertEquals("Ptg array length", 17, ptgs.length); assertEquals("Ptg array length", 17, ptgs.length);
assertEquals("6th Ptg is not a goto (Attr) ptg",AttrPtg.class,ptgs[5].getClass()); assertEquals("6th Ptg is not a goto (Attr) ptg",AttrPtg.class,ptgs[5].getClass());
assertEquals("9th Ptg is not a not equal ptg",NotEqualPtg.class,ptgs[8].getClass()); assertEquals("9th Ptg is not a not equal ptg",NotEqualPtg.class,ptgs[8].getClass());
assertEquals("15th Ptg is not the inner IF variable function ptg",FuncVarPtg.class,ptgs[14].getClass()); assertEquals("15th Ptg is not the inner IF variable function ptg",FuncVarPtg.class,ptgs[14].getClass());
} }
public void testMacroFunction() { public void testMacroFunction() {
@ -302,7 +279,6 @@ public final class TestFormulaParser extends TestCase {
Ptg[] ptg = fp.getRPNPtg(); Ptg[] ptg = fp.getRPNPtg();
assertTrue("first ptg is string",ptg[0] instanceof StringPtg); assertTrue("first ptg is string",ptg[0] instanceof StringPtg);
assertTrue("second ptg is string",ptg[1] instanceof StringPtg); assertTrue("second ptg is string",ptg[1] instanceof StringPtg);
} }
public void testConcatenate() { public void testConcatenate() {
@ -775,8 +751,34 @@ public final class TestFormulaParser extends TestCase {
StringPtg sp = (StringPtg) parseSingleToken(formula, StringPtg.class); StringPtg sp = (StringPtg) parseSingleToken(formula, StringPtg.class);
assertEquals(expectedValue, sp.getValue()); assertEquals(expectedValue, sp.getValue());
} }
public void testParseStringLiterals_bug28754() {
public void testPaseStringLiterals() { StringPtg sp;
try {
sp = (StringPtg) parseSingleToken("\"test\"\"ing\"", StringPtg.class);
} catch (RuntimeException e) {
if(e.getMessage().startsWith("Cannot Parse")) {
throw new AssertionFailedError("Identified bug 28754a");
}
throw e;
}
assertEquals("test\"ing", sp.getValue());
HSSFWorkbook wb = new HSSFWorkbook();
HSSFSheet sheet = wb.createSheet();
wb.setSheetName(0, "Sheet1");
HSSFRow row = sheet.createRow(0);
HSSFCell cell = row.createCell((short)0);
cell.setCellFormula("right(\"test\"\"ing\", 3)");
String actualCellFormula = cell.getCellFormula();
if("RIGHT(\"test\"ing\",3)".equals(actualCellFormula)) {
throw new AssertionFailedError("Identified bug 28754b");
}
assertEquals("RIGHT(\"test\"\"ing\",3)", actualCellFormula);
}
public void testParseStringLiterals() {
confirmStringParse("goto considered harmful"); confirmStringParse("goto considered harmful");
confirmStringParse("goto 'considered' harmful"); confirmStringParse("goto 'considered' harmful");
@ -810,11 +812,9 @@ public final class TestFormulaParser extends TestCase {
parseExpectedException("#DIV/ 0+2"); parseExpectedException("#DIV/ 0+2");
if (false) { // TODO - add functionality to detect func arg count mismatch
parseExpectedException("IF(TRUE)"); parseExpectedException("IF(TRUE)");
parseExpectedException("countif(A1:B5, C1, D1)"); parseExpectedException("countif(A1:B5, C1, D1)");
} }
}
private static void parseExpectedException(String formula) { private static void parseExpectedException(String formula) {
try { try {