]> source.dussan.org Git - poi.git/commitdiff
Patch from Josh from bug #44609 - Handle leading spaces in formulas, such as '= 4'
authorNick Burch <nick@apache.org>
Sun, 16 Mar 2008 15:48:02 +0000 (15:48 +0000)
committerNick Burch <nick@apache.org>
Sun, 16 Mar 2008 15:48:02 +0000 (15:48 +0000)
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@637601 13f79535-47bb-0310-9956-ffa450edef68

src/documentation/content/xdocs/changes.xml
src/documentation/content/xdocs/status.xml
src/java/org/apache/poi/hssf/model/FormulaParser.java
src/java/org/apache/poi/hssf/record/formula/AttrPtg.java
src/testcases/org/apache/poi/hssf/model/TestFormulaParser.java

index 2b12bdf7929d35449db80aeec723be108fa79f8e..eab4f646381f84f08b392630f8bc453baf6f7a03 100644 (file)
@@ -36,6 +36,7 @@
 
                <!-- Don't forget to update status.xml too! -->
         <release version="3.1-beta1" date="2008-??-??">
+           <action dev="POI-DEVELOPERS" type="fix">44609 - Handle leading spaces in formulas, such as '= 4'</action>
            <action dev="POI-DEVELOPERS" type="add">44608 - Support for PercentPtg in the formula evaluator</action>
            <action dev="POI-DEVELOPERS" type="fix">44606 - Support calculated string values for evaluated formulas</action>
            <action dev="POI-DEVELOPERS" type="add">Add accessors to horizontal and vertical alignment in HSSFTextbox</action>
index 57bc3fa34d7f80acdd4fd0bbd1f95546c56083ab..a6467d51647da7f9534bafe47eb4d2366d3f01f7 100644 (file)
@@ -33,6 +33,7 @@
        <!-- Don't forget to update changes.xml too! -->
     <changes>
         <release version="3.1-beta1" date="2008-??-??">
+           <action dev="POI-DEVELOPERS" type="fix">44609 - Handle leading spaces in formulas, such as '= 4'</action>
            <action dev="POI-DEVELOPERS" type="add">44608 - Support for PercentPtg in the formula evaluator</action>
            <action dev="POI-DEVELOPERS" type="fix">44606 - Support calculated string values for evaluated formulas</action>
            <action dev="POI-DEVELOPERS" type="add">Add accessors to horizontal and vertical alignment in HSSFTextbox</action>
index 7b89b90d81f27193380e8f1f7d594bb228152be3..07bb51483c0c304e0007cdc5bc357c609db53670 100644 (file)
@@ -943,23 +943,7 @@ end;
         }
         Stack stack = new Stack();
 
-           // Excel allows to have AttrPtg at position 0 (such as Blanks) which
-           // do not have any operands. Skip them.
-        int i;
-        if(ptgs[0] instanceof AttrPtg) {
-            AttrPtg attrPtg0 = (AttrPtg) ptgs[0];
-            if(attrPtg0.isSemiVolatile()) {
-                // no visible formula for semi-volatile
-            } else {
-                // TODO -this requirement is unclear and is not addressed by any junits
-                stack.push(ptgs[0].toFormulaString(book));
-            }
-            i=1;
-        } else {
-            i=0;
-        }
-
-        for ( ; i < ptgs.length; i++) {
+        for (int i=0 ; i < ptgs.length; i++) {
             Ptg ptg = ptgs[i];
             // TODO - what about MemNoMemPtg?
             if(ptg instanceof MemAreaPtg || ptg instanceof MemFuncPtg || ptg instanceof MemErrPtg) {
@@ -973,21 +957,30 @@ end;
                 continue;
             }
 
-            if (ptg instanceof AttrPtg && ((AttrPtg) ptg).isOptimizedIf()) {
-                continue;
+            if (ptg instanceof AttrPtg) {
+                AttrPtg attrPtg = ((AttrPtg) ptg);
+                if (attrPtg.isOptimizedIf()) {
+                    continue;
+                }
+                if (attrPtg.isSpace()) {
+                    // POI currently doesn't render spaces in formulas
+                    continue;
+                    // but if it ever did, care must be taken:
+                    // tAttrSpace comes *before* the operand it applies to, which may be consistent
+                    // with how the formula text appears but is against the RPN ordering assumed here 
+                }
             }
 
             final OperationPtg o = (OperationPtg) ptg;
             int nOperands = o.getNumberOfOperands();
             final String[] operands = new String[nOperands];
 
-            for (int j = nOperands-1; j >= 0; j--) {
+            for (int j = nOperands-1; j >= 0; j--) { // reverse iteration because args were pushed in-order
                 if(stack.isEmpty()) {
-                    //TODO: write junit to prove this works
                    String msg = "Too few arguments suppled to operation token ("
                         + o.getClass().getName() + "). Expected (" + nOperands
-                        + " but got " + (nOperands - j + 1);
-                    throw new FormulaParseException(msg);
+                        + ") operands but got (" + (nOperands - j + 1) + ")";
+                    throw new IllegalStateException(msg);
                 }
                 operands[j] = (String) stack.pop();
             }
index 2ba47238043efd6d447c9ded6d3949e021d9ed81..d355fbfa1f3d2bb9b5e8aa4a3ac2ea40e59a269c 100644 (file)
@@ -33,20 +33,42 @@ import org.apache.poi.util.BitFieldFactory;
  * @author Jason Height (jheight at chariot dot net dot au)
  */
 
-public class AttrPtg
-    extends OperationPtg
-{
+public final class AttrPtg extends OperationPtg {
     public final static byte sid  = 0x19;
     private final static int  SIZE = 4;
     private byte              field_1_options;
     private short             field_2_data;
-    private BitField          semiVolatile = BitFieldFactory.getInstance(0x01);
-    private BitField          optiIf       = BitFieldFactory.getInstance(0x02);
-    private BitField          optiChoose   = BitFieldFactory.getInstance(0x04);
-    private BitField          optGoto      = BitFieldFactory.getInstance(0x08);
-    private BitField          sum          = BitFieldFactory.getInstance(0x10);
-    private BitField          baxcel       = BitFieldFactory.getInstance(0x20);
-    private BitField          space        = BitFieldFactory.getInstance(0x40);
+    
+    // flags 'volatile' and 'space', can be combined.  
+    // OOO spec says other combinations are theoretically possible but not likely to occur.
+    private static final BitField semiVolatile = BitFieldFactory.getInstance(0x01);
+    private static final BitField optiIf       = BitFieldFactory.getInstance(0x02);
+    private static final BitField optiChoose   = BitFieldFactory.getInstance(0x04);
+    private static final BitField optGoto      = BitFieldFactory.getInstance(0x08); // skip
+    private static final BitField sum          = BitFieldFactory.getInstance(0x10);
+    private static final BitField baxcel       = BitFieldFactory.getInstance(0x20); // 'assignment-style formula in a macro sheet'
+    private static final BitField space        = BitFieldFactory.getInstance(0x40);
+    
+    public static final class SpaceType {
+        private SpaceType() {
+            // no instances of this class
+        }
+        
+        /** 00H = Spaces before the next token (not allowed before tParen token) */
+        public static final int SPACE_BEFORE = 0x00;
+        /** 01H = Carriage returns before the next token (not allowed before tParen token) */
+        public static final int CR_BEFORE = 0x01;
+        /** 02H = Spaces before opening parenthesis (only allowed before tParen token) */
+        public static final int SPACE_BEFORE_OPEN_PAREN = 0x02;
+        /** 03H = Carriage returns before opening parenthesis (only allowed before tParen token) */
+        public static final int CR_BEFORE_OPEN_PAREN = 0x03;
+        /** 04H = Spaces before closing parenthesis (only allowed before tParen, tFunc, and tFuncVar tokens) */
+        public static final int SPACE_BEFORE_CLOSE_PAERN = 0x04;
+        /** 05H = Carriage returns before closing parenthesis (only allowed before tParen, tFunc, and tFuncVar tokens) */
+        public static final int CR_BEFORE_CLOSE_PAREN = 0x05;
+        /** 06H = Spaces following the equality sign (only in macro sheets) */
+        public static final int SPACE_AFTER_EQUALITY = 0x06;
+    }
 
     public AttrPtg() {
     }
@@ -56,6 +78,19 @@ public class AttrPtg
         field_1_options = in.readByte();
         field_2_data    = in.readShort();
     }
+    private AttrPtg(int options, int data) {
+        field_1_options = (byte) options;
+        field_2_data = (short) data;
+    }
+    
+    /**
+     * @param type a constant from <tt>SpaceType</tt>
+     * @param count the number of space characters
+     */
+    public static AttrPtg createSpace(int type, int count) {
+        int data = type & 0x00FF | (count << 8) & 0x00FFFF;
+        return new AttrPtg(space.set(0), data);
+    }
 
     public void setOptions(byte options)
     {
@@ -131,21 +166,31 @@ public class AttrPtg
         return field_2_data;
     }
 
-    public String toString()
-    {
-        StringBuffer buffer = new StringBuffer();
+    public String toString() {
+        StringBuffer sb = new StringBuffer(64);
+        sb.append(getClass().getName()).append(" [");
 
-        buffer.append("AttrPtg\n");
-        buffer.append("options=").append(field_1_options).append("\n");
-        buffer.append("data   =").append(field_2_data).append("\n");
-        buffer.append("semi   =").append(isSemiVolatile()).append("\n");
-        buffer.append("optimif=").append(isOptimizedIf()).append("\n");
-        buffer.append("optchos=").append(isOptimizedChoose()).append("\n");
-        buffer.append("isGoto =").append(isGoto()).append("\n");
-        buffer.append("isSum  =").append(isSum()).append("\n");
-        buffer.append("isBaxce=").append(isBaxcel()).append("\n");
-        buffer.append("isSpace=").append(isSpace()).append("\n");
-        return buffer.toString();
+        if(isSemiVolatile()) {
+            sb.append("volatile ");
+        }
+        if(isSpace()) {
+            sb.append("space count=").append((field_2_data >> 8) & 0x00FF);
+            sb.append(" type=").append(field_2_data & 0x00FF).append(" ");
+        }
+        // the rest seem to be mutually exclusive
+        if(isOptimizedIf()) {
+            sb.append("if dist=").append(getData());
+        } else if(isOptimizedChoose()) {
+            sb.append("choose dist=").append(getData());
+        } else if(isGoto()) {
+            sb.append("skip dist=").append(getData()); 
+        } else if(isSum()) {
+            sb.append("sum ");
+        } else if(isBaxcel()) {
+            sb.append("assign ");
+        }
+        sb.append("]");
+        return sb.toString();
     }
 
     public void writeBytes(byte [] array, int offset)
index f922e75d8280aeacdee06f9479a404f9530a6e62..b79236ff08c138d6ae158218b193d188c95082ca 100644 (file)
@@ -844,4 +844,48 @@ public final class TestFormulaParser extends TestCase {
         }
         assertEquals("SUM(A32769:A32770)", cell.getCellFormula());
     }
+
+    public void testSpaceAtStartOfFormula() {
+        // Simulating cell formula of "= 4" (note space)
+        // The same Ptg array can be observed if an excel file is saved with that exact formula
+
+        AttrPtg spacePtg = AttrPtg.createSpace(AttrPtg.SpaceType.SPACE_BEFORE, 1);
+        Ptg[] ptgs = { spacePtg, new IntPtg(4), };
+        String formulaString;
+        try {
+            formulaString = FormulaParser.toFormulaString(null, ptgs);
+        } catch (IllegalStateException e) {
+            if(e.getMessage().equalsIgnoreCase("too much stuff left on the stack")) {
+                throw new AssertionFailedError("Identified bug 44609");
+            }
+            // else some unexpected error
+            throw e;
+        }
+        // FormulaParser strips spaces anyway
+        assertEquals("4", formulaString); 
+
+        ptgs = new Ptg[] { new IntPtg(3), spacePtg, new IntPtg(4), spacePtg, new AddPtg()};
+        formulaString = FormulaParser.toFormulaString(null, ptgs);
+        assertEquals("3+4", formulaString); 
+    }
+
+    /**
+     * Checks some internal error detecting logic ('stack underflow error' in toFormulaString)
+     */
+    public void testTooFewOperandArgs() {
+        // Simulating badly encoded cell formula of "=/1"
+        // Not sure if Excel could ever produce this
+        Ptg[] ptgs = { 
+                // Excel would probably have put tMissArg here
+                new IntPtg(1),
+                new DividePtg(),
+        };
+        try {
+            FormulaParser.toFormulaString(null, ptgs);
+            fail("Expected exception was not thrown");
+        } catch (IllegalStateException e) {
+            // expected during successful test
+            assertTrue(e.getMessage().startsWith("Too few arguments suppled to operation token"));
+        }
+    }
 }