]> source.dussan.org Git - poi.git/commitdiff
Fix for bug 15716 - - converted Ptg arrays into Formula objects to optimise memory...
authorJosh Micich <josh@apache.org>
Thu, 30 Oct 2008 20:17:04 +0000 (20:17 +0000)
committerJosh Micich <josh@apache.org>
Thu, 30 Oct 2008 20:17:04 +0000 (20:17 +0000)
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@709235 13f79535-47bb-0310-9956-ffa450edef68

31 files changed:
src/documentation/content/xdocs/changes.xml
src/documentation/content/xdocs/status.xml
src/java/org/apache/poi/hssf/record/ArrayRecord.java
src/java/org/apache/poi/hssf/record/CFRuleRecord.java
src/java/org/apache/poi/hssf/record/DVRecord.java
src/java/org/apache/poi/hssf/record/ExternalNameRecord.java
src/java/org/apache/poi/hssf/record/FormulaRecord.java
src/java/org/apache/poi/hssf/record/NameRecord.java
src/java/org/apache/poi/hssf/record/SharedFormulaRecord.java
src/java/org/apache/poi/hssf/record/SharedValueRecordBase.java
src/java/org/apache/poi/hssf/record/TableRecord.java
src/java/org/apache/poi/hssf/record/aggregates/FormulaRecordAggregate.java
src/java/org/apache/poi/hssf/record/aggregates/RowRecordsAggregate.java
src/java/org/apache/poi/hssf/record/aggregates/SharedValueManager.java
src/java/org/apache/poi/hssf/record/aggregates/ValueRecordsAggregate.java
src/java/org/apache/poi/hssf/record/cf/BorderFormatting.java
src/java/org/apache/poi/hssf/record/cf/PatternFormatting.java
src/java/org/apache/poi/hssf/usermodel/HSSFBorderFormatting.java
src/java/org/apache/poi/hssf/usermodel/HSSFCell.java
src/java/org/apache/poi/hssf/usermodel/HSSFEvaluationWorkbook.java
src/java/org/apache/poi/hssf/usermodel/HSSFPatternFormatting.java
src/java/org/apache/poi/hssf/util/CellRangeAddress.java
src/java/org/apache/poi/hssf/util/CellRangeAddress8Bit.java
src/java/org/apache/poi/hssf/util/CellRangeAddressList.java
src/java/org/apache/poi/ss/formula/Formula.java [new file with mode: 0644]
src/java/org/apache/poi/util/LittleEndianByteArrayOutputStream.java
src/java/org/apache/poi/util/LittleEndianOutput.java
src/java/org/apache/poi/util/LittleEndianOutputStream.java
src/testcases/org/apache/poi/hssf/record/TestFormulaRecord.java
src/testcases/org/apache/poi/hssf/record/TestNameRecord.java
src/testcases/org/apache/poi/hssf/record/TestSharedFormulaRecord.java

index 0bd615c15b1aad7004fa893aab02b4aa01799b21..c84244ecc846811a9937050d72d1885428b7a818 100644 (file)
@@ -37,6 +37,7 @@
 
                <!-- Don't forget to update status.xml too! -->
         <release version="3.5-beta4" date="2008-??-??">
+           <action dev="POI-DEVELOPERS" type="fix">15716 - memory usage optimisation - converted Ptg arrays into Formula objects</action>
            <action dev="POI-DEVELOPERS" type="add">46065 - added implementation for VALUE function</action>
            <action dev="POI-DEVELOPERS" type="add">45966 - added implementation for FIND function</action>
            <action dev="POI-DEVELOPERS" type="fix">45778 - fixed ObjRecord to read ftLbsData properly</action>
index c704553b5ba275765ebd34c14fe93f24cf7b79f8..fb8903f7d45efb33f980e8dea9b57be76d2f910c 100644 (file)
@@ -34,6 +34,7 @@
        <!-- Don't forget to update changes.xml too! -->
     <changes>
         <release version="3.5-beta4" date="2008-??-??">
+           <action dev="POI-DEVELOPERS" type="fix">15716 - memory usage optimisation - converted Ptg arrays into Formula objects</action>
            <action dev="POI-DEVELOPERS" type="add">46065 - added implementation for VALUE function</action>
            <action dev="POI-DEVELOPERS" type="add">45966 - added implementation for FIND function</action>
            <action dev="POI-DEVELOPERS" type="fix">45778 - fixed ObjRecord to read ftLbsData properly</action>
index 7a04bdf99f2eeb3fb765bcdb9ccc954b499c64ff..35239bb23852791d1b2c1ee6c582ccaa19fdda89 100644 (file)
@@ -18,8 +18,9 @@
 package org.apache.poi.hssf.record;\r
 \r
 import org.apache.poi.hssf.record.formula.Ptg;\r
+import org.apache.poi.ss.formula.Formula;\r
 import org.apache.poi.util.HexDump;\r
-import org.apache.poi.util.LittleEndian;\r
+import org.apache.poi.util.LittleEndianOutput;\r
 \r
 /**\r
  * ARRAY (0x0221)<p/>\r
@@ -36,14 +37,15 @@ public final class ArrayRecord extends SharedValueRecordBase {
        \r
        private int     _options;\r
        private int _field3notUsed;\r
-       private Ptg[] _formulaTokens;\r
+       private Formula _formula;\r
 \r
        public ArrayRecord(RecordInputStream in) {\r
                super(in);\r
                _options = in.readUShort();\r
                _field3notUsed = in.readInt();\r
-               int formulaLen = in.readUShort();\r
-               _formulaTokens = Ptg.readTokens(formulaLen, in);\r
+               int formulaTokenLen = in.readUShort();\r
+               int totalFormulaLen = in.available();\r
+               _formula = Formula.read(formulaTokenLen, in, totalFormulaLen);\r
        }\r
 \r
        public boolean isAlwaysRecalculate() {\r
@@ -55,18 +57,12 @@ public final class ArrayRecord extends SharedValueRecordBase {
 \r
        protected int getExtraDataSize() {\r
                return 2 + 4\r
-                       + 2 + Ptg.getEncodedSize(_formulaTokens);\r
+                       + _formula.getEncodedSize();\r
        }\r
-       protected void serializeExtraData(int offset, byte[] data) {\r
-               int pos = offset;\r
-               LittleEndian.putUShort(data, pos, _options);\r
-               pos+=2;\r
-               LittleEndian.putInt(data, pos, _field3notUsed);\r
-               pos+=4;\r
-               int tokenSize = Ptg.getEncodedSizeWithoutArrayData(_formulaTokens);\r
-               LittleEndian.putUShort(data, pos, tokenSize);\r
-               pos+=2;\r
-               Ptg.serializePtgs(_formulaTokens, data, pos);\r
+       protected void serializeExtraData(LittleEndianOutput out) {\r
+               out.writeShort(_options);\r
+               out.writeInt(_field3notUsed);\r
+               _formula.serialize(out);\r
        }\r
 \r
        public short getSid() {\r
@@ -80,8 +76,9 @@ public final class ArrayRecord extends SharedValueRecordBase {
                sb.append(" options=").append(HexDump.shortToHex(_options)).append("\n");\r
                sb.append(" notUsed=").append(HexDump.intToHex(_field3notUsed)).append("\n");\r
                sb.append(" formula:").append("\n");\r
-               for (int i = 0; i < _formulaTokens.length; i++) {\r
-                       Ptg ptg = _formulaTokens[i];\r
+               Ptg[] ptgs = _formula.getTokens();\r
+               for (int i = 0; i < ptgs.length; i++) {\r
+                       Ptg ptg = ptgs[i];\r
                        sb.append(ptg.toString()).append(ptg.getRVAType()).append("\n");\r
                }\r
                sb.append("]");\r
index ef8e05c29d54998b6deac6f22f63e7d6ab89edb1..214ebaa2991cfcef6e7df18c86802adfbd00170b 100644 (file)
@@ -23,9 +23,10 @@ import org.apache.poi.hssf.record.cf.FontFormatting;
 import org.apache.poi.hssf.record.cf.PatternFormatting;
 import org.apache.poi.hssf.record.formula.Ptg;
 import org.apache.poi.hssf.usermodel.HSSFWorkbook;
+import org.apache.poi.ss.formula.Formula;
 import org.apache.poi.util.BitField;
 import org.apache.poi.util.BitFieldFactory;
-import org.apache.poi.util.LittleEndian;
+import org.apache.poi.util.LittleEndianByteArrayOutputStream;
 
 /**
  * Conditional Formatting Rule Record.
@@ -94,18 +95,12 @@ public final class CFRuleRecord extends Record {
 
        private FontFormatting fontFormatting;
 
-       private byte  field_8_align_text_break;
-       private byte  field_9_align_text_rotation_angle;
-       private short field_10_align_indentation;
-       private short field_11_relative_indentation;
-       private short field_12_not_used;
-       
        private BorderFormatting borderFormatting;
 
        private PatternFormatting patternFormatting;
        
-       private Ptg[] field_17_formula1;
-       private Ptg[] field_18_formula2;
+       private Formula field_17_formula1;
+       private Formula field_18_formula2;
        
        /** Creates new CFRuleRecord */
        private CFRuleRecord(byte conditionType, byte comparisonOperation)
@@ -121,23 +116,18 @@ public final class CFRuleRecord extends Record {
 
                field_6_not_used = (short)0x8002; // Excel seems to write this value, but it doesn't seem to care what it reads
                fontFormatting=null;
-               field_8_align_text_break = 0;
-               field_9_align_text_rotation_angle = 0;
-               field_10_align_indentation = 0;
-               field_11_relative_indentation = 0;
-               field_12_not_used = 0;
                borderFormatting=null;
                patternFormatting=null;
-               field_17_formula1=null;
-               field_18_formula2=null;
+               field_17_formula1=Formula.create(Ptg.EMPTY_PTG_ARRAY);
+               field_18_formula2=Formula.create(Ptg.EMPTY_PTG_ARRAY);
        }
        
        private CFRuleRecord(byte conditionType, byte comparisonOperation, Ptg[] formula1, Ptg[] formula2) {
                this(conditionType, comparisonOperation); 
                field_1_condition_type = CONDITION_TYPE_CELL_VALUE_IS;
                field_2_comparison_operator = comparisonOperation;
-               field_17_formula1 = formula1;
-               field_18_formula2 = formula2;
+               field_17_formula1 = Formula.create(formula1);
+               field_18_formula2 = Formula.create(formula2);
        }
 
        /**
@@ -178,12 +168,9 @@ public final class CFRuleRecord extends Record {
                        patternFormatting = new PatternFormatting(in);
                }
 
-               if (field_3_formula1_len > 0) {
-                       field_17_formula1 = Ptg.readTokens(field_3_formula1_len, in);
-               }
-               if (field_4_formula2_len > 0) {
-                       field_18_formula2 = Ptg.readTokens(field_4_formula2_len, in);
-               }
+               // "You may not use unions, intersections or array constants in Conditional Formatting criteria"
+               field_17_formula1 = Formula.read(field_3_formula1_len, in);
+               field_18_formula2 = Formula.read(field_4_formula2_len, in);
        }
 
        public byte getConditionType()
@@ -414,33 +401,22 @@ public final class CFRuleRecord extends Record {
 
        public Ptg[] getParsedExpression1()
        {
-               return field_17_formula1;
+               return field_17_formula1.getTokens();
        }
        public void setParsedExpression1(Ptg[] ptgs) {
-               field_17_formula1 = safeClone(ptgs);
-       }
-       private static Ptg[] safeClone(Ptg[] ptgs) {
-               if (ptgs == null) {
-                       return null;
-               }
-               return (Ptg[]) ptgs.clone();
+               field_17_formula1 = Formula.create(ptgs);
        }
 
        /**
         * get the stack of the 2nd expression as a list
         *
-        * @return list of tokens (casts stack to a list and returns it!)
-        * this method can return null is we are unable to create Ptgs from 
-        *       existing excel file
-        * callers should check for null!
+        * @return array of {@link Ptg}s, possibly <code>null</code>
         */
-
-       public Ptg[] getParsedExpression2()
-       {
-               return field_18_formula2;
+       public Ptg[] getParsedExpression2() {
+               return Formula.getTokens(field_18_formula2);
        }
        public void setParsedExpression2(Ptg[] ptgs) {
-               field_18_formula2 = safeClone(ptgs);
+               field_18_formula2 = Formula.create(ptgs);
        }
 
        public short getSid()
@@ -449,14 +425,11 @@ public final class CFRuleRecord extends Record {
        }
 
        /**
-        * @param ptgs may be <code>null</code>
-        * @return encoded size of the formula
+        * @param ptgs must not be <code>null</code>
+        * @return encoded size of the formula tokens (does not include 2 bytes for ushort length)
         */
-       private static int getFormulaSize(Ptg[] ptgs) {
-               if (ptgs == null) {
-                       return 0;
-               }
-               return Ptg.getEncodedSize(ptgs);
+       private static int getFormulaSize(Formula formula) {
+               return formula.getEncodedTokenSize();
        }
        
        /**
@@ -468,51 +441,43 @@ public final class CFRuleRecord extends Record {
         * @param data byte array containing instance data
         * @return number of bytes written
         */
-       public int serialize(int pOffset, byte [] data)
-       {
+       public int serialize(int pOffset, byte [] data) {
                
                int formula1Len=getFormulaSize(field_17_formula1);
                int formula2Len=getFormulaSize(field_18_formula2);
                
-               int offset = pOffset;
                int recordsize = getRecordSize();
-               LittleEndian.putShort(data, 0 + offset, sid);
-               LittleEndian.putShort(data, 2 + offset, (short)(recordsize-4));
-               data[4 + offset] = field_1_condition_type;
-               data[5 + offset] = field_2_comparison_operator;
-               LittleEndian.putUShort(data, 6 + offset, formula1Len);
-               LittleEndian.putUShort(data, 8 + offset, formula2Len);
-               LittleEndian.putInt(data,  10 + offset, field_5_options);
-               LittleEndian.putShort(data,14 + offset, field_6_not_used);
                
-               offset += 16;
+               LittleEndianByteArrayOutputStream out = new LittleEndianByteArrayOutputStream(data, pOffset, recordsize);
                
-               if( containsFontFormattingBlock() )
-               {
+               out.writeShort(sid);
+               out.writeShort(recordsize-4);
+               out.writeByte(field_1_condition_type);
+               out.writeByte(field_2_comparison_operator);
+               out.writeShort(formula1Len);
+               out.writeShort(formula2Len);
+               out.writeInt(field_5_options);
+               out.writeShort(field_6_not_used);
+               
+               if (containsFontFormattingBlock()) {
                        byte[] fontFormattingRawRecord  = fontFormatting.getRawRecord();
-                       System.arraycopy(fontFormattingRawRecord, 0, data, offset, fontFormattingRawRecord.length);
-                       offset += fontFormattingRawRecord.length;
+                       out.write(fontFormattingRawRecord);
                }
                
-               if( containsBorderFormattingBlock())
-               {
-                       offset += borderFormatting.serialize(offset, data);
+               if (containsBorderFormattingBlock()) {
+                       borderFormatting.serialize(out);
                }
                
-               if( containsPatternFormattingBlock() )
-               {
-                       offset += patternFormatting.serialize(offset, data);
+               if (containsPatternFormattingBlock()) {
+                       patternFormatting.serialize(out);
                }
                
-               if (field_17_formula1 != null) {
-                       offset += Ptg.serializePtgs(field_17_formula1, data, offset);
-               }
-
-               if (field_18_formula2 != null) {
-                       offset += Ptg.serializePtgs(field_18_formula2, data, offset);
-               }
-               if(offset - pOffset != recordsize) {
-                       throw new IllegalStateException("write mismatch (" + (offset - pOffset) + "!=" + recordsize + ")");
+               field_17_formula1.serializeTokens(out);
+               field_18_formula2.serializeTokens(out);
+               
+               if(out.getWriteIndex() - pOffset != recordsize) {
+                       throw new IllegalStateException("write mismatch (" 
+                                       + (out.getWriteIndex() - pOffset) + "!=" + recordsize + ")");
                }
                return recordsize;
        }
@@ -531,25 +496,22 @@ public final class CFRuleRecord extends Record {
        }
 
 
-       public String toString()
-       {
+       public String toString() {
                StringBuffer buffer = new StringBuffer();
                buffer.append("[CFRULE]\n");
                buffer.append("    OPTION FLAGS=0x"+Integer.toHexString(getOptions()));
-               /*
-               if( containsFontFormattingBlock())
-               {
-                       buffer.append(fontFormatting.toString());
+               if (false) {
+                       if (containsFontFormattingBlock()) {
+                               buffer.append(fontFormatting.toString());
+                       }
+                       if (containsBorderFormattingBlock()) {
+                               buffer.append(borderFormatting.toString());
+                       }
+                       if (containsPatternFormattingBlock()) {
+                               buffer.append(patternFormatting.toString());
+                       }
+                       buffer.append("[/CFRULE]\n");
                }
-               if( containsBorderFormattingBlock())
-               {
-                       buffer.append(borderFormatting.toString());
-               }
-               if( containsPatternFormattingBlock())
-               {
-                       buffer.append(patternFormatting.toString());
-               }
-               buffer.append("[/CFRULE]\n");*/
                return buffer.toString();
        }
        
@@ -566,12 +528,8 @@ public final class CFRuleRecord extends Record {
                if (containsPatternFormattingBlock()) {
                        rec.patternFormatting = (PatternFormatting) patternFormatting.clone();
                }
-               if (field_17_formula1 != null) {
-                       rec.field_17_formula1 = (Ptg[]) field_17_formula1.clone();
-               }
-               if (field_18_formula2 != null) {
-                       rec.field_18_formula2 = (Ptg[]) field_18_formula2.clone();
-               }
+               rec.field_17_formula1 = field_17_formula1.copy();
+               rec.field_18_formula2 = field_17_formula1.copy();
 
                return rec;
        }
index 0c0cd1b73f581e3b8e450dfe866b9ee80c21810f..44235467689b6d87b9c4e12e4a77faffe8034f83 100644 (file)
 
 package org.apache.poi.hssf.record;
 
-import org.apache.poi.hssf.record.UnicodeString.UnicodeRecordStats;
 import org.apache.poi.hssf.record.formula.Ptg;
 import org.apache.poi.hssf.usermodel.DVConstraint;
 import org.apache.poi.hssf.usermodel.HSSFDataValidation;
 import org.apache.poi.hssf.util.CellRangeAddress;
 import org.apache.poi.hssf.util.CellRangeAddressList;
+import org.apache.poi.ss.formula.Formula;
 import org.apache.poi.util.BitField;
-import org.apache.poi.util.LittleEndian;
+import org.apache.poi.util.LittleEndianByteArrayOutputStream;
+import org.apache.poi.util.LittleEndianOutput;
+import org.apache.poi.util.StringUtil;
 
 /**
  * Title:        DATAVALIDATION Record (0x01BE)<p/>
@@ -53,11 +55,11 @@ public final class DVRecord extends Record {
        /** Not used - Excel seems to always write 0x3FE0 */
        private short _not_used_1 = 0x3FE0;
        /** Formula data for first condition (RPN token array without size field) */
-       private Ptg[] _formula1;
+       private Formula _formula1;
        /** Not used - Excel seems to always write 0x0000 */
        private short _not_used_2 = 0x0000;
        /** Formula data for second condition (RPN token array without size field) */
-       private Ptg[] _formula2;
+       private Formula _formula2;
        /** Cell range address list with all affected ranges */
        private CellRangeAddressList _regions;
 
@@ -96,34 +98,36 @@ public final class DVRecord extends Record {
                _promptText = resolveTitleText(promptText);
                _errorTitle = resolveTitleText(errorTitle);
                _errorText = resolveTitleText(errorText);
-               _formula1 = formula1;
-               _formula2 = formula2;
+               _formula1 = Formula.create(formula1);
+               _formula2 = Formula.create(formula2);
                _regions = regions;
        }
 
        public DVRecord(RecordInputStream in) {
-               
-          _option_flags = in.readInt();
-          
-          _promptTitle = readUnicodeString(in);
-          _errorTitle = readUnicodeString(in);
-          _promptText = readUnicodeString(in);
-          _errorText = readUnicodeString(in);
 
-          int field_size_first_formula = in.readUShort(); 
-          _not_used_1 = in.readShort();
+               _option_flags = in.readInt();
+
+               _promptTitle = readUnicodeString(in);
+               _errorTitle = readUnicodeString(in);
+               _promptText = readUnicodeString(in);
+               _errorText = readUnicodeString(in);
+
+               int field_size_first_formula = in.readUShort();
+               _not_used_1 = in.readShort();
 
-          //read first formula data condition
-          _formula1 = Ptg.readTokens(field_size_first_formula, in);
+               // "You may not use unions, intersections or array constants in Data Validation criteria"
 
-          int field_size_sec_formula = in.readUShort(); 
-          _not_used_2 = in.readShort();
+               // read first formula data condition
+               _formula1 = Formula.read(field_size_first_formula, in);
 
-          //read sec formula data condition
-          _formula2 = Ptg.readTokens(field_size_sec_formula, in);
+               int field_size_sec_formula = in.readUShort();
+               _not_used_2 = in.readShort();
 
-          //read cell range address list with all affected ranges
-          _regions = new CellRangeAddressList(in);
+               // read sec formula data condition
+               _formula2 = Formula.read(field_size_sec_formula, in);
+
+               // read cell range address list with all affected ranges
+               _regions = new CellRangeAddressList(in);
        }
 
        // --> start option flags
@@ -235,45 +239,43 @@ public final class DVRecord extends Record {
                return str;
        }
 
-       private void appendFormula(StringBuffer sb, String label, Ptg[] ptgs) {
+       private static void appendFormula(StringBuffer sb, String label, Formula f) {
                sb.append(label);
-               if (ptgs.length < 1) {
+               
+               if (f == null) {
                        sb.append("<empty>\n");
                        return;
                }
-               sb.append("\n");
+               Ptg[] ptgs = f.getTokens();
+               sb.append('\n');
                for (int i = 0; i < ptgs.length; i++) {
                        sb.append('\t').append(ptgs[i].toString()).append('\n');
                }
        }
 
        public int serialize(int offset, byte [] data) {
-               int size = this.getRecordSize();
-               LittleEndian.putShort(data, 0 + offset, sid);
-               LittleEndian.putShort(data, 2 + offset, ( short ) (size-4));
+               int recSize = getRecordSize();
+               LittleEndianOutput out = new LittleEndianByteArrayOutputStream(data, offset, recSize);
+               
+               out.writeShort(sid);
+               out.writeShort(recSize-4);
 
-               int pos = 4;
-               LittleEndian.putInt(data, pos + offset, _option_flags);
-               pos += 4;
+               out.writeInt(_option_flags);
                
-               pos += serializeUnicodeString(_promptTitle, pos+offset, data);
-               pos += serializeUnicodeString(_errorTitle, pos+offset, data);
-               pos += serializeUnicodeString(_promptText, pos+offset, data);
-               pos += serializeUnicodeString(_errorText, pos+offset, data);
-               LittleEndian.putUShort(data, offset+pos, Ptg.getEncodedSize(_formula1));
-               pos += 2;
-               LittleEndian.putUShort(data, offset+pos, _not_used_1);
-               pos += 2;
-
-               pos += Ptg.serializePtgs(_formula1, data, pos+offset);
-
-               LittleEndian.putUShort(data, offset+pos, Ptg.getEncodedSize(_formula2));
-               pos += 2;
-               LittleEndian.putShort(data, offset+pos, _not_used_2);
-               pos += 2;
-               pos += Ptg.serializePtgs(_formula2, data, pos+offset);
-               _regions.serialize(pos+offset, data);
-               return size;
+               serializeUnicodeString(_promptTitle, out);
+               serializeUnicodeString(_errorTitle, out);
+               serializeUnicodeString(_promptText, out);
+               serializeUnicodeString(_errorText, out);
+               out.writeShort(_formula1.getEncodedTokenSize());
+               out.writeShort(_not_used_1);
+               _formula1.serializeTokens(out);
+               
+               out.writeShort(_formula2.getEncodedTokenSize());
+               out.writeShort(_not_used_2);
+               _formula2.serializeTokens(out);
+               
+               _regions.serialize(out);
+               return recSize;
        }
 
        /**
@@ -293,13 +295,12 @@ public final class DVRecord extends Record {
                return new UnicodeString(in);
        }
 
-       private static int serializeUnicodeString(UnicodeString us, int offset, byte[] data) {
-               UnicodeRecordStats urs = new UnicodeRecordStats();
-               us.serialize(urs, offset, data);
-               return urs.recordSize;
+       private static void serializeUnicodeString(UnicodeString us, LittleEndianOutput out) {
+               StringUtil.writeUnicodeString(out, us.getString());
        }
-       private static int getUnicodeStringSize(UnicodeString str) {
-               return 3 + str.getString().length();
+       private static int getUnicodeStringSize(UnicodeString us) {
+               String str = us.getString();
+               return 3 + str.length() * (StringUtil.hasMultibyte(str) ? 2 : 1);
        }
 
        public int getRecordSize()  {
@@ -308,8 +309,8 @@ public final class DVRecord extends Record {
                size += getUnicodeStringSize(_errorTitle);
                size += getUnicodeStringSize(_promptText);
                size += getUnicodeStringSize(_errorText);
-               size += Ptg.getEncodedSize(_formula1);
-               size += Ptg.getEncodedSize(_formula2);
+               size += _formula1.getEncodedTokenSize();
+               size += _formula2.getEncodedTokenSize();
                size += _regions.getSize();
                return size;
        }
index 4d7f312bd5942f9d001a1b1f7b6fc0a85885e8b8..a3dfe575c18a73fa9830afe4543f8409664259d1 100755 (executable)
 
 package org.apache.poi.hssf.record;
 
-import org.apache.poi.hssf.record.formula.Ptg;
-import org.apache.poi.util.LittleEndian;
+import org.apache.poi.ss.formula.Formula;
+import org.apache.poi.util.LittleEndianByteArrayOutputStream;
+import org.apache.poi.util.LittleEndianOutput;
 import org.apache.poi.util.StringUtil;
 
 /**
- * EXTERNALNAME<p/>
+ * EXTERNALNAME (0x0023)<p/>
  * 
  * @author Josh Micich
  */
 public final class ExternalNameRecord extends Record {
 
-       private static final Ptg[] EMPTY_PTG_ARRAY = { };
-
-       public final static short sid = 0x23; // as per BIFF8. (some old versions used 0x223)
+       public final static short sid = 0x0023; // as per BIFF8. (some old versions used 0x223)
 
        private static final int OPT_BUILTIN_NAME          = 0x0001;
        private static final int OPT_AUTOMATIC_LINK        = 0x0002; // m$ doc calls this fWantAdvise 
@@ -45,7 +44,7 @@ public final class ExternalNameRecord extends Record {
        private short  field_2_index;
        private short  field_3_not_used;
        private String field_4_name;
-       private Ptg[]  field_5_name_definition;
+       private Formula  field_5_name_definition;
 
        /**
         * Convenience Function to determine if the name is a built-in name
@@ -88,7 +87,7 @@ public final class ExternalNameRecord extends Record {
                int result = 3 * 2  // 3 short fields
                        + 2 + field_4_name.length(); // nameLen and name
                if(hasFormula()) {
-                       result += 2 + getNameDefinitionSize(); // nameDefLen and nameDef
+                       result += field_5_name_definition.getEncodedSize();
                }
                return result;
        }
@@ -104,28 +103,23 @@ public final class ExternalNameRecord extends Record {
         */
        public int serialize( int offset, byte[] data ) {
                int dataSize = getDataSize();
-
-               LittleEndian.putShort( data, 0 + offset, sid );
-               LittleEndian.putShort( data, 2 + offset, (short) dataSize );
-               LittleEndian.putShort( data, 4 + offset, field_1_option_flag );
-               LittleEndian.putShort( data, 6 + offset, field_2_index );
-               LittleEndian.putShort( data, 8 + offset, field_3_not_used );
+               int recSize = dataSize + 4;
+               LittleEndianOutput out = new LittleEndianByteArrayOutputStream(data, offset, recSize);
+
+               out.writeShort(sid);
+               out.writeShort(dataSize);
+               out.writeShort(field_1_option_flag);
+               out.writeShort(field_2_index);
+               out.writeShort(field_3_not_used);
                int nameLen = field_4_name.length();
-               LittleEndian.putUShort( data, 10 + offset, nameLen );
-               StringUtil.putCompressedUnicode( field_4_name, data, 12 + offset );
-               if(hasFormula()) {
-                       int defLen = getNameDefinitionSize();
-                       LittleEndian.putUShort( data, 12 + nameLen + offset, defLen );
-                       Ptg.serializePtgs(field_5_name_definition, data, 14 + nameLen + offset );
+               out.writeShort(nameLen);
+               StringUtil.putCompressedUnicode(field_4_name, out);
+               if (hasFormula()) {
+                       field_5_name_definition.serialize(out);
                }
-               return dataSize + 4;
+               return recSize;
        }
 
-       private int getNameDefinitionSize() {
-               return Ptg.getEncodedSize(field_5_name_definition);
-       }
-
-
        public int getRecordSize(){
                return 4 + getDataSize();
        }
@@ -141,14 +135,16 @@ public final class ExternalNameRecord extends Record {
                        if(in.remaining() > 0) {
                                throw readFail("Some unread data (is formula present?)");
                        }
-                       field_5_name_definition = EMPTY_PTG_ARRAY;
+                       field_5_name_definition = null;
                        return;
                }
-               if(in.remaining() <= 0) {
+               int nBytesRemaining = in.available();
+               if(nBytesRemaining <= 0) {
                        throw readFail("Ran out of record data trying to read formula.");
                }
-               short formulaLen = in.readShort();
-               field_5_name_definition = Ptg.readTokens(formulaLen, in);
+               int formulaLen = in.readUShort();
+               nBytesRemaining -=2;
+               field_5_name_definition = Formula.read(formulaLen, in, nBytesRemaining);
        }
        /*
         * Makes better error messages (while hasFormula() is not reliable) 
@@ -157,7 +153,7 @@ public final class ExternalNameRecord extends Record {
        private RuntimeException readFail(String msg) {
                String fullMsg = msg + " fields: (option=" + field_1_option_flag + " index=" + field_2_index 
                + " not_used=" + field_3_not_used + " name='" + field_4_name + "')";
-               return new RuntimeException(fullMsg);
+               return new RecordFormatException(fullMsg);
        }
 
        private boolean hasFormula() {
index a6c97d46efd85ec4b98e93df859409de210d535e..e51ed77a96705cd125d424bbfdc10501af89082a 100644 (file)
@@ -20,10 +20,13 @@ package org.apache.poi.hssf.record;
 import org.apache.poi.hssf.record.formula.Ptg;
 import org.apache.poi.hssf.record.formula.eval.ErrorEval;
 import org.apache.poi.hssf.usermodel.HSSFCell;
+import org.apache.poi.ss.formula.Formula;
 import org.apache.poi.util.BitField;
 import org.apache.poi.util.BitFieldFactory;
 import org.apache.poi.util.HexDump;
-import org.apache.poi.util.LittleEndian;
+import org.apache.poi.util.LittleEndianByteArrayOutputStream;
+import org.apache.poi.util.LittleEndianInput;
+import org.apache.poi.util.LittleEndianOutput;
 
 /**
  * Formula Record (0x0006).
@@ -35,7 +38,7 @@ import org.apache.poi.util.LittleEndian;
 public final class FormulaRecord extends Record implements CellValueRecordInterface {
 
        public static final short sid = 0x0006;   // docs say 406...because of a bug Microsoft support site article #Q184647)
-       private static int FIXED_SIZE = 22;
+       private static int FIXED_SIZE = 20;
 
        private static final BitField alwaysCalc = BitFieldFactory.getInstance(0x0001);
        private static final BitField calcOnLoad = BitFieldFactory.getInstance(0x0002);
@@ -92,9 +95,9 @@ public final class FormulaRecord extends Record implements CellValueRecordInterf
                        }
                        return new SpecialCachedValue(result);
                }
-               public void serialize(byte[] data, int offset) {
-                       System.arraycopy(_variableData, 0, data, offset, VARIABLE_DATA_LENGTH);
-                       LittleEndian.putUShort(data, offset+VARIABLE_DATA_LENGTH, 0xFFFF);
+               public void serialize(LittleEndianOutput out) {
+                       out.write(_variableData);
+                       out.writeShort(0xFFFF);
                }
                public String formatDebugString() {
                        return formatValue() + ' ' + HexDump.toHex(_variableData);
@@ -172,8 +175,13 @@ public final class FormulaRecord extends Record implements CellValueRecordInterf
        private short  field_3_xf;
        private double field_4_value;
        private short  field_5_options;
-       private int    field_6_zero;
-       private Ptg[]  field_8_parsed_expr;
+       /**
+        * Unused field.  As it turns out this field is often not zero..
+        * According to Microsoft Excel Developer's Kit Page 318:
+        * when writing the chn field (offset 20), it's supposed to be 0 but ignored on read
+        */
+       private int field_6_zero;
+       private Formula field_8_parsed_expr;
 
        /**
         * Since the NaN support seems sketchy (different constants) we'll store and spit it out directly
@@ -183,13 +191,14 @@ public final class FormulaRecord extends Record implements CellValueRecordInterf
        /** Creates new FormulaRecord */
 
        public FormulaRecord() {
-               field_8_parsed_expr = Ptg.EMPTY_PTG_ARRAY;
+               field_8_parsed_expr = Formula.create(Ptg.EMPTY_PTG_ARRAY);
        }
 
-       public FormulaRecord(RecordInputStream in) {
-               field_1_row      = in.readUShort();
-               field_2_column  = in.readShort();
-               field_3_xf        = in.readShort();
+       public FormulaRecord(RecordInputStream ris) {
+               LittleEndianInput in = ris;
+               field_1_row = in.readUShort();
+               field_2_column = in.readShort();
+               field_3_xf = in.readShort();
                long valueLongBits  = in.readLong();
                field_5_options = in.readShort();
                specialCachedValue = SpecialCachedValue.create(valueLongBits);
@@ -197,14 +206,11 @@ public final class FormulaRecord extends Record implements CellValueRecordInterf
                        field_4_value = Double.longBitsToDouble(valueLongBits);
                }
 
-               field_6_zero               = in.readInt();
+               field_6_zero = in.readInt();
+
                int field_7_expression_len = in.readShort(); // this length does not include any extra array data
-               field_8_parsed_expr = Ptg.readTokens(field_7_expression_len, in);
-               if (in.remaining() == 10) {
-                       // TODO - this seems to occur when IntersectionPtg is present
-                       // 10 extra bytes are just 0x01 and 0x00
-                       // This causes POI stderr: "WARN. Unread 10 bytes of record 0x6"
-               }
+               int nBytesAvailable = in.available();
+               field_8_parsed_expr = Formula.read(field_7_expression_len, in, nBytesAvailable);
        }
 
 
@@ -336,11 +342,11 @@ public final class FormulaRecord extends Record implements CellValueRecordInterf
         * @return the formula tokens. never <code>null</code>
         */
        public Ptg[] getParsedExpression() {
-               return (Ptg[]) field_8_parsed_expr.clone();
+               return field_8_parsed_expr.getTokens();
        }
 
        public void setParsedExpression(Ptg[] ptgs) {
-               field_8_parsed_expr = ptgs;
+               field_8_parsed_expr = Formula.create(ptgs);
        }
 
        public short getSid() {
@@ -348,33 +354,30 @@ public final class FormulaRecord extends Record implements CellValueRecordInterf
        }
 
        private int getDataSize() {
-               return FIXED_SIZE + Ptg.getEncodedSize(field_8_parsed_expr);
+               return FIXED_SIZE + field_8_parsed_expr.getEncodedSize();
        }
        public int serialize(int offset, byte [] data) {
 
                int dataSize = getDataSize();
-
-               LittleEndian.putShort(data, 0 + offset, sid);
-               LittleEndian.putUShort(data, 2 + offset, dataSize);
-               LittleEndian.putUShort(data, 4 + offset, getRow());
-               LittleEndian.putShort(data, 6 + offset, getColumn());
-               LittleEndian.putShort(data, 8 + offset, getXFIndex());
+               int recSize = 4 + dataSize;
+               LittleEndianOutput out = new LittleEndianByteArrayOutputStream(data, offset, recSize);
+               out.writeShort(sid);
+               out.writeShort(dataSize);
+               out.writeShort(getRow());
+               out.writeShort(getColumn());
+               out.writeShort(getXFIndex());
 
                if (specialCachedValue == null) {
-                       LittleEndian.putDouble(data, 10 + offset, field_4_value);
+                       out.writeDouble(field_4_value);
                } else {
-                       specialCachedValue.serialize(data, 10+offset);
+                       specialCachedValue.serialize(out);
                }
 
-               LittleEndian.putShort(data, 18 + offset, getOptions());
+               out.writeShort(getOptions());
 
-               //when writing the chn field (offset 20), it's supposed to be 0 but ignored on read
-               //Microsoft Excel Developer's Kit Page 318
-               LittleEndian.putInt(data, 20 + offset, 0);
-               int formulaTokensSize = Ptg.getEncodedSizeWithoutArrayData(field_8_parsed_expr);
-               LittleEndian.putUShort(data, 24 + offset, formulaTokensSize);
-               Ptg.serializePtgs(field_8_parsed_expr, data, 26+offset);
-               return 4 + dataSize;
+               out.writeInt(field_6_zero); // may as well write original data back so as to minimise differences from original
+               field_8_parsed_expr.serialize(out);
+               return recSize;
        }
 
        public int getRecordSize() {
@@ -385,24 +388,25 @@ public final class FormulaRecord extends Record implements CellValueRecordInterf
 
                StringBuffer sb = new StringBuffer();
                sb.append("[FORMULA]\n");
-               sb.append("     .row       = ").append(HexDump.shortToHex(getRow())).append("\n");
-               sb.append("     .column = ").append(HexDump.shortToHex(getColumn())).append("\n");
-               sb.append("     .xf             = ").append(HexDump.shortToHex(getXFIndex())).append("\n");
-               sb.append("     .value   = ");
+               sb.append("  .row          = ").append(HexDump.shortToHex(getRow())).append("\n");
+               sb.append("  .column    = ").append(HexDump.shortToHex(getColumn())).append("\n");
+               sb.append("  .xf                = ").append(HexDump.shortToHex(getXFIndex())).append("\n");
+               sb.append("  .value      = ");
                if (specialCachedValue == null) {
                        sb.append(field_4_value).append("\n");
                } else {
                        sb.append(specialCachedValue.formatDebugString()).append("\n");
                }
-               sb.append("     .options   = ").append(HexDump.shortToHex(getOptions())).append("\n");
-               sb.append("     .alwaysCalc= ").append(alwaysCalc.isSet(getOptions())).append("\n");
-               sb.append("     .calcOnLoad= ").append(calcOnLoad.isSet(getOptions())).append("\n");
-               sb.append("     .shared = ").append(sharedFormula.isSet(getOptions())).append("\n");
-               sb.append("     .zero     = ").append(HexDump.intToHex(field_6_zero)).append("\n");
-
-               for (int k = 0; k < field_8_parsed_expr.length; k++ ) {
-                       sb.append("      Ptg[").append(k).append("]=");
-                       Ptg ptg = field_8_parsed_expr[k];
+               sb.append("  .options   = ").append(HexDump.shortToHex(getOptions())).append("\n");
+               sb.append("    .alwaysCalc= ").append(isAlwaysCalc()).append("\n");
+               sb.append("    .calcOnLoad= ").append(isCalcOnLoad()).append("\n");
+               sb.append("    .shared    = ").append(isSharedFormula()).append("\n");
+               sb.append("  .zero      = ").append(HexDump.intToHex(field_6_zero)).append("\n");
+
+               Ptg[] ptgs = field_8_parsed_expr.getTokens();
+               for (int k = 0; k < ptgs.length; k++ ) {
+                       sb.append("    Ptg[").append(k).append("]=");
+                       Ptg ptg = ptgs[k];
                        sb.append(ptg.toString()).append(ptg.getRVAType()).append("\n");
                }
                sb.append("[/FORMULA]\n");
@@ -417,12 +421,7 @@ public final class FormulaRecord extends Record implements CellValueRecordInterf
                rec.field_4_value = field_4_value;
                rec.field_5_options = field_5_options;
                rec.field_6_zero = field_6_zero;
-               int nTokens = field_8_parsed_expr.length;
-               Ptg[] ptgs = new Ptg[nTokens];
-               for (int i = 0; i < nTokens; i++) {
-                       ptgs[i] = field_8_parsed_expr[i].copy();
-               }
-               rec.field_8_parsed_expr = ptgs;
+               rec.field_8_parsed_expr = field_8_parsed_expr;
                rec.specialCachedValue = specialCachedValue;
                return rec;
        }
index 00114c0e25fc5fc55efb114b28c435b067f30c67..f75716d5093c7842c7837dcf7fb93f7526b11a19 100644 (file)
@@ -28,8 +28,10 @@ import org.apache.poi.hssf.record.formula.UnionPtg;
 import org.apache.poi.hssf.usermodel.HSSFWorkbook;
 import org.apache.poi.hssf.util.AreaReference;
 import org.apache.poi.hssf.util.RangeAddress;
+import org.apache.poi.ss.formula.Formula;
 import org.apache.poi.util.HexDump;
-import org.apache.poi.util.LittleEndian;
+import org.apache.poi.util.LittleEndianByteArrayOutputStream;
+import org.apache.poi.util.LittleEndianInput;
 import org.apache.poi.util.StringUtil;
 
 /**
@@ -89,7 +91,7 @@ public final class NameRecord extends Record {
        private boolean           field_11_nameIsMultibyte;
        private byte              field_12_built_in_code;
        private String            field_12_name_text;
-       private Ptg[]             field_13_name_definition;
+       private Formula           field_13_name_definition;
        private String            field_14_custom_menu_text;
        private String            field_15_description_text;
        private String            field_16_help_topic_text;
@@ -98,7 +100,7 @@ public final class NameRecord extends Record {
 
        /** Creates new NameRecord */
        public NameRecord() {
-               field_13_name_definition = Ptg.EMPTY_PTG_ARRAY;
+               field_13_name_definition = Formula.create(Ptg.EMPTY_PTG_ARRAY);
 
                field_12_name_text = "";
                field_14_custom_menu_text = "";
@@ -245,7 +247,7 @@ public final class NameRecord extends Record {
         * @return <code>true</code> if name has a formula (named range or defined value)
         */
        public boolean hasFormula() {
-               return field_1_option_flag == 0 && field_13_name_definition.length > 0;
+               return field_1_option_flag == 0 && field_13_name_definition.getEncodedTokenSize() > 0;
        }
 
        /**
@@ -296,11 +298,11 @@ public final class NameRecord extends Record {
         * @return the name formula. never <code>null</code>
         */
        public Ptg[] getNameDefinition() {
-               return (Ptg[]) field_13_name_definition.clone();
+               return field_13_name_definition.getTokens();
        }
 
        public void setNameDefinition(Ptg[] ptgs) {
-               field_13_name_definition = (Ptg[]) ptgs.clone();
+               field_13_name_definition = Formula.create(ptgs);
        }
 
        /** get the custom menu text
@@ -346,9 +348,9 @@ public final class NameRecord extends Record {
                int field_9_length_help_topic_text = field_16_help_topic_text.length();
                int field_10_length_status_bar_text = field_17_status_bar_text.length();
                int rawNameSize = getNameRawSize();
-               
-               int formulaTotalSize = Ptg.getEncodedSize(field_13_name_definition);
-               int dataSize = 15 // 4 shorts + 7 bytes
+
+               int formulaTotalSize = field_13_name_definition.getEncodedSize();
+               int dataSize = 13 // 3 shorts + 7 bytes
                        + rawNameSize
                        + field_7_length_custom_menu
                        + field_8_length_description_text
@@ -356,48 +358,45 @@ public final class NameRecord extends Record {
                        + field_10_length_status_bar_text
                        + formulaTotalSize;
                
-               LittleEndian.putShort(data, 0 + offset, sid);
-               LittleEndian.putUShort(data, 2 + offset, dataSize);
+               int recSize = 4 + dataSize;
+               LittleEndianByteArrayOutputStream out = new LittleEndianByteArrayOutputStream(data, offset, recSize);
+               
+               out.writeShort(sid);
+               out.writeShort(dataSize);
                // size defined below
-               LittleEndian.putShort(data, 4 + offset, getOptionFlag());
-               LittleEndian.putByte(data, 6 + offset, getKeyboardShortcut());
-               LittleEndian.putByte(data, 7 + offset, getNameTextLength());
-               // Note -
-               LittleEndian.putUShort(data, 8 + offset, Ptg.getEncodedSizeWithoutArrayData(field_13_name_definition));
-               LittleEndian.putUShort(data, 10 + offset, field_5_externSheetIndex_plus1);
-               LittleEndian.putUShort(data, 12 + offset, field_6_sheetNumber);
-               LittleEndian.putByte(data, 14 + offset, field_7_length_custom_menu);
-               LittleEndian.putByte(data, 15 + offset, field_8_length_description_text);
-               LittleEndian.putByte(data, 16 + offset, field_9_length_help_topic_text);
-               LittleEndian.putByte(data, 17 + offset, field_10_length_status_bar_text);
-               LittleEndian.putByte(data, 18 + offset, field_11_nameIsMultibyte ? 1 : 0);
-               int pos = 19 + offset;
+               out.writeShort(getOptionFlag());
+               out.writeByte(getKeyboardShortcut());
+               out.writeByte(getNameTextLength());
+               // Note - formula size is not immediately before encoded formula, and does not include any array constant data
+               out.writeShort(field_13_name_definition.getEncodedTokenSize());
+               out.writeShort(field_5_externSheetIndex_plus1);
+               out.writeShort(field_6_sheetNumber);
+               out.writeByte(field_7_length_custom_menu);
+               out.writeByte(field_8_length_description_text);
+               out.writeByte(field_9_length_help_topic_text);
+               out.writeByte(field_10_length_status_bar_text);
+               out.writeByte(field_11_nameIsMultibyte ? 1 : 0);
 
                if (isBuiltInName()) {
                        //can send the builtin name directly in
-                       LittleEndian.putByte(data, pos,  field_12_built_in_code);
+                       out.writeByte(field_12_built_in_code);
                } else {
                        String nameText = field_12_name_text;
                        if (field_11_nameIsMultibyte) {
-                       StringUtil.putUnicodeLE(nameText, data, pos);
-               } else {
-                       StringUtil.putCompressedUnicode(nameText, data, pos);
-               }
+                               StringUtil.putUnicodeLE(nameText, out);
+                       } else {
+                               StringUtil.putCompressedUnicode(nameText, out);
+                       }
                }
-               pos += rawNameSize;
-
-               Ptg.serializePtgs(field_13_name_definition,  data, pos);
-               pos += formulaTotalSize;
+               field_13_name_definition.serializeTokens(out);
+               field_13_name_definition.serializeArrayConstantData(out);
                
-               StringUtil.putCompressedUnicode( getCustomMenuText(), data, pos);
-               pos += field_7_length_custom_menu;
-               StringUtil.putCompressedUnicode( getDescriptionText(), data, pos);
-               pos += field_8_length_description_text;
-               StringUtil.putCompressedUnicode( getHelpTopicText(), data, pos);
-               pos += field_9_length_help_topic_text;
-               StringUtil.putCompressedUnicode( getStatusBarText(), data, pos);
+               StringUtil.putCompressedUnicode( getCustomMenuText(), out);
+               StringUtil.putCompressedUnicode( getDescriptionText(), out);
+               StringUtil.putCompressedUnicode( getHelpTopicText(), out);
+               StringUtil.putCompressedUnicode( getStatusBarText(), out);
 
-               return 4 + dataSize;
+               return recSize;
        }
        private int getNameRawSize() {
                if (isBuiltInName()) {
@@ -412,23 +411,23 @@ public final class NameRecord extends Record {
 
        public int getRecordSize(){
                return 4 // sid + size
-                       + 15 // 4 shorts + 7 bytes
+                       + 13 // 3 shorts + 7 bytes
                        + getNameRawSize()
                        + field_14_custom_menu_text.length()
                        + field_15_description_text.length()
                        + field_16_help_topic_text.length()
                        + field_17_status_bar_text.length()
-                       + Ptg.getEncodedSize(field_13_name_definition);
+                       + field_13_name_definition.getEncodedSize();
        }
 
        /** gets the extern sheet number
         * @return extern sheet index
         */
        public int getExternSheetNumber(){
-               if (field_13_name_definition.length < 1) {
+               if (field_13_name_definition.getEncodedSize() < 1) {
                        return 0;
                }
-               Ptg ptg = field_13_name_definition[0];
+               Ptg ptg = field_13_name_definition.getTokens()[0];
 
                if (ptg.getClass() == Area3DPtg.class){
                        return ((Area3DPtg) ptg).getExternSheetIndex();
@@ -444,15 +443,14 @@ public final class NameRecord extends Record {
         * @param externSheetNumber extern sheet number
         */
        public void setExternSheetNumber(short externSheetNumber){
+               Ptg[] ptgs = field_13_name_definition.getTokens();
                Ptg ptg;
 
-               if (field_13_name_definition.length < 1){
+               if (ptgs.length < 1){
                        ptg = createNewPtg();
-                       field_13_name_definition = new Ptg[] {
-                               ptg,
-                       };
+                       ptgs = new Ptg[] { ptg, };
                } else {
-                       ptg = field_13_name_definition[0];
+                       ptg = ptgs[0];
                }
 
                if (ptg.getClass() == Area3DPtg.class){
@@ -461,7 +459,7 @@ public final class NameRecord extends Record {
                } else if (ptg.getClass() == Ref3DPtg.class){
                        ((Ref3DPtg) ptg).setExternSheetIndex(externSheetNumber);
                }
-
+               field_13_name_definition = Formula.create(ptgs);
        }
 
        private static Ptg createNewPtg(){
@@ -472,7 +470,7 @@ public final class NameRecord extends Record {
         * @return area reference
         */
        public String getAreaReference(HSSFWorkbook book){
-               return HSSFFormulaParser.toFormulaString(book, field_13_name_definition);
+               return HSSFFormulaParser.toFormulaString(book, field_13_name_definition.getTokens());
        }
 
        /** sets the reference , the area only (range)
@@ -483,11 +481,11 @@ public final class NameRecord extends Record {
                RangeAddress ra = new RangeAddress(ref);
                Ptg oldPtg;
 
-               if (field_13_name_definition.length < 1){
+               if (field_13_name_definition.getEncodedTokenSize() < 1){
                        oldPtg = createNewPtg();
                } else {
                        //Trying to find extern sheet index
-                       oldPtg = field_13_name_definition[0];
+                       oldPtg = field_13_name_definition.getTokens()[0];
                }
                List temp = new ArrayList();
                int externSheetIndex = 0;
@@ -519,7 +517,7 @@ public final class NameRecord extends Record {
                }
                Ptg[] ptgs = new Ptg[temp.size()];
                temp.toArray(ptgs);
-               field_13_name_definition = ptgs;
+               field_13_name_definition = Formula.create(ptgs);
        }
 
        /**
@@ -528,17 +526,18 @@ public final class NameRecord extends Record {
         *
         * @param in the RecordInputstream to read the record from
         */
-       public NameRecord(RecordInputStream in) {
+       public NameRecord(RecordInputStream ris) {
+               LittleEndianInput in = ris;
                field_1_option_flag                 = in.readShort();
                field_2_keyboard_shortcut           = in.readByte();
                int field_3_length_name_text        = in.readByte();
                int field_4_length_name_definition  = in.readShort();
                field_5_externSheetIndex_plus1      = in.readShort();
                field_6_sheetNumber                 = in.readUShort();
-               int field_7_length_custom_menu      = in.readUByte();
-               int field_8_length_description_text = in.readUByte();
-               int field_9_length_help_topic_text  = in.readUByte();
-               int field_10_length_status_bar_text = in.readUByte();
+               int f7_customMenuLen      = in.readUByte();
+               int f8_descriptionTextLen = in.readUByte();
+               int f9_helpTopicTextLen  = in.readUByte();
+               int f10_statusBarTextLen = in.readUByte();
 
                //store the name in byte form if it's a built-in name
                field_11_nameIsMultibyte = (in.readByte() != 0);
@@ -546,19 +545,21 @@ public final class NameRecord extends Record {
                        field_12_built_in_code = in.readByte();
                } else {
                        if (field_11_nameIsMultibyte) {
-                               field_12_name_text = in.readUnicodeLEString(field_3_length_name_text);
+                               field_12_name_text = StringUtil.readUnicodeLE(in, field_3_length_name_text);
                        } else {
-                               field_12_name_text = in.readCompressedUnicode(field_3_length_name_text);
+                               field_12_name_text = StringUtil.readCompressedUnicode(in, field_3_length_name_text);
                        }
                }
 
-               field_13_name_definition = Ptg.readTokens(field_4_length_name_definition, in);
+               int nBytesAvailable = in.available() - (f7_customMenuLen 
+                               + f8_descriptionTextLen + f9_helpTopicTextLen + f10_statusBarTextLen);
+               field_13_name_definition = Formula.read(field_4_length_name_definition, in, nBytesAvailable);
 
                //Who says that this can only ever be compressed unicode???
-               field_14_custom_menu_text = in.readCompressedUnicode(field_7_length_custom_menu);
-               field_15_description_text = in.readCompressedUnicode(field_8_length_description_text);
-               field_16_help_topic_text  = in.readCompressedUnicode(field_9_length_help_topic_text);
-               field_17_status_bar_text  = in.readCompressedUnicode(field_10_length_status_bar_text);
+               field_14_custom_menu_text = StringUtil.readCompressedUnicode(in, f7_customMenuLen);
+               field_15_description_text = StringUtil.readCompressedUnicode(in, f8_descriptionTextLen);
+               field_16_help_topic_text  = StringUtil.readCompressedUnicode(in, f9_helpTopicTextLen);
+               field_17_status_bar_text  = StringUtil.readCompressedUnicode(in, f10_statusBarTextLen);
        }
 
        /**
@@ -633,9 +634,10 @@ public final class NameRecord extends Record {
                sb.append("    .Status bar text length = ").append(field_17_status_bar_text.length()).append("\n");
                sb.append("    .NameIsMultibyte        = ").append(field_11_nameIsMultibyte).append("\n");
                sb.append("    .Name (Unicode text)    = ").append( getNameText() ).append("\n");
-               sb.append("    .Formula (nTokens=").append(field_13_name_definition.length).append("):") .append("\n");
-               for (int i = 0; i < field_13_name_definition.length; i++) {
-                       Ptg ptg = field_13_name_definition[i];
+               Ptg[] ptgs = field_13_name_definition.getTokens();
+               sb.append("    .Formula (nTokens=").append(ptgs.length).append("):") .append("\n");
+               for (int i = 0; i < ptgs.length; i++) {
+                       Ptg ptg = ptgs[i];
                        sb.append("       " + ptg.toString()).append(ptg.getRVAType()).append("\n");
                }
 
index 9ede66d95755657139963c3ce7c59fd868c0709f..52b14d3db9bd3507cfa155d768ee4632d83539c0 100755 (executable)
@@ -22,7 +22,10 @@ import org.apache.poi.hssf.record.formula.AreaPtg;
 import org.apache.poi.hssf.record.formula.Ptg;
 import org.apache.poi.hssf.record.formula.RefNPtg;
 import org.apache.poi.hssf.record.formula.RefPtg;
+import org.apache.poi.hssf.util.CellRangeAddress8Bit;
+import org.apache.poi.ss.formula.Formula;
 import org.apache.poi.util.HexDump;
+import org.apache.poi.util.LittleEndianOutput;
 
 /**
  * Title:        SHAREDFMLA (0x04BC) SharedFormulaRecord
@@ -39,10 +42,15 @@ public final class SharedFormulaRecord extends SharedValueRecordBase {
     public final static short   sid = 0x04BC;
 
     private int field_5_reserved;
-    private Ptg[] field_7_parsed_expr;
+    private Formula field_7_parsed_expr;
 
+    // for testing only
     public SharedFormulaRecord() {
-        field_7_parsed_expr = Ptg.EMPTY_PTG_ARRAY;
+        this(new CellRangeAddress8Bit(0,0,0,0));
+    }
+    private SharedFormulaRecord(CellRangeAddress8Bit range) {
+        super(range);
+        field_7_parsed_expr = Formula.create(Ptg.EMPTY_PTG_ARRAY);
     }
 
     /**
@@ -52,17 +60,17 @@ public final class SharedFormulaRecord extends SharedValueRecordBase {
         super(in);
         field_5_reserved        = in.readShort();
         int field_6_expression_len = in.readShort();
-        field_7_parsed_expr = Ptg.readTokens(field_6_expression_len, in);
+        int nAvailableBytes = in.available();
+        field_7_parsed_expr = Formula.read(field_6_expression_len, in, nAvailableBytes);
     }
-    protected void serializeExtraData(int offset, byte[] data) {
-        //Because this record is converted to individual Formula records, this method is not required.
-        throw new UnsupportedOperationException("Cannot serialize a SharedFormulaRecord");
+
+    protected void serializeExtraData(LittleEndianOutput out) {
+        out.writeShort(field_5_reserved);
+        field_7_parsed_expr.serialize(out);
     }
     
     protected int getExtraDataSize() {
-        //Because this record is converted to individual Formula records, this method is not required.
-        throw new UnsupportedOperationException("Cannot get the size for a SharedFormulaRecord");
-
+        return 2 + field_7_parsed_expr.getEncodedSize();
     }
 
     /**
@@ -77,9 +85,10 @@ public final class SharedFormulaRecord extends SharedValueRecordBase {
         buffer.append("    .range      = ").append(getRange().toString()).append("\n");
         buffer.append("    .reserved    = ").append(HexDump.shortToHex(field_5_reserved)).append("\n");
 
-        for (int k = 0; k < field_7_parsed_expr.length; k++ ) {
+        Ptg[] ptgs = field_7_parsed_expr.getTokens();
+        for (int k = 0; k < ptgs.length; k++ ) {
            buffer.append("Formula[").append(k).append("]");
-           Ptg ptg = field_7_parsed_expr[k];
+           Ptg ptg = ptgs[k];
            buffer.append(ptg.toString()).append(ptg.getRVAType()).append("\n");
         }
 
@@ -92,20 +101,13 @@ public final class SharedFormulaRecord extends SharedValueRecordBase {
     }
 
     /**
-     * Creates a non shared formula from the shared formula
-     * counter part
+     * Creates a non shared formula from the shared formula counterpart<br/>
+     * 
+     * Perhaps this functionality could be implemented in terms of the raw 
+     * byte array inside {@link Formula}.
      */
-    protected static Ptg[] convertSharedFormulas(Ptg[] ptgs, int formulaRow, int formulaColumn) {
-        if(false) {
-            /*
-             * TODO - (May-2008) Stop converting relative ref Ptgs in shared formula records.
-             * If/when POI writes out the workbook, this conversion makes an unnecessary diff in the BIFF records.
-             * Disabling this code breaks one existing junit.
-             * Some fix-up will be required to make Ptg.toFormulaString(HSSFWorkbook) work properly.
-             * That method will need 2 extra params: rowIx and colIx.
-             */
-            return ptgs;
-        }
+    static Ptg[] convertSharedFormulas(Ptg[] ptgs, int formulaRow, int formulaColumn) {
+
         Ptg[] newPtgStack = new Ptg[ptgs.length];
 
         for (int k = 0; k < ptgs.length; k++) {
@@ -145,10 +147,9 @@ public final class SharedFormulaRecord extends SharedValueRecordBase {
     }
 
     /**
-     * Creates a non shared formula from the shared formula
-     * counter part
+     * @return the equivalent {@link Ptg} array that the formula would have, were it not shared.
      */
-    public void convertSharedFormulaRecord(FormulaRecord formula) {
+    public Ptg[] getFormulaTokens(FormulaRecord formula) {
         int formulaRow = formula.getRow();
         int formulaColumn = formula.getColumn();
         //Sanity checks
@@ -156,10 +157,7 @@ public final class SharedFormulaRecord extends SharedValueRecordBase {
             throw new RuntimeException("Shared Formula Conversion: Coding Error");
         }
 
-        Ptg[] ptgs = convertSharedFormulas(field_7_parsed_expr, formulaRow, formulaColumn);
-        formula.setParsedExpression(ptgs);
-        //Now its not shared!
-        formula.setSharedFormula(false);
+        return convertSharedFormulas(field_7_parsed_expr.getTokens(), formulaRow, formulaColumn);
     }
 
     private static int fixupRelativeColumn(int currentcolumn, int column, boolean relative) {
@@ -179,7 +177,9 @@ public final class SharedFormulaRecord extends SharedValueRecordBase {
     }
 
     public Object clone() {
-        //Because this record is converted to individual Formula records, this method is not required.
-        throw new UnsupportedOperationException("Cannot clone a SharedFormulaRecord");
+        SharedFormulaRecord result = new SharedFormulaRecord(getRange());
+        result.field_5_reserved = field_5_reserved;
+        result.field_7_parsed_expr = field_7_parsed_expr.copy();
+        return result;
     }
 }
index fc51de8465de19aa0dadaf954568ca053c51a63e..3672b881a260f11b83d94a8c741d54aab622831a 100644 (file)
@@ -18,7 +18,9 @@
 package org.apache.poi.hssf.record;
 
 import org.apache.poi.hssf.util.CellRangeAddress8Bit;
-import org.apache.poi.util.LittleEndian;
+import org.apache.poi.util.LittleEndianByteArrayOutputStream;
+import org.apache.poi.util.LittleEndianInput;
+import org.apache.poi.util.LittleEndianOutput;
 
 /**
  * Common base class for {@link SharedFormulaRecord}, {@link ArrayRecord} and
@@ -41,7 +43,7 @@ public abstract class SharedValueRecordBase extends Record {
        /**
         * reads only the range (1 {@link CellRangeAddress8Bit}) from the stream
         */
-       public SharedValueRecordBase(RecordInputStream in) {
+       public SharedValueRecordBase(LittleEndianInput in) {
                _range = new CellRangeAddress8Bit(in);
        }
 
@@ -71,19 +73,19 @@ public abstract class SharedValueRecordBase extends Record {
 
        protected abstract int getExtraDataSize();
 
-       protected abstract void serializeExtraData(int offset, byte[] data);
+       protected abstract void serializeExtraData(LittleEndianOutput out);
 
        public final int serialize(int offset, byte[] data) {
                int dataSize = CellRangeAddress8Bit.ENCODED_SIZE + getExtraDataSize();
-
-               LittleEndian.putShort(data, 0 + offset, getSid());
-               LittleEndian.putUShort(data, 2 + offset, dataSize);
-
-               int pos = offset + 4;
-               _range.serialize(pos, data);
-               pos += CellRangeAddress8Bit.ENCODED_SIZE;
-               serializeExtraData(pos, data);
-               return dataSize + 4;
+               
+               int totalRecSize = dataSize + 4;
+               LittleEndianOutput out = new LittleEndianByteArrayOutputStream(data, offset, totalRecSize);
+               out.writeShort(getSid());
+               out.writeShort(dataSize);
+
+               _range.serialize(out);
+               serializeExtraData(out);
+               return totalRecSize;
        }
 
        /**
index 0d4934620e4cacd492d7b47a9a360dd3542d0ade..0a3921cbbe752ac69925b8b403f006c32005cfa1 100644 (file)
@@ -23,7 +23,7 @@ import org.apache.poi.hssf.util.CellReference;
 import org.apache.poi.util.BitField;
 import org.apache.poi.util.BitFieldFactory;
 import org.apache.poi.util.HexDump;
-import org.apache.poi.util.LittleEndian;
+import org.apache.poi.util.LittleEndianOutput;
 /**
  * DATATABLE (0x0236)<p/>
  *
@@ -146,13 +146,13 @@ public final class TableRecord extends SharedValueRecordBase {
                2 // 2 byte fields
                + 8; // 4 short fields
        }
-       protected void serializeExtraData(int offset, byte[] data) {
-               LittleEndian.putByte(data, 0 + offset, field_5_flags);
-               LittleEndian.putByte(data, 1 + offset, field_6_res);
-               LittleEndian.putUShort(data, 2 + offset, field_7_rowInputRow);
-               LittleEndian.putUShort(data, 4 + offset, field_8_colInputRow);
-               LittleEndian.putUShort(data, 6 + offset, field_9_rowInputCol);
-               LittleEndian.putUShort(data, 8 + offset, field_10_colInputCol);
+       protected void serializeExtraData(LittleEndianOutput out) {
+               out.writeByte(field_5_flags);
+               out.writeByte(field_6_res);
+               out.writeShort(field_7_rowInputRow);
+               out.writeShort(field_8_colInputRow);
+               out.writeShort(field_9_rowInputCol);
+               out.writeShort(field_10_colInputCol);
        }
 
        public String toString() {
index 2e86b138210d0901f77a9a7b701ed8253888e2ec..282afc427cb9b335841570a7cf2fa31884367f17 100644 (file)
@@ -21,7 +21,10 @@ import org.apache.poi.hssf.record.CellValueRecordInterface;
 import org.apache.poi.hssf.record.FormulaRecord;
 import org.apache.poi.hssf.record.Record;
 import org.apache.poi.hssf.record.RecordFormatException;
+import org.apache.poi.hssf.record.SharedFormulaRecord;
 import org.apache.poi.hssf.record.StringRecord;
+import org.apache.poi.hssf.record.formula.ExpPtg;
+import org.apache.poi.hssf.record.formula.Ptg;
 
 /**
  * The formula record aggregate is used to join together the formula record and it's
@@ -31,113 +34,177 @@ import org.apache.poi.hssf.record.StringRecord;
  */
 public final class FormulaRecordAggregate extends RecordAggregate implements CellValueRecordInterface {
 
-    private final FormulaRecord _formulaRecord;
-    private SharedValueManager _sharedValueManager;
-    /** caches the calculated result of the formula */
-    private StringRecord _stringRecord;
-
-    /**
-     * @param stringRec may be <code>null</code> if this formula does not have a cached text
-     * value.
-     * @param svm the {@link SharedValueManager} for the current sheet
-     */
-    public FormulaRecordAggregate(FormulaRecord formulaRec, StringRecord stringRec, SharedValueManager svm) {
-        if (svm == null) {
-            throw new IllegalArgumentException("sfm must not be null");
-        }
-        boolean hasStringRec = stringRec != null;
-        boolean hasCachedStringFlag = formulaRec.hasCachedResultString();
-        if (hasStringRec != hasCachedStringFlag) {
-            throw new RecordFormatException("String record was "
-                    + (hasStringRec ? "": "not ") + " supplied but formula record flag is "
-                    + (hasCachedStringFlag ? "" : "not ") + " set");
-        }
-
-        if (formulaRec.isSharedFormula()) {
-            svm.convertSharedFormulaRecord(formulaRec);
-        }
-        _formulaRecord = formulaRec;
-        _sharedValueManager = svm;
-        _stringRecord = stringRec;
-    }
-
-    public FormulaRecord getFormulaRecord() {
-        return _formulaRecord;
-    }
-
-    /**
-     * debug only
-     * TODO - encapsulate
-     */
-    public StringRecord getStringRecord() {
-        return _stringRecord;
-    }
-
-    public short getXFIndex() {
-        return _formulaRecord.getXFIndex();
-    }
-
-    public void setXFIndex(short xf) {
-        _formulaRecord.setXFIndex(xf);
-    }
-
-    public void setColumn(short col) {
-        _formulaRecord.setColumn(col);
-    }
-
-    public void setRow(int row) {
-        _formulaRecord.setRow(row);
-    }
-
-    public short getColumn() {
-        return _formulaRecord.getColumn();
-    }
-
-    public int getRow() {
-        return _formulaRecord.getRow();
-    }
-
-    public String toString() {
-        return _formulaRecord.toString();
-    }
-
-    public void visitContainedRecords(RecordVisitor rv) {
-         rv.visitRecord(_formulaRecord);
-         Record sharedFormulaRecord = _sharedValueManager.getRecordForFirstCell(_formulaRecord);
-         if (sharedFormulaRecord != null) {
-             rv.visitRecord(sharedFormulaRecord);
-         }
-         if (_stringRecord != null) {
-             rv.visitRecord(_stringRecord);
-         }
-    }
-
-    public String getStringValue() {
-        if(_stringRecord==null) {
-            return null;
-        }
-        return _stringRecord.getString();
-    }
-
-    public void setCachedStringResult(String value) {
-
-        // Save the string into a String Record, creating one if required
-        if(_stringRecord == null) {
-            _stringRecord = new StringRecord();
-        }
-        _stringRecord.setString(value);
-        if (value.length() < 1) {
-            _formulaRecord.setCachedResultTypeEmptyString();
-        } else {
-            _formulaRecord.setCachedResultTypeString();
-        }
-    }
-    public void setCachedBooleanResult(boolean value) {
-        _stringRecord = null;
-        _formulaRecord.setCachedResultBoolean(value);
-    }
-    public void setCachedErrorResult(int errorCode) {
-        _stringRecord = null;
-        _formulaRecord.setCachedResultErrorCode(errorCode);
-    }
+       private final FormulaRecord _formulaRecord;
+       private SharedValueManager _sharedValueManager;
+       /** caches the calculated result of the formula */
+       private StringRecord _stringRecord;
+       private SharedFormulaRecord _sharedFormulaRecord;
+
+       /**
+        * @param stringRec may be <code>null</code> if this formula does not have a cached text
+        * value.
+        * @param svm the {@link SharedValueManager} for the current sheet
+        */
+       public FormulaRecordAggregate(FormulaRecord formulaRec, StringRecord stringRec, SharedValueManager svm) {
+               if (svm == null) {
+                       throw new IllegalArgumentException("sfm must not be null");
+               }
+               boolean hasStringRec = stringRec != null;
+               boolean hasCachedStringFlag = formulaRec.hasCachedResultString();
+               if (hasStringRec != hasCachedStringFlag) {
+                       throw new RecordFormatException("String record was "
+                                       + (hasStringRec ? "": "not ") + " supplied but formula record flag is "
+                                       + (hasCachedStringFlag ? "" : "not ") + " set");
+               }
+
+               _formulaRecord = formulaRec;
+               _sharedValueManager = svm;
+               _stringRecord = stringRec;
+               if (formulaRec.isSharedFormula()) {
+                       _sharedFormulaRecord = svm.linkSharedFormulaRecord(this);
+                       if (_sharedFormulaRecord == null) {
+                               handleMissingSharedFormulaRecord(formulaRec);
+                       }
+               }
+       }
+       /**
+        * Sometimes the shared formula flag "seems" to be erroneously set (because the corresponding
+        * {@link SharedFormulaRecord} does not exist). Normally this would leave no way of determining
+        * the {@link Ptg} tokens for the formula.  However as it turns out in these 
+        * cases, Excel encodes the unshared {@link Ptg} tokens in the right place (inside the {@link
+        * FormulaRecord}).  So the the only thing that needs to be done is to ignore the erroneous
+        * shared formula flag.<br/>
+        * 
+        * This method may also be used for setting breakpoints to help diagnose issues regarding the
+        * abnormally-set 'shared formula' flags. 
+        * (see TestValueRecordsAggregate.testSpuriousSharedFormulaFlag()).<p/>
+        */
+       private static void handleMissingSharedFormulaRecord(FormulaRecord formula) {
+               // make sure 'unshared' formula is actually available
+               Ptg firstToken = formula.getParsedExpression()[0]; 
+               if (firstToken instanceof ExpPtg) {
+                       throw new RecordFormatException(
+                                       "SharedFormulaRecord not found for FormulaRecord with (isSharedFormula=true)");
+               }
+               // could log an info message here since this is a fairly unusual occurrence.
+               formula.setSharedFormula(false); // no point leaving the flag erroneously set
+       }
+
+       public FormulaRecord getFormulaRecord() {
+               return _formulaRecord;
+       }
+
+       /**
+        * debug only
+        * TODO - encapsulate
+        */
+       public StringRecord getStringRecord() {
+               return _stringRecord;
+       }
+
+       public short getXFIndex() {
+               return _formulaRecord.getXFIndex();
+       }
+
+       public void setXFIndex(short xf) {
+               _formulaRecord.setXFIndex(xf);
+       }
+
+       public void setColumn(short col) {
+               _formulaRecord.setColumn(col);
+       }
+
+       public void setRow(int row) {
+               _formulaRecord.setRow(row);
+       }
+
+       public short getColumn() {
+               return _formulaRecord.getColumn();
+       }
+
+       public int getRow() {
+               return _formulaRecord.getRow();
+       }
+
+       public String toString() {
+               return _formulaRecord.toString();
+       }
+
+       public void visitContainedRecords(RecordVisitor rv) {
+                rv.visitRecord(_formulaRecord);
+                // TODO - only bother with this if array or table formula
+                Record sharedFormulaRecord = _sharedValueManager.getRecordForFirstCell(_formulaRecord);
+                if (sharedFormulaRecord != null) {
+                        rv.visitRecord(sharedFormulaRecord);
+                }
+                if (_stringRecord != null) {
+                        rv.visitRecord(_stringRecord);
+                }
+       }
+
+       public String getStringValue() {
+               if(_stringRecord==null) {
+                       return null;
+               }
+               return _stringRecord.getString();
+       }
+
+       public void setCachedStringResult(String value) {
+
+               // Save the string into a String Record, creating one if required
+               if(_stringRecord == null) {
+                       _stringRecord = new StringRecord();
+               }
+               _stringRecord.setString(value);
+               if (value.length() < 1) {
+                       _formulaRecord.setCachedResultTypeEmptyString();
+               } else {
+                       _formulaRecord.setCachedResultTypeString();
+               }
+       }
+       public void setCachedBooleanResult(boolean value) {
+               _stringRecord = null;
+               _formulaRecord.setCachedResultBoolean(value);
+       }
+       public void setCachedErrorResult(int errorCode) {
+               _stringRecord = null;
+               _formulaRecord.setCachedResultErrorCode(errorCode);
+       }
+
+       public Ptg[] getFormulaTokens() {
+               if (_sharedFormulaRecord == null) {
+                       return _formulaRecord.getParsedExpression();
+               }
+               return _sharedFormulaRecord.getFormulaTokens(_formulaRecord);
+       }
+
+       /**
+        * Also checks for a related shared formula and unlinks it if found
+        */
+       public void setParsedExpression(Ptg[] ptgs) {
+               notifyFormulaChanging();
+               _formulaRecord.setParsedExpression(ptgs);
+       }
+
+       public void unlinkSharedFormula() {
+               SharedFormulaRecord sfr = _sharedFormulaRecord;
+               if (sfr == null) {
+                       throw new IllegalStateException("Formula not linked to shared formula");
+               }
+               Ptg[] ptgs = sfr.getFormulaTokens(_formulaRecord);
+               _formulaRecord.setParsedExpression(ptgs);
+               //Now its not shared!
+               _formulaRecord.setSharedFormula(false);
+               _sharedFormulaRecord = null;
+       }
+       /**
+        * Should be called by any code which is either deleting this formula cell, or changing
+        * its type.  This method gives the aggregate a chance to unlink any shared formula
+        * that may be involved with this cell formula.
+        */
+       public void notifyFormulaChanging() {
+               if (_sharedFormulaRecord != null) {
+                       _sharedValueManager.unlink(_sharedFormulaRecord);
+               }
+       }
 }
index 1e5eac38c2860eb2024c43ccdfb1a8db6d552076..2a2497f363cbdefba9015143dbfe051c3fb576aa 100644 (file)
@@ -499,6 +499,9 @@ public final class RowRecordsAggregate extends RecordAggregate {
         _valuesAgg.insertCell(cvRec);
     }
     public void removeCell(CellValueRecordInterface cvRec) {
+        if (cvRec instanceof FormulaRecordAggregate) {
+            ((FormulaRecordAggregate)cvRec).notifyFormulaChanging();
+        }
         _valuesAgg.removeCell(cvRec);
     }
     public FormulaRecordAggregate createFormula(int row, int col) {
index d5cf1b6ac8e928d600e6be515609a42c4c80b342..98eebe0880d09d7ef47f696b22d554fc2810051d 100644 (file)
@@ -17,6 +17,9 @@
 
 package org.apache.poi.hssf.record.aggregates;
 
+import java.util.HashMap;
+import java.util.Map;
+
 import org.apache.poi.hssf.record.ArrayRecord;
 import org.apache.poi.hssf.record.FormulaRecord;
 import org.apache.poi.hssf.record.SharedFormulaRecord;
@@ -35,18 +38,72 @@ import org.apache.poi.hssf.record.TableRecord;
  * @author Josh Micich
  */
 public final class SharedValueManager {
+       
+       // This class should probably be generalised to handle array and table groups too
+       private static final class SharedValueGroup {
+               private final SharedValueRecordBase _svr;
+               private final FormulaRecordAggregate[] _frAggs;
+               private int _numberOfFormulas;
+
+               public SharedValueGroup(SharedValueRecordBase svr) {
+                       _svr = svr;
+                       int width = svr.getLastColumn() - svr.getFirstColumn() + 1;
+                       int height = svr.getLastRow() - svr.getFirstRow() + 1;
+                       _frAggs = new FormulaRecordAggregate[width * height];
+                       _numberOfFormulas = 0;
+               }
+
+               public void add(FormulaRecordAggregate agg) {
+                       _frAggs[_numberOfFormulas++] = agg;
+               }
+
+               public void unlinkSharedFormulas() {
+                       for (int i = 0; i < _numberOfFormulas; i++) {
+                               _frAggs[i].unlinkSharedFormula();
+                       }
+               }
+
+               public boolean isInRange(int rowIx, int columnIx) {
+                       return _svr.isInRange(rowIx, columnIx);
+               }
+
+               public SharedValueRecordBase getSVR() {
+                       return _svr;
+               }
+
+               /**
+                * Note - Sometimes the first formula in a group is not present (because the range
+                * is sparsely populated), so this method can return <code>true</code> for a cell
+                * that is not the top-left corner of the range.
+                * @return <code>true</code> if this is the first formula cell in the group
+                */
+               public boolean isFirstCell(int row, int column) {
+                       // hack for the moment, just check against the first formula that 
+                       // came in through the add() method.
+                       FormulaRecordAggregate fra = _frAggs[0];
+                       return fra.getRow() == row && fra.getColumn() == column;
+               }
+               
+       }
 
        public static final SharedValueManager EMPTY = new SharedValueManager(
                        new SharedFormulaRecord[0], new ArrayRecord[0], new TableRecord[0]);
-       private final SharedFormulaRecord[] _sfrs;
        private final ArrayRecord[] _arrayRecords;
        private final TableRecord[] _tableRecords;
+       private final Map _groupsBySharedFormulaRecord;
+       /** cached for optimization purposes */
+       private SharedValueGroup[] _groups;
 
        private SharedValueManager(SharedFormulaRecord[] sharedFormulaRecords,
                        ArrayRecord[] arrayRecords, TableRecord[] tableRecords) {
-               _sfrs = sharedFormulaRecords;
                _arrayRecords = arrayRecords;
                _tableRecords = tableRecords;
+               Map m = new HashMap(sharedFormulaRecords.length * 3 / 2);
+               for (int i = 0; i < sharedFormulaRecords.length; i++) {
+                       SharedFormulaRecord sfr = sharedFormulaRecords[i];
+                       m.put(sfr, new SharedValueGroup(sfr));
+               }
+               _groupsBySharedFormulaRecord = m;
        }
 
        /**
@@ -64,42 +121,42 @@ public final class SharedValueManager {
                return new SharedValueManager(sharedFormulaRecords, arrayRecords, tableRecords);
        }
 
-       public void convertSharedFormulaRecord(FormulaRecord formula) {
+
+       /**
+        * @return <code>null</code> if the specified formula does not have any corresponding
+        * {@link SharedFormulaRecord}
+        */
+       public SharedFormulaRecord linkSharedFormulaRecord(FormulaRecordAggregate agg) {
+               FormulaRecord formula = agg.getFormulaRecord();
                int row = formula.getRow();
                int column = formula.getColumn();
                // Traverse the list of shared formulas in
                // reverse order, and try to find the correct one
                // for us
-               for (int i = 0; i < _sfrs.length; i++) {
-                       SharedFormulaRecord shrd = _sfrs[i];
-                       if (shrd.isInRange(row, column)) {
-                               shrd.convertSharedFormulaRecord(formula);
-                               return;
+               
+               SharedValueGroup[] groups = getGroups();
+               for (int i = 0; i < groups.length; i++) {
+                       SharedValueGroup svr = groups[i];
+                       if (svr.isInRange(row, column)) {
+                               svr.add(agg);
+                               return (SharedFormulaRecord) svr.getSVR();
                        }
                }
-               // not found
-               handleMissingSharedFormulaRecord(formula);
+               return null;
        }
 
-       /**
-        * Sometimes the shared formula flag "seems" to be erroneously set, in which case there is no 
-        * call to <tt>SharedFormulaRecord.convertSharedFormulaRecord</tt> and hence the 
-        * <tt>parsedExpression</tt> field of this <tt>FormulaRecord</tt> will not get updated.<br/>
-        * As it turns out, this is not a problem, because in these circumstances, the existing value
-        * for <tt>parsedExpression</tt> is perfectly OK.<p/>
-        * 
-        * This method may also be used for setting breakpoints to help diagnose issues regarding the
-        * abnormally-set 'shared formula' flags. 
-        * (see TestValueRecordsAggregate.testSpuriousSharedFormulaFlag()).<p/>
-        * 
-        * The method currently does nothing but do not delete it without finding a nice home for this 
-        * comment.
-        */
-       private static void handleMissingSharedFormulaRecord(FormulaRecord formula) {
-               // could log an info message here since this is a fairly unusual occurrence.
-               formula.setSharedFormula(false); // no point leaving the flag erroneously set
+       private SharedValueGroup[] getGroups() {
+               if (_groups == null) {
+                       SharedValueGroup[] groups = new SharedValueGroup[_groupsBySharedFormulaRecord.size()];
+                       _groupsBySharedFormulaRecord.values().toArray(groups);
+                       _groups = groups;
+                       
+               }
+               return _groups;
        }
 
+
+
        /**
         * Note - does not return SharedFormulaRecords currently, because the corresponding formula
         * records have been converted to 'unshared'. POI does not attempt to re-share formulas. On
@@ -125,6 +182,26 @@ public final class SharedValueManager {
                                return ar;
                        }
                }
+               SharedValueGroup[] groups = getGroups();
+               for (int i = 0; i < groups.length; i++) {
+                       SharedValueGroup svg = groups[i];
+                       if (svg.isFirstCell(row, column)) {
+                               return svg.getSVR();
+                       }
+               }
                return null;
        }
+
+       /**
+        * Converts all {@link FormulaRecord}s handled by <tt>sharedFormulaRecord</tt> 
+        * to plain unshared formulas
+        */
+       public void unlink(SharedFormulaRecord sharedFormulaRecord) {
+               SharedValueGroup svg = (SharedValueGroup) _groupsBySharedFormulaRecord.remove(sharedFormulaRecord);
+               _groups = null; // be sure to reset cached value
+               if (svg == null) {
+                       throw new IllegalStateException("Failed to find formulas for shared formula");
+               }
+               svg.unlinkSharedFormulas();
+       }
 }
index a9b1de2531f392f1a85c7d9a6c2309f858b40ab8..27000811ce8de6b25e8763c078fd4906c390c460 100644 (file)
@@ -145,9 +145,6 @@ public final class ValueRecordsAggregate {
     public void construct(CellValueRecordInterface rec, RecordStream rs, SharedValueManager sfh) {
         if (rec instanceof FormulaRecord) {
             FormulaRecord formulaRec = (FormulaRecord)rec;
-            if (formulaRec.isSharedFormula()) {
-                sfh.convertSharedFormulaRecord(formulaRec);
-            }
             // read optional cached text value
             StringRecord cachedText;
             Class nextClass = rs.peekNextClass();
index 4343760ddd994e890b96295c750eb488501164f9..4698c5c310fe98f007d5ecf80440b01796753ef0 100644 (file)
@@ -1,4 +1,3 @@
-
 /* ====================================================================
    Licensed to the Apache Software Foundation (ASF) under one or more
    contributor license agreements.  See the NOTICE file distributed with
    See the License for the specific language governing permissions and
    limitations under the License.
 ==================================================================== */
-        
 
-/*
- * FontFormatting.java
- *
- * Created on January 22, 2008, 10:05 PM
- */
 package org.apache.poi.hssf.record.cf;
 
-import org.apache.poi.hssf.record.RecordInputStream;
 import org.apache.poi.util.BitField;
 import org.apache.poi.util.BitFieldFactory;
 import org.apache.poi.util.LittleEndian;
+import org.apache.poi.util.LittleEndianInput;
+import org.apache.poi.util.LittleEndianOutput;
 
 /**
  * Border Formatting Block of the Conditional Formatting Rule Record.
- * 
+ *
  * @author Dmitriy Kumshayev
  */
+public final class BorderFormatting {
 
-public class BorderFormatting 
-{
-       
-    /**
-     * No border
-     */
-
+    /** No border */
     public final static short    BORDER_NONE                = 0x0;
-
-    /**
-     * Thin border
-     */
-
+    /** Thin border */
     public final static short    BORDER_THIN                = 0x1;
-
-    /**
-     * Medium border
-     */
-
+    /** Medium border */
     public final static short    BORDER_MEDIUM              = 0x2;
-
-    /**
-     * dash border
-     */
-
+    /** dash border */
     public final static short    BORDER_DASHED              = 0x3;
-
-    /**
-     * dot border
-     */
-
-    public final static short    BORDER_HAIR              = 0x4;
-
-    /**
-     * Thick border
-     */
-
+    /** dot border */
+    public final static short    BORDER_HAIR                = 0x4;
+    /** Thick border */
     public final static short    BORDER_THICK               = 0x5;
-
-    /**
-     * double-line border
-     */
-
+    /** double-line border */
     public final static short    BORDER_DOUBLE              = 0x6;
-
-    /**
-     * hair-line border
-     */
-
-    public final static short    BORDER_DOTTED                = 0x7;
-
-    /**
-     * Medium dashed border
-     */
-
+    /** hair-line border */
+    public final static short    BORDER_DOTTED              = 0x7;
+    /** Medium dashed border */
     public final static short    BORDER_MEDIUM_DASHED       = 0x8;
-
-    /**
-     * dash-dot border
-     */
-
+    /** dash-dot border */
     public final static short    BORDER_DASH_DOT            = 0x9;
-
-    /**
-     * medium dash-dot border
-     */
-
+    /** medium dash-dot border */
     public final static short    BORDER_MEDIUM_DASH_DOT     = 0xA;
-
-    /**
-     * dash-dot-dot border
-     */
-
+    /** dash-dot-dot border */
     public final static short    BORDER_DASH_DOT_DOT        = 0xB;
-
-    /**
-     * medium dash-dot-dot border
-     */
-
+    /** medium dash-dot-dot border */
     public final static short    BORDER_MEDIUM_DASH_DOT_DOT = 0xC;
-
-    /**
-     * slanted dash-dot border
-     */
-
+    /** slanted dash-dot border */
     public final static short    BORDER_SLANTED_DASH_DOT    = 0xD;
-       
-    public BorderFormatting()
-    {
-        field_13_border_styles1        = (short)0;
-        field_14_border_styles2        = (short)0;
-    }
-    
-    /** Creates new FontFormatting */
-    public BorderFormatting(RecordInputStream in)
-       {
-        field_13_border_styles1        = in.readInt();
-        field_14_border_styles2        = in.readInt();
-       }
-    
+
     // BORDER FORMATTING BLOCK
     // For Border Line Style codes see HSSFCellStyle.BORDER_XXXXXX
-    private int                         field_13_border_styles1;
+    private int              field_13_border_styles1;
     private static final BitField  bordLeftLineStyle  = BitFieldFactory.getInstance(0x0000000F);
     private static final BitField  bordRightLineStyle = BitFieldFactory.getInstance(0x000000F0);
     private static final BitField  bordTopLineStyle   = BitFieldFactory.getInstance(0x00000F00);
@@ -147,12 +71,25 @@ public class BorderFormatting
     private static final BitField  bordTlBrLineOnOff  = BitFieldFactory.getInstance(0x40000000);
     private static final BitField  bordBlTrtLineOnOff = BitFieldFactory.getInstance(0x80000000);
 
-    private int                         field_14_border_styles2;
+    private int              field_14_border_styles2;
     private static final BitField  bordTopLineColor   = BitFieldFactory.getInstance(0x0000007F);
     private static final BitField  bordBottomLineColor= BitFieldFactory.getInstance(0x00003f80);
     private static final BitField  bordDiagLineColor  = BitFieldFactory.getInstance(0x001FC000);
     private static final BitField  bordDiagLineStyle  = BitFieldFactory.getInstance(0x01E00000);
 
+
+    public BorderFormatting() {
+        field_13_border_styles1    = 0;
+        field_14_border_styles2    = 0;
+    }
+
+    /** Creates new FontFormatting */
+    public BorderFormatting(LittleEndianInput in) {
+        field_13_border_styles1    = in.readInt();
+        field_14_border_styles2    = in.readInt();
+    }
+
+
     /**
      * set the type of border to use for the left border of the cell
      * @param border type
@@ -171,10 +108,8 @@ public class BorderFormatting
      * @see #BORDER_MEDIUM_DASH_DOT_DOT
      * @see #BORDER_SLANTED_DASH_DOT
      */
-
-    public void setBorderLeft(short border)
-    {
-       field_13_border_styles1 = bordLeftLineStyle.setValue(field_13_border_styles1, border);
+    public void setBorderLeft(int border) {
+        field_13_border_styles1 = bordLeftLineStyle.setValue(field_13_border_styles1, border);
     }
 
     /**
@@ -195,10 +130,8 @@ public class BorderFormatting
      * @see #BORDER_MEDIUM_DASH_DOT_DOT
      * @see #BORDER_SLANTED_DASH_DOT
      */
-
-    public short getBorderLeft()
-    {
-       return (short)bordLeftLineStyle.getValue(field_13_border_styles1);
+    public int getBorderLeft() {
+        return bordLeftLineStyle.getValue(field_13_border_styles1);
     }
 
     /**
@@ -219,10 +152,8 @@ public class BorderFormatting
      * @see #BORDER_MEDIUM_DASH_DOT_DOT
      * @see #BORDER_SLANTED_DASH_DOT
      */
-
-    public void setBorderRight(short border)
-    {
-       field_13_border_styles1 = bordRightLineStyle.setValue(field_13_border_styles1, border);
+    public void setBorderRight(int border) {
+        field_13_border_styles1 = bordRightLineStyle.setValue(field_13_border_styles1, border);
     }
 
     /**
@@ -243,10 +174,8 @@ public class BorderFormatting
      * @see #BORDER_MEDIUM_DASH_DOT_DOT
      * @see #BORDER_SLANTED_DASH_DOT
      */
-
-    public short getBorderRight()
-    {
-       return (short)bordRightLineStyle.getValue(field_13_border_styles1);
+    public int getBorderRight() {
+        return bordRightLineStyle.getValue(field_13_border_styles1);
     }
 
     /**
@@ -267,10 +196,8 @@ public class BorderFormatting
      * @see #BORDER_MEDIUM_DASH_DOT_DOT
      * @see #BORDER_SLANTED_DASH_DOT
      */
-
-    public void setBorderTop(short border)
-    {
-       field_13_border_styles1 = bordTopLineStyle.setValue(field_13_border_styles1, border);
+    public void setBorderTop(int border) {
+        field_13_border_styles1 = bordTopLineStyle.setValue(field_13_border_styles1, border);
     }
 
     /**
@@ -291,10 +218,8 @@ public class BorderFormatting
      * @see #BORDER_MEDIUM_DASH_DOT_DOT
      * @see #BORDER_SLANTED_DASH_DOT
      */
-
-    public short getBorderTop()
-    {
-       return (short)bordTopLineStyle.getValue(field_13_border_styles1);
+    public int getBorderTop() {
+        return bordTopLineStyle.getValue(field_13_border_styles1);
     }
 
     /**
@@ -315,10 +240,8 @@ public class BorderFormatting
      * @see #BORDER_MEDIUM_DASH_DOT_DOT
      * @see #BORDER_SLANTED_DASH_DOT
      */
-
-    public void setBorderBottom(short border)
-    {
-       field_13_border_styles1 = bordBottomLineStyle.setValue(field_13_border_styles1, border);
+    public void setBorderBottom(int border) {
+        field_13_border_styles1 = bordBottomLineStyle.setValue(field_13_border_styles1, border);
     }
 
     /**
@@ -339,11 +262,10 @@ public class BorderFormatting
      * @see #BORDER_MEDIUM_DASH_DOT_DOT
      * @see #BORDER_SLANTED_DASH_DOT
      */
-    public short getBorderBottom()
-    {
-       return (short)bordBottomLineStyle.getValue(field_13_border_styles1);
+    public int getBorderBottom() {
+        return bordBottomLineStyle.getValue(field_13_border_styles1);
     }
-    
+
     /**
      * set the type of border to use for the diagonal border of the cell
      * @param border type
@@ -362,10 +284,8 @@ public class BorderFormatting
      * @see #BORDER_MEDIUM_DASH_DOT_DOT
      * @see #BORDER_SLANTED_DASH_DOT
      */
-
-    public void setBorderDiagonal(short border)
-    {
-       field_14_border_styles2 = bordDiagLineStyle.setValue(field_14_border_styles2, border);
+    public void setBorderDiagonal(int border) {
+        field_14_border_styles2 = bordDiagLineStyle.setValue(field_14_border_styles2, border);
     }
 
     /**
@@ -386,18 +306,16 @@ public class BorderFormatting
      * @see #BORDER_MEDIUM_DASH_DOT_DOT
      * @see #BORDER_SLANTED_DASH_DOT
      */
-    public short getBorderDiagonal()
-    {
-       return (short)bordDiagLineStyle.getValue(field_14_border_styles2);
+    public int getBorderDiagonal() {
+        return bordDiagLineStyle.getValue(field_14_border_styles2);
     }
 
     /**
      * set the color to use for the left border
      * @param color The index of the color definition
      */
-    public void setLeftBorderColor(short color)
-    {
-       field_13_border_styles1 = bordLeftLineColor.setValue(field_13_border_styles1, color);
+    public void setLeftBorderColor(int color) {
+        field_13_border_styles1 = bordLeftLineColor.setValue(field_13_border_styles1, color);
     }
 
     /**
@@ -405,18 +323,16 @@ public class BorderFormatting
      * @see org.apache.poi.hssf.usermodel.HSSFPalette#getColor(short)
      * @param color The index of the color definition
      */
-    public short getLeftBorderColor()
-    {
-       return (short)bordLeftLineColor.getValue(field_13_border_styles1);
+    public int getLeftBorderColor() {
+        return bordLeftLineColor.getValue(field_13_border_styles1);
     }
 
     /**
      * set the color to use for the right border
      * @param color The index of the color definition
      */
-    public void setRightBorderColor(short color)
-    {
-       field_13_border_styles1 = bordRightLineColor.setValue(field_13_border_styles1, color);
+    public void setRightBorderColor(int color) {
+        field_13_border_styles1 = bordRightLineColor.setValue(field_13_border_styles1, color);
     }
 
     /**
@@ -424,18 +340,16 @@ public class BorderFormatting
      * @see org.apache.poi.hssf.usermodel.HSSFPalette#getColor(short)
      * @param color The index of the color definition
      */
-    public short getRightBorderColor()
-    {
-       return (short)bordRightLineColor.getValue(field_13_border_styles1);
+    public int getRightBorderColor() {
+        return bordRightLineColor.getValue(field_13_border_styles1);
     }
 
     /**
      * set the color to use for the top border
      * @param color The index of the color definition
      */
-    public void setTopBorderColor(short color)
-    {
-       field_14_border_styles2 = bordTopLineColor.setValue(field_14_border_styles2, color);
+    public void setTopBorderColor(int color) {
+        field_14_border_styles2 = bordTopLineColor.setValue(field_14_border_styles2, color);
     }
 
     /**
@@ -443,18 +357,17 @@ public class BorderFormatting
      * @see org.apache.poi.hssf.usermodel.HSSFPalette#getColor(short)
      * @param color The index of the color definition
      */
-    public short getTopBorderColor()
-    {
-       return (short)bordTopLineColor.getValue(field_14_border_styles2);
+    public int getTopBorderColor() {
+        return bordTopLineColor.getValue(field_14_border_styles2);
     }
 
     /**
      * set the color to use for the bottom border
      * @param color The index of the color definition
      */
-    public void setBottomBorderColor(short color)
+    public void setBottomBorderColor(int color)
     {
-       field_14_border_styles2 = bordBottomLineColor.setValue(field_14_border_styles2, color);
+        field_14_border_styles2 = bordBottomLineColor.setValue(field_14_border_styles2, color);
     }
 
     /**
@@ -462,18 +375,16 @@ public class BorderFormatting
      * @see org.apache.poi.hssf.usermodel.HSSFPalette#getColor(short)
      * @param color The index of the color definition
      */
-    public short getBottomBorderColor()
-    {
-       return (short)bordBottomLineColor.getValue(field_14_border_styles2);
+    public int getBottomBorderColor() {
+        return bordBottomLineColor.getValue(field_14_border_styles2);
     }
-    
+
     /**
      * set the color to use for the diagonal borders
      * @param color The index of the color definition
      */
-    public void setDiagonalBorderColor(short color)
-    {
-       field_14_border_styles2 = bordDiagLineColor.setValue(field_14_border_styles2, color);
+    public void setDiagonalBorderColor(int color) {
+        field_14_border_styles2 = bordDiagLineColor.setValue(field_14_border_styles2, color);
     }
 
     /**
@@ -481,50 +392,44 @@ public class BorderFormatting
      * @see org.apache.poi.hssf.usermodel.HSSFPalette#getColor(short)
      * @param color The index of the color definition
      */
-    public short getDiagonalBorderColor()
-    {
-       return (short)bordDiagLineColor.getValue(field_14_border_styles2);
+    public int getDiagonalBorderColor() {
+        return bordDiagLineColor.getValue(field_14_border_styles2);
     }
 
     /**
      * Of/off bottom left to top right line
-     *   
-     * @param on - if true - on, otherwise off
+     *
+     * @param on - if <code>true</code> - on, otherwise off
      */
-    public void setForwardDiagonalOn(boolean on)
-    {
-       field_13_border_styles1 = bordBlTrtLineOnOff.setBoolean(field_13_border_styles1, on);
+    public void setForwardDiagonalOn(boolean on) {
+        field_13_border_styles1 = bordBlTrtLineOnOff.setBoolean(field_13_border_styles1, on);
     }
 
     /**
      * Of/off top left to bottom right line
-     *   
-     * @param on - if true - on, otherwise off
+     *
+     * @param on - if <code>true</code> - on, otherwise off
      */
-    public void setBackwardDiagonalOn(boolean on)
-    {
-       field_13_border_styles1 = bordTlBrLineOnOff.setBoolean(field_13_border_styles1, on);
+    public void setBackwardDiagonalOn(boolean on) {
+        field_13_border_styles1 = bordTlBrLineOnOff.setBoolean(field_13_border_styles1, on);
     }
-    
+
     /**
-     * @return true if forward diagonal is on
+     * @return <code>true</code> if forward diagonal is on
      */
-    public boolean isForwardDiagonalOn()
-    {
-       return bordBlTrtLineOnOff.isSet(field_13_border_styles1);
+    public boolean isForwardDiagonalOn() {
+        return bordBlTrtLineOnOff.isSet(field_13_border_styles1);
     }
 
     /**
-     * @return true if backward diagonal is on
+     * @return <code>true</code> if backward diagonal is on
      */
-    public boolean isBackwardDiagonalOn()
-    {
-       return bordTlBrLineOnOff.isSet(field_13_border_styles1);
+    public boolean isBackwardDiagonalOn() {
+        return bordTlBrLineOnOff.isSet(field_13_border_styles1);
     }
-    
-    
-    public String toString()
-    {
+
+
+    public String toString() {
         StringBuffer buffer = new StringBuffer();
         buffer.append("    [Border Formatting]\n");
         buffer.append("          .lftln     = ").append(Integer.toHexString(getBorderLeft())).append("\n");
@@ -540,21 +445,21 @@ public class BorderFormatting
         buffer.append("    [/Border Formatting]\n");
         return buffer.toString();
     }
-    
-    public Object clone() 
-    {
+
+    public Object clone() {
       BorderFormatting rec = new BorderFormatting();
-      rec.field_13_border_styles1 = field_13_border_styles1; 
-      rec.field_14_border_styles2 = field_14_border_styles2; 
+      rec.field_13_border_styles1 = field_13_border_styles1;
+      rec.field_14_border_styles2 = field_14_border_styles2;
       return rec;
     }
-    
-    public int serialize(int offset, byte [] data)
-    {
-       LittleEndian.putInt(data, offset, field_13_border_styles1);
-       offset += 4;
-       LittleEndian.putInt(data, offset, field_14_border_styles2);
-       offset += 4;
-       return 8;
+
+    public int serialize(int offset, byte [] data) {
+        LittleEndian.putInt(data, offset+0, field_13_border_styles1);
+        LittleEndian.putInt(data, offset+4, field_14_border_styles2);
+        return 8;
+    }
+    public void serialize(LittleEndianOutput out) {
+        out.writeInt(field_13_border_styles1);
+        out.writeInt(field_14_border_styles2);
     }
 }
index 3157bf46c8596d44896380aa379101376b72733e..2587157f78499b5943223fcc503a7e0c69b86db6 100644 (file)
@@ -1,4 +1,3 @@
-
 /* ====================================================================
    Licensed to the Apache Software Foundation (ASF) under one or more
    contributor license agreements.  See the NOTICE file distributed with
    See the License for the specific language governing permissions and
    limitations under the License.
 ==================================================================== */
-        
 
-/*
- * FontFormatting.java
- *
- * Created on January 22, 2008, 10:05 PM
- */
 package org.apache.poi.hssf.record.cf;
 
-import org.apache.poi.hssf.record.RecordInputStream;
 import org.apache.poi.util.BitField;
 import org.apache.poi.util.BitFieldFactory;
-import org.apache.poi.util.LittleEndian;
+import org.apache.poi.util.LittleEndianInput;
+import org.apache.poi.util.LittleEndianOutput;
 
 /**
  * Pattern Formatting Block of the Conditional Formatting Rule Record.
  * 
  * @author Dmitriy Kumshayev
  */
-
-public class PatternFormatting implements Cloneable
-{
+public final class PatternFormatting implements Cloneable {
     /**  No background */
     public final static short     NO_FILL             = 0  ;
     /**  Solidly filled */
@@ -75,29 +66,29 @@ public class PatternFormatting implements Cloneable
     public final static short     LESS_DOTS           = 17 ;
     /**  Least Dots */
     public final static short     LEAST_DOTS          = 18 ;
-       
-    public PatternFormatting()
-    {
-        field_15_pattern_style = (short)0;
-        field_16_pattern_color_indexes = (short)0;
-    }
     
-    /** Creates new FontFormatting */
-    public PatternFormatting(RecordInputStream in)
-       {
-       field_15_pattern_style  = in.readShort();
-        field_16_pattern_color_indexes = in.readShort();
-       }
     
     // PATTERN FORMATING BLOCK
     // For Pattern Styles see constants at HSSFCellStyle (from NO_FILL to LEAST_DOTS)
-    private short                       field_15_pattern_style;
+    private int              field_15_pattern_style;
     private static final BitField  fillPatternStyle = BitFieldFactory.getInstance(0xFC00);
     
-    private short                       field_16_pattern_color_indexes;
-    private static final BitField  patternColorIndex = BitFieldFactory.getInstance(0x007F);             
-    private static final BitField  patternBackgroundColorIndex = BitFieldFactory.getInstance(0x3F80);           
+    private int              field_16_pattern_color_indexes;
+    private static final BitField  patternColorIndex = BitFieldFactory.getInstance(0x007F);         
+    private static final BitField  patternBackgroundColorIndex = BitFieldFactory.getInstance(0x3F80);         
 
+    
+    public PatternFormatting() {
+        field_15_pattern_style    = 0;
+        field_16_pattern_color_indexes    = 0;
+    }
+    
+    /** Creates new FontFormatting */
+    public PatternFormatting(LittleEndianInput in) {
+        field_15_pattern_style    = in.readUShort();
+        field_16_pattern_color_indexes    = in.readUShort();
+    }
+    
     /**
      * setting fill pattern
      *
@@ -121,63 +112,48 @@ public class PatternFormatting implements Cloneable
      *
      * @param fp  fill pattern 
      */
-    public void setFillPattern(short fp)
-    {
-       field_15_pattern_style = fillPatternStyle.setShortValue(field_15_pattern_style, fp);
+    public void setFillPattern(int fp) {
+        field_15_pattern_style = fillPatternStyle.setValue(field_15_pattern_style, fp);
     }
 
     /**
-     * get the fill pattern 
      * @return fill pattern
      */
-
-    public short getFillPattern()
-    {
-        return fillPatternStyle.getShortValue(field_15_pattern_style);
+    public int getFillPattern() {
+        return fillPatternStyle.getValue(field_15_pattern_style);
     }
     
     /**
      * set the background fill color.
-     *
-     * @param bg  color
      */
-
-    public void setFillBackgroundColor(short bg)
-    {          
-       field_16_pattern_color_indexes = patternBackgroundColorIndex.setShortValue(field_16_pattern_color_indexes,bg);
+    public void setFillBackgroundColor(int bg) {        
+        field_16_pattern_color_indexes = patternBackgroundColorIndex.setValue(field_16_pattern_color_indexes,bg);
     }
 
     /**
-     * get the background fill color
      * @see org.apache.poi.hssf.usermodel.HSSFPalette#getColor(short)
-     * @return fill color
+     * @return get the background fill color
      */
-    public short getFillBackgroundColor()
-    {
-       return patternBackgroundColorIndex.getShortValue(field_16_pattern_color_indexes);
+    public int getFillBackgroundColor() {
+        return patternBackgroundColorIndex.getValue(field_16_pattern_color_indexes);
     }
 
     /**
      * set the foreground fill color
-     * @param bg  color
      */
-    public void setFillForegroundColor(short fg)
-    {
-       field_16_pattern_color_indexes = patternColorIndex.setShortValue(field_16_pattern_color_indexes,fg);
+    public void setFillForegroundColor(int fg) {
+        field_16_pattern_color_indexes = patternColorIndex.setValue(field_16_pattern_color_indexes,fg);
     }
 
     /**
-     * get the foreground fill color
      * @see org.apache.poi.hssf.usermodel.HSSFPalette#getColor(short)
-     * @return fill color
+     * @return get the foreground fill color
      */
-    public short getFillForegroundColor()
-    {
-       return patternColorIndex.getShortValue(field_16_pattern_color_indexes);
+    public int getFillForegroundColor() {
+        return patternColorIndex.getValue(field_16_pattern_color_indexes);
     }
     
-    public String toString()
-    {
+    public String toString() {
         StringBuffer buffer = new StringBuffer();
         buffer.append("    [Pattern Formatting]\n");
         buffer.append("          .fillpattern= ").append(Integer.toHexString(getFillPattern())).append("\n");
@@ -187,20 +163,15 @@ public class PatternFormatting implements Cloneable
         return buffer.toString();
     }
     
-    public Object clone() 
-    {
+    public Object clone()  {
       PatternFormatting rec = new PatternFormatting();
       rec.field_15_pattern_style = field_15_pattern_style;
       rec.field_16_pattern_color_indexes = field_16_pattern_color_indexes; 
       return rec;
     }
-    
-    public int serialize(int offset, byte [] data)
-    {
-       LittleEndian.putShort(data, offset, field_15_pattern_style);
-       offset += 2;
-       LittleEndian.putShort(data, offset, field_16_pattern_color_indexes);
-       offset += 2;
-       return 4;
+
+    public void serialize(LittleEndianOutput out) {
+        out.writeShort(field_15_pattern_style);
+        out.writeShort(field_16_pattern_color_indexes);
     }
 }
index 33e1d3a8501f43e595cc381289a89add54e2607f..178e1675ef70e69e428b0227ae2e4357a7f8ceac 100644 (file)
@@ -75,52 +75,52 @@ public final class HSSFBorderFormatting
 
        public short getBorderBottom()
        {
-               return borderFormatting.getBorderBottom();
+               return (short)borderFormatting.getBorderBottom();
        }
 
        public short getBorderDiagonal()
        {
-               return borderFormatting.getBorderDiagonal();
+               return (short)borderFormatting.getBorderDiagonal();
        }
 
        public short getBorderLeft()
        {
-               return borderFormatting.getBorderLeft();
+               return (short)borderFormatting.getBorderLeft();
        }
 
        public short getBorderRight()
        {
-               return borderFormatting.getBorderRight();
+               return (short)borderFormatting.getBorderRight();
        }
 
        public short getBorderTop()
        {
-               return borderFormatting.getBorderTop();
+               return (short)borderFormatting.getBorderTop();
        }
 
        public short getBottomBorderColor()
        {
-               return borderFormatting.getBottomBorderColor();
+               return (short)borderFormatting.getBottomBorderColor();
        }
 
        public short getDiagonalBorderColor()
        {
-               return borderFormatting.getDiagonalBorderColor();
+               return (short)borderFormatting.getDiagonalBorderColor();
        }
 
        public short getLeftBorderColor()
        {
-               return borderFormatting.getLeftBorderColor();
+               return (short)borderFormatting.getLeftBorderColor();
        }
 
        public short getRightBorderColor()
        {
-               return borderFormatting.getRightBorderColor();
+               return (short)borderFormatting.getRightBorderColor();
        }
 
        public short getTopBorderColor()
        {
-               return borderFormatting.getTopBorderColor();
+               return (short)borderFormatting.getTopBorderColor();
        }
 
        public boolean isBackwardDiagonalOn()
index 9c1f1457f505ca3e6494f902b534492a6738700a..0b3c4756156fc463c886be52855ec0910bd160f7 100644 (file)
@@ -265,9 +265,8 @@ public final class HSSFCell {
      * @see #CELL_TYPE_BOOLEAN
      * @see #CELL_TYPE_ERROR
      */
-
-    public void setCellType(int cellType)
-    {
+    public void setCellType(int cellType) {
+        notifyFormulaChanging();
         int row=record.getRow();
         short col=record.getColumn();
         short styleIndex=record.getXFIndex();
@@ -533,13 +532,14 @@ public final class HSSFCell {
      * @param value  value to set the cell to.  For formulas we'll set the formula
      * string, for String cells we'll set its value.  For other types we will
      * change the cell to a string cell and set its value.
-     * If value is null then we will change the cell to a Blank cell.
+     * If value is <code>null</code> then we will change the cell to a Blank cell.
      */
     public void setCellValue(HSSFRichTextString value) {
         int row=record.getRow();
         short col=record.getColumn();
         short styleIndex=record.getXFIndex();
         if (value == null) {
+            notifyFormulaChanging();
             setCellType(CELL_TYPE_BLANK, false, row, col, styleIndex);
             return;
         }
@@ -577,25 +577,35 @@ public final class HSSFCell {
         short styleIndex=record.getXFIndex();
 
         if (formula==null) {
+            notifyFormulaChanging();
             setCellType(CELL_TYPE_BLANK, false, row, col, styleIndex);
             return;
         }
         setCellType(CELL_TYPE_FORMULA, false, row, col, styleIndex);
-        FormulaRecordAggregate rec = (FormulaRecordAggregate) record;
-        FormulaRecord frec = rec.getFormulaRecord();
+        FormulaRecordAggregate agg = (FormulaRecordAggregate) record;
+        FormulaRecord frec = agg.getFormulaRecord();
         frec.setOptions((short) 2);
         frec.setValue(0);
 
         //only set to default if there is no extended format index already set
-        if (rec.getXFIndex() == (short)0) {
-            rec.setXFIndex((short) 0x0f);
+        if (agg.getXFIndex() == (short)0) {
+            agg.setXFIndex((short) 0x0f);
         }
         Ptg[] ptgs = HSSFFormulaParser.parse(formula, book);
-        frec.setParsedExpression(ptgs);
+        agg.setParsedExpression(ptgs);
+    }
+    /**
+     * Should be called any time that a formula could potentially be deleted.
+     * Does nothing if this cell currently does not hold a formula
+     */
+    private void notifyFormulaChanging() {
+        if (record instanceof FormulaRecordAggregate) {
+            ((FormulaRecordAggregate)record).notifyFormulaChanging();
+        }
     }
 
     public String getCellFormula() {
-        return HSSFFormulaParser.toFormulaString(book, ((FormulaRecordAggregate)record).getFormulaRecord().getParsedExpression());
+        return HSSFFormulaParser.toFormulaString(book, ((FormulaRecordAggregate)record).getFormulaTokens());
     }
 
     /**
index 359862e47732343d6670bf7bd2c5be514e77f637..9830b9f27b17f1ad42d6afd265045285e92cffc2 100644 (file)
@@ -19,7 +19,6 @@ package org.apache.poi.hssf.usermodel;
 \r
 import org.apache.poi.hssf.model.HSSFFormulaParser;\r
 import org.apache.poi.hssf.model.Workbook;\r
-import org.apache.poi.hssf.record.FormulaRecord;\r
 import org.apache.poi.hssf.record.NameRecord;\r
 import org.apache.poi.hssf.record.aggregates.FormulaRecordAggregate;\r
 import org.apache.poi.hssf.record.formula.NamePtg;\r
@@ -121,8 +120,8 @@ public final class HSSFEvaluationWorkbook implements FormulaRenderingWorkbook, E
                        // to make sure that all formulas POI can evaluate can also be parsed.\r
                        return HSSFFormulaParser.parse(cell.getCellFormula(), _uBook);\r
                }\r
-               FormulaRecord fr = ((FormulaRecordAggregate) cell.getCellValueRecord()).getFormulaRecord();\r
-               return fr.getParsedExpression();\r
+               FormulaRecordAggregate fra = (FormulaRecordAggregate) cell.getCellValueRecord();\r
+               return fra.getFormulaTokens();\r
        }\r
 \r
        private static final class Name implements EvaluationName {\r
index cb1ae9365eaaae0aef175f9a57589abf8a4628ac..1ec1dfa9c6c91731b548aaffd4e0a58fec63ba8a 100644 (file)
@@ -87,7 +87,7 @@ public class HSSFPatternFormatting
         */
        public short getFillBackgroundColor()
        {
-               return patternFormatting.getFillBackgroundColor();
+               return (short)patternFormatting.getFillBackgroundColor();
        }
 
        /**
@@ -96,7 +96,7 @@ public class HSSFPatternFormatting
         */
        public short getFillForegroundColor()
        {
-               return patternFormatting.getFillForegroundColor();
+               return (short)patternFormatting.getFillForegroundColor();
        }
 
        /**
@@ -105,7 +105,7 @@ public class HSSFPatternFormatting
         */
        public short getFillPattern()
        {
-               return patternFormatting.getFillPattern();
+               return (short)patternFormatting.getFillPattern();
        }
 
        /**
index 883932cb9572b53cbc46dd33ade47c49212b6a30..8f1c07d108e7adbe417bb6c2f22d2e21e43dbae7 100644 (file)
@@ -19,6 +19,8 @@ package org.apache.poi.hssf.util;
 import org.apache.poi.hssf.record.RecordInputStream;\r
 import org.apache.poi.hssf.record.SelectionRecord;\r
 import org.apache.poi.util.LittleEndian;\r
+import org.apache.poi.util.LittleEndianByteArrayOutputStream;\r
+import org.apache.poi.util.LittleEndianOutput;\r
 \r
 /**\r
  * See OOO documentation: excelfileformat.pdf sec 2.5.14 - 'Cell Range Address'<p/>\r
@@ -48,13 +50,19 @@ public final class CellRangeAddress extends CellRangeAddressBase {
                return in.readUShort();\r
        }\r
 \r
+       /**\r
+        * @deprecated use {@link #serialize(LittleEndianOutput)}\r
+        */\r
        public int serialize(int offset, byte[] data) {\r
-               LittleEndian.putUShort(data, offset + 0, getFirstRow());\r
-               LittleEndian.putUShort(data, offset + 2, getLastRow());\r
-               LittleEndian.putUShort(data, offset + 4, getFirstColumn());\r
-               LittleEndian.putUShort(data, offset + 6, getLastColumn());\r
+               serialize(new LittleEndianByteArrayOutputStream(data, offset, ENCODED_SIZE));\r
                return ENCODED_SIZE;\r
        }\r
+       public void serialize(LittleEndianOutput out) {\r
+               out.writeShort(getFirstRow());\r
+               out.writeShort(getLastRow());\r
+               out.writeShort(getFirstColumn());\r
+               out.writeShort(getLastColumn());\r
+       }\r
        \r
        public CellRangeAddress copy() {\r
                return new CellRangeAddress(getFirstRow(), getLastRow(), getFirstColumn(), getLastColumn());\r
index ef949f4f5548cb737e7f7a493d0008f8d6701569..9a4cc122bb2bfea0ae42a3b6392c0f343e6fc737 100644 (file)
@@ -16,8 +16,9 @@
 \r
 package org.apache.poi.hssf.util;\r
 \r
-import org.apache.poi.hssf.record.RecordInputStream;\r
-import org.apache.poi.util.LittleEndian;\r
+import org.apache.poi.util.LittleEndianByteArrayOutputStream;\r
+import org.apache.poi.util.LittleEndianInput;\r
+import org.apache.poi.util.LittleEndianOutput;\r
 \r
 /**\r
  * See OOO documentation: excelfileformat.pdf sec 2.5.14 - 'Cell Range Address'<p/>\r
@@ -34,25 +35,31 @@ public final class CellRangeAddress8Bit extends CellRangeAddressBase {
                super(firstRow, lastRow, firstCol, lastCol);\r
        }\r
 \r
-       public CellRangeAddress8Bit(RecordInputStream in) {\r
+       public CellRangeAddress8Bit(LittleEndianInput in) {\r
                super(readUShortAndCheck(in), in.readUShort(), in.readUByte(), in.readUByte());\r
        }\r
 \r
-       private static int readUShortAndCheck(RecordInputStream in) {\r
-               if (in.remaining() < ENCODED_SIZE) {\r
+       private static int readUShortAndCheck(LittleEndianInput in) {\r
+               if (in.available() < ENCODED_SIZE) {\r
                        // Ran out of data\r
                        throw new RuntimeException("Ran out of data reading CellRangeAddress");\r
                }\r
                return in.readUShort();\r
        }\r
 \r
+       /**\r
+        * @deprecated use {@link #serialize(LittleEndianOutput)}\r
+        */\r
        public int serialize(int offset, byte[] data) {\r
-               LittleEndian.putUShort(data, offset + 0, getFirstRow());\r
-               LittleEndian.putUShort(data, offset + 2, getLastRow());\r
-               LittleEndian.putByte(data, offset + 4, getFirstColumn());\r
-               LittleEndian.putByte(data, offset + 5, getLastColumn());\r
+               serialize(new LittleEndianByteArrayOutputStream(data, offset, ENCODED_SIZE));\r
                return ENCODED_SIZE;\r
        }\r
+       public void serialize(LittleEndianOutput out) {\r
+               out.writeShort(getFirstRow());\r
+               out.writeShort(getLastRow());\r
+               out.writeByte(getFirstColumn());\r
+               out.writeByte(getLastColumn());\r
+       }\r
        \r
        public CellRangeAddress8Bit copy() {\r
                return new CellRangeAddress8Bit(getFirstRow(), getLastRow(), getFirstColumn(), getLastColumn());\r
index b29a8bf3a46bf4ef38a7cb0d6e6c9f7caf113e8a..c67f5537f5936798009991ebfe59eafac727a8ae 100644 (file)
@@ -21,6 +21,8 @@ import java.util.List;
 
 import org.apache.poi.hssf.record.RecordInputStream;
 import org.apache.poi.util.LittleEndian;
+import org.apache.poi.util.LittleEndianByteArrayOutputStream;
+import org.apache.poi.util.LittleEndianOutput;
 
 /**
  * Implementation of the cell range address lists,like is described
@@ -113,15 +115,17 @@ public final class CellRangeAddressList {
        }
 
        public int serialize(int offset, byte[] data) {
-               int pos = 2;
-
+               int totalSize = getSize();
+               serialize(new LittleEndianByteArrayOutputStream(data, offset, totalSize));
+               return totalSize;
+       }
+       public void serialize(LittleEndianOutput out) {
                int nItems = _list.size();
-               LittleEndian.putUShort(data, offset, nItems);
+               out.writeShort(nItems);
                for (int k = 0; k < nItems; k++) {
                        CellRangeAddress region = (CellRangeAddress) _list.get(k);
-                       pos += region.serialize(offset + pos, data);
+                       region.serialize(out);
                }
-               return getSize();
        }
 
        public int getSize() {
diff --git a/src/java/org/apache/poi/ss/formula/Formula.java b/src/java/org/apache/poi/ss/formula/Formula.java
new file mode 100644 (file)
index 0000000..5531445
--- /dev/null
@@ -0,0 +1,133 @@
+package org.apache.poi.ss.formula;\r
+\r
+import org.apache.poi.hssf.record.formula.Ptg;\r
+import org.apache.poi.util.LittleEndianByteArrayInputStream;\r
+import org.apache.poi.util.LittleEndianInput;\r
+import org.apache.poi.util.LittleEndianOutput;\r
+\r
+public class Formula {\r
+\r
+       private static final byte[] EMPTY_BYTE_ARRAY = { };\r
+       private final byte[] _byteEncoding;\r
+       private final int _encodedTokenLen;\r
+       \r
+       private Formula(byte[] byteEncoding, int encodedTokenLen) {\r
+               _byteEncoding = byteEncoding;\r
+               _encodedTokenLen = encodedTokenLen;\r
+               if (false) { // set to true to eagerly check Ptg decoding \r
+               LittleEndianByteArrayInputStream in = new LittleEndianByteArrayInputStream(byteEncoding);\r
+               Ptg.readTokens(encodedTokenLen, in);\r
+               int nUnusedBytes = _byteEncoding.length - in.getReadIndex();\r
+               if (nUnusedBytes > 0) {\r
+                       // TODO - this seems to occur when IntersectionPtg is present\r
+                       // This example file "IntersectionPtg.xls"\r
+                       // used by test: TestIntersectionPtg.testReading()\r
+                       // has 10 bytes unused at the end of the formula\r
+                       // 10 extra bytes are just 0x01 and 0x00\r
+                       System.out.println(nUnusedBytes + " unused bytes at end of formula");\r
+               }\r
+               }\r
+       }\r
+       /**\r
+        * Convenience method for {@link #read(int, LittleEndianInput, int)}\r
+        */\r
+       public static Formula read(int encodedTokenLen, LittleEndianInput in) {\r
+               return read(encodedTokenLen, in, encodedTokenLen);\r
+       }\r
+       /**\r
+        * When there are no array constants present, <tt>encodedTokenLen</tt>==<tt>totalEncodedLen</tt>\r
+        * @param encodedTokenLen number of bytes in the stream taken by the plain formula tokens\r
+        * @param totalEncodedLen the total number of bytes in the formula (includes trailing encoding\r
+        * for array constants, but does not include 2 bytes for initial <tt>ushort encodedTokenLen</tt> field.\r
+        * @return A new formula object as read from the stream.  Possibly empty, never <code>null</code>.\r
+        */\r
+       public static Formula read(int encodedTokenLen, LittleEndianInput in, int totalEncodedLen) {\r
+               byte[] byteEncoding = new byte[totalEncodedLen];\r
+               in.readFully(byteEncoding);\r
+               return new Formula(byteEncoding, encodedTokenLen);\r
+       }\r
+       \r
+       public Ptg[] getTokens() {\r
+               LittleEndianInput in = new LittleEndianByteArrayInputStream(_byteEncoding);\r
+               return Ptg.readTokens(_encodedTokenLen, in);\r
+       }\r
+       /**\r
+        * Writes  The formula encoding is includes:\r
+        * <ul>\r
+        * <li>ushort tokenDataLen</li>\r
+        * <li>tokenData</li>\r
+        * <li>arrayConstantData (if present)</li>\r
+        * </ul>\r
+        */\r
+       public void serialize(LittleEndianOutput out) {\r
+               out.writeShort(_encodedTokenLen);\r
+               out.write(_byteEncoding);\r
+       }\r
+\r
+       public void serializeTokens(LittleEndianOutput out) {\r
+               out.write(_byteEncoding, 0, _encodedTokenLen);\r
+       }\r
+       public void serializeArrayConstantData(LittleEndianOutput out) {\r
+               int len = _byteEncoding.length-_encodedTokenLen;\r
+               out.write(_byteEncoding, _encodedTokenLen, len);\r
+       }\r
+       \r
+       \r
+       /**\r
+        * @return total formula encoding length.  The formula encoding includes:\r
+        * <ul>\r
+        * <li>ushort tokenDataLen</li>\r
+        * <li>tokenData</li>\r
+        * <li>arrayConstantData (optional)</li>\r
+        * </ul>\r
+        * Note - this value is different to <tt>tokenDataLength</tt>\r
+        */\r
+       public int getEncodedSize() {\r
+               return 2 + _byteEncoding.length;\r
+       }\r
+       /**\r
+        * This method is often used when the formula length does not appear immediately before\r
+        * the encoded token data.\r
+        * \r
+        * @return the encoded length of the plain formula tokens.  This does <em>not</em> include\r
+        * the leading ushort field, nor any trailing array constant data.\r
+        */\r
+       public int getEncodedTokenSize() {\r
+               return _encodedTokenLen;\r
+       }\r
+       \r
+       /**\r
+        * Creates a {@link Formula} object from a supplied {@link Ptg} array. \r
+        * Handles <code>null</code>s OK.\r
+        * @param ptgs may be <code>null</code>\r
+        * @return Never <code>null</code> (Possibly empty if the supplied <tt>ptgs</tt> is <code>null</code>)\r
+        */\r
+       public static Formula create(Ptg[] ptgs) {\r
+               if (ptgs == null) {\r
+                       return new Formula(EMPTY_BYTE_ARRAY, 0);\r
+               }\r
+               int totalSize = Ptg.getEncodedSize(ptgs);\r
+               byte[] encodedData = new byte[totalSize];\r
+               Ptg.serializePtgs(ptgs, encodedData, 0);\r
+               int encodedTokenLen = Ptg.getEncodedSizeWithoutArrayData(ptgs);\r
+               return new Formula(encodedData, encodedTokenLen);\r
+       }\r
+       /**\r
+        * Gets the {@link Ptg} array from the supplied {@link Formula}. \r
+        * Handles <code>null</code>s OK.\r
+        * \r
+        * @param formula may be <code>null</code>\r
+        * @return possibly <code>null</code> (if the supplied <tt>formula</tt> is <code>null</code>)\r
+        */\r
+       public static Ptg[] getTokens(Formula formula) {\r
+               if (formula == null) {\r
+                       return null;\r
+               }\r
+               return formula.getTokens();\r
+       }\r
+       \r
+       public Formula copy() {\r
+               // OK to return this for the moment because currently immutable\r
+               return this;\r
+       }\r
+}\r
index b77407c7a926b70e1480bb99705734162b279437..1b68a348bee1ad21152b28e9e25ad59432355411 100644 (file)
@@ -81,6 +81,11 @@ public final class LittleEndianByteArrayOutputStream implements LittleEndianOutp
                System.arraycopy(b, 0, _buf, _writeIndex, len);
                _writeIndex += len;
        }
+       public void write(byte[] b, int offset, int len) {
+               checkPosition(len);
+               System.arraycopy(b, offset, _buf, _writeIndex, len);
+               _writeIndex += len;
+       }
        public int getWriteIndex() {
                return _writeIndex;
        }
index ccea11a17dd902701f6b46ae6c8873839b685bd7..450b337bdbe5f182dbc67edc51a92254d4cf9d1a 100644 (file)
@@ -26,5 +26,6 @@ public interface LittleEndianOutput {
        void writeInt(int v);\r
        void writeLong(long v);\r
        void writeDouble(double v);\r
-       void write(byte[] data);\r
+       void write(byte[] b);\r
+       void write(byte[] b, int offset, int len);\r
 }\r
index 45f2d9e3dd6d87bc5d7d329105a5b53612ab06df..249211d049086d5c492d086057ec9a11a92ed7e0 100644 (file)
@@ -80,4 +80,12 @@ public final class LittleEndianOutputStream extends FilterOutputStream implement
                        throw new RuntimeException(e);\r
                }\r
        }\r
+       public void write(byte[] b, int off, int len) {\r
+               // suppress IOException for interface method\r
+               try {\r
+                       super.write(b, off, len);\r
+               } catch (IOException e) {\r
+                       throw new RuntimeException(e);\r
+               }\r
+       }\r
 }\r
index cf8fe0bd2fd328100af52dd3664a83dd81be6825..b84ffc4c1040d06df266eb68a26208e179ccf8ca 100644 (file)
@@ -17,8 +17,6 @@
 
 package org.apache.poi.hssf.record;
 
-import java.io.ByteArrayInputStream;
-
 import junit.framework.TestCase;
 
 import org.apache.poi.hssf.record.formula.AttrPtg;
@@ -152,4 +150,22 @@ public final class TestFormulaRecord extends TestCase {
                FuncVarPtg choose = (FuncVarPtg)ptgs[8];
                assertEquals("CHOOSE", choose.getName());
        }
+       
+       public void testReserialize() {
+               FormulaRecord formulaRecord = new FormulaRecord();
+               formulaRecord.setRow(1);
+               formulaRecord.setColumn((short) 1);
+               formulaRecord.setParsedExpression(new Ptg[] { new RefPtg("B$5"), });
+               formulaRecord.setValue(3.3);
+               byte[] ser = formulaRecord.serialize();
+               assertEquals(31, ser.length);
+
+               RecordInputStream in = TestcaseRecordInputStream.create(ser);
+               FormulaRecord fr2 = new FormulaRecord(in);
+               assertEquals(3.3, fr2.getValue(), 0.0);
+               Ptg[] ptgs = fr2.getParsedExpression();
+               assertEquals(1, ptgs.length);
+               RefPtg rp = (RefPtg) ptgs[0];
+               assertEquals("B$5", rp.toFormulaString());
+       }
 }
index cbb4b048a0e7a88e34108711310f0244c8cad5f0..65e8fe4809a45d2dc64c8510752aad26470bce65 100755 (executable)
 
 package org.apache.poi.hssf.record;
 
+import org.apache.poi.util.HexRead;
+
 import junit.framework.TestCase;
 
 /**
  * Tests the NameRecord serializes/deserializes correctly
- *
+ * 
  * @author Danny Mui (dmui at apache dot org)
  */
 public final class TestNameRecord extends TestCase {
 
-    /**
-     * Makes sure that additional name information is parsed properly such as menu/description
-     */
-    public void testFillExtras()
-    {
-
-        byte[] examples = {
-            (byte) 0x88, (byte) 0x03, (byte) 0x67, (byte) 0x06,
-            (byte) 0x07, (byte) 0x00, (byte) 0x00, (byte) 0x00,
-            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x23,
-            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x4D,
-            (byte) 0x61, (byte) 0x63, (byte) 0x72, (byte) 0x6F,
-            (byte) 0x31, (byte) 0x3A, (byte) 0x01, (byte) 0x00,
-            (byte) 0x00, (byte) 0x00, (byte) 0x11, (byte) 0x00,
-            (byte) 0x00, (byte) 0x4D, (byte) 0x61, (byte) 0x63,
-            (byte) 0x72, (byte) 0x6F, (byte) 0x20, (byte) 0x72,
-            (byte) 0x65, (byte) 0x63, (byte) 0x6F, (byte) 0x72,
-            (byte) 0x64, (byte) 0x65, (byte) 0x64, (byte) 0x20,
-            (byte) 0x32, (byte) 0x37, (byte) 0x2D, (byte) 0x53,
-            (byte) 0x65, (byte) 0x70, (byte) 0x2D, (byte) 0x39,
-            (byte) 0x33, (byte) 0x20, (byte) 0x62, (byte) 0x79,
-            (byte) 0x20, (byte) 0x41, (byte) 0x4C, (byte) 0x4C,
-            (byte) 0x57, (byte) 0x4F, (byte) 0x52
-        };
-
-
-        NameRecord name = new NameRecord(TestcaseRecordInputStream.create(NameRecord.sid, examples));
-        String description = name.getDescriptionText();
-        assertNotNull( description );
-        assertTrue( "text contains ALLWOR", description.indexOf( "ALLWOR" ) > 0 );
-    }
+       /**
+        * Makes sure that additional name information is parsed properly such as menu/description
+        */
+       public void testFillExtras() {
+
+               byte[] examples = HexRead.readFromString(""
+                               + "88 03 67 06 07 00 00 00 00 00 00 23 00 00 00 4D "
+                               + "61 63 72 6F 31 3A 01 00 00 00 11 00 00 4D 61 63 "
+                               + "72 6F 20 72 65 63 6F 72 64 65 64 20 32 37 2D 53 "
+                               + "65 70 2D 39 33 20 62 79 20 41 4C 4C 57 4F 52");
+
+               NameRecord name = new NameRecord(TestcaseRecordInputStream.create(NameRecord.sid, examples));
+               String description = name.getDescriptionText();
+               assertNotNull(description);
+               assertTrue(description.endsWith("Macro recorded 27-Sep-93 by ALLWOR"));
+       }
+
+       public void testReserialize() {
+               byte[] data = HexRead
+                               .readFromString(""
+                                               + "20 00 00 01 0B 00 00 00 01 00 00 00 00 00 00 06 3B 00 00 00 00 02 00 00 00 09 00]");
+               RecordInputStream in = TestcaseRecordInputStream.create(NameRecord.sid, data);
+               NameRecord nr = new NameRecord(in);
+               assertEquals(0x0020, nr.getOptionFlag());
+               byte[] data2 = nr.serialize();
+               TestcaseRecordInputStream.confirmRecordEncoding(NameRecord.sid, data, data2);
+       }
 }
-
-
index 7dea16b3084f55970af374e49f9ead4f5cf87685..8177d3ed03ecd7ce4b02f77081099eb5c52d9cc3 100644 (file)
@@ -21,8 +21,15 @@ import junit.framework.AssertionFailedError;
 import junit.framework.ComparisonFailure;
 import junit.framework.TestCase;
 
+import org.apache.poi.hssf.HSSFTestDataSamples;
 import org.apache.poi.hssf.record.formula.Ptg;
 import org.apache.poi.hssf.record.formula.RefPtg;
+import org.apache.poi.hssf.usermodel.HSSFCell;
+import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator;
+import org.apache.poi.hssf.usermodel.HSSFSheet;
+import org.apache.poi.hssf.usermodel.HSSFWorkbook;
+import org.apache.poi.hssf.usermodel.RecordInspector;
+import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator.CellValue;
 import org.apache.poi.util.LittleEndianInput;
 
 /**
@@ -30,6 +37,10 @@ import org.apache.poi.util.LittleEndianInput;
  */
 public final class TestSharedFormulaRecord extends TestCase {
 
+       /**
+        * A sample spreadsheet known to have one sheet with 4 shared formula ranges
+        */
+       private static final String SHARED_FORMULA_TEST_XLS = "SharedFormulaTest.xls";
        /**
         * Binary data for an encoded formula.  Taken from attachment 22062 (bugzilla 45123/45421).
         * The shared formula is in Sheet1!C6:C21, with text "SUMPRODUCT(--(End_Acct=$C6),--(End_Bal))"
@@ -86,4 +97,113 @@ public final class TestSharedFormulaRecord extends TestCase {
                        }
                }
        }
+       
+       /**
+        * Make sure that POI preserves {@link SharedFormulaRecord}s
+        */
+       public void testPreserveOnReserialize() {
+               HSSFWorkbook wb;
+               HSSFSheet sheet;
+               HSSFCell cellB32769;
+               HSSFCell cellC32769;
+               
+               // Reading directly from XLS file
+               wb = HSSFTestDataSamples.openSampleWorkbook(SHARED_FORMULA_TEST_XLS);
+               sheet = wb.getSheetAt(0);
+               cellB32769 = sheet.getRow(32768).getCell(1);
+               cellC32769 = sheet.getRow(32768).getCell(2);
+               // check reading of formulas which are shared (two cells from a 1R x 8C range) 
+               assertEquals("B32770*2", cellB32769.getCellFormula()); 
+               assertEquals("C32770*2", cellC32769.getCellFormula());
+               confirmCellEvaluation(wb, cellB32769, 4);
+               confirmCellEvaluation(wb, cellC32769, 6);
+               // Confirm this example really does have SharedFormulas.
+               // there are 3 others besides the one at A32769:H32769
+               assertEquals(4, countSharedFormulas(sheet)); 
+               
+               
+               // Re-serialize and check again
+               wb = HSSFTestDataSamples.writeOutAndReadBack(wb);
+               sheet = wb.getSheetAt(0);
+               cellB32769 = sheet.getRow(32768).getCell(1);
+               cellC32769 = sheet.getRow(32768).getCell(2);
+               assertEquals("B32770*2", cellB32769.getCellFormula());
+               confirmCellEvaluation(wb, cellB32769, 4);
+               assertEquals(4, countSharedFormulas(sheet));
+       }
+       
+       public void testUnshareFormulaDueToChangeFormula() {
+               HSSFWorkbook wb;
+               HSSFSheet sheet;
+               HSSFCell cellB32769;
+               HSSFCell cellC32769;
+               
+               wb = HSSFTestDataSamples.openSampleWorkbook(SHARED_FORMULA_TEST_XLS);
+               sheet = wb.getSheetAt(0);
+               cellB32769 = sheet.getRow(32768).getCell(1);
+               cellC32769 = sheet.getRow(32768).getCell(2);
+               
+               // Updating cell formula, causing it to become unshared
+               cellB32769.setCellFormula("1+1");
+               confirmCellEvaluation(wb, cellB32769, 2);
+               // currently (Oct 2008) POI handles this by exploding the whole shared formula group
+               assertEquals(3, countSharedFormulas(sheet)); // one less now
+               // check that nearby cell of the same group still has the same formula
+               assertEquals("C32770*2", cellC32769.getCellFormula());
+               confirmCellEvaluation(wb, cellC32769, 6);
+       }
+       public void testUnshareFormulaDueToDelete() {
+               HSSFWorkbook wb;
+               HSSFSheet sheet;
+               HSSFCell cell;
+               final int ROW_IX = 2;
+
+               // changing shared formula cell to blank
+               wb = HSSFTestDataSamples.openSampleWorkbook(SHARED_FORMULA_TEST_XLS);
+               sheet = wb.getSheetAt(0);
+       
+               assertEquals("A$1*2", sheet.getRow(ROW_IX).getCell(1).getCellFormula());
+               cell = sheet.getRow(ROW_IX).getCell(1);
+               cell.setCellType(HSSFCell.CELL_TYPE_BLANK);
+               assertEquals(3, countSharedFormulas(sheet));
+               
+               wb = HSSFTestDataSamples.writeOutAndReadBack(wb);
+               sheet = wb.getSheetAt(0);
+               assertEquals("A$1*2", sheet.getRow(ROW_IX+1).getCell(1).getCellFormula());
+               
+               // deleting shared formula cell
+               wb = HSSFTestDataSamples.openSampleWorkbook(SHARED_FORMULA_TEST_XLS);
+               sheet = wb.getSheetAt(0);
+       
+               assertEquals("A$1*2", sheet.getRow(ROW_IX).getCell(1).getCellFormula());
+               cell = sheet.getRow(ROW_IX).getCell(1);
+               sheet.getRow(ROW_IX).removeCell(cell);
+               assertEquals(3, countSharedFormulas(sheet));
+               
+               wb = HSSFTestDataSamples.writeOutAndReadBack(wb);
+               sheet = wb.getSheetAt(0);
+               assertEquals("A$1*2", sheet.getRow(ROW_IX+1).getCell(1).getCellFormula());
+       }
+
+       private static void confirmCellEvaluation(HSSFWorkbook wb, HSSFCell cell, double expectedValue) {
+               HSSFFormulaEvaluator fe = new HSSFFormulaEvaluator(wb);
+               CellValue cv = fe.evaluate(cell);
+               assertEquals(HSSFCell.CELL_TYPE_NUMERIC, cv.getCellType());
+               assertEquals(expectedValue, cv.getNumberValue(), 0.0);
+       }
+
+       /**
+        * @return the number of {@link SharedFormulaRecord}s encoded for the specified sheet
+        */
+       private static int countSharedFormulas(HSSFSheet sheet) {
+               Record[] records = RecordInspector.getRecords(sheet, 0);
+               int count = 0;
+               for (int i = 0; i < records.length; i++) {
+                       Record rec = records[i];
+                       if(rec instanceof SharedFormulaRecord) {
+                               count++;
+                       }
+               }
+               return count;
+       }
 }