From 8685d30fd5f66e66b72234482c930db49c76d15d Mon Sep 17 00:00:00 2001 From: Josh Micich Date: Thu, 28 Aug 2008 20:39:41 +0000 Subject: [PATCH] Consolidated TableRecord inside FormulaRecordAggregate. Simplifications to FormulaRecord git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@689973 13f79535-47bb-0310-9956-ffa450edef68 --- .../apache/poi/hssf/model/RecordStream.java | 22 +- .../apache/poi/hssf/record/FormulaRecord.java | 372 +++++------------- .../poi/hssf/record/SharedFormulaRecord.java | 62 +-- .../aggregates/FormulaRecordAggregate.java | 72 ++-- .../aggregates/RowRecordsAggregate.java | 2 +- .../aggregates/ValueRecordsAggregate.java | 82 ++-- .../poi/hssf/record/formula/ArrayPtg.java | 17 +- .../apache/poi/hssf/record/formula/Ptg.java | 28 +- .../apache/poi/hssf/usermodel/HSSFCell.java | 48 +-- src/java/org/apache/poi/util/HexDump.java | 80 +++- .../TestFormulaRecordAggregate.java | 3 +- .../poi/hssf/usermodel/TestBug42464.java | 2 +- .../org/apache/poi/util/TestHexDump.java | 88 ++--- 13 files changed, 370 insertions(+), 508 deletions(-) diff --git a/src/java/org/apache/poi/hssf/model/RecordStream.java b/src/java/org/apache/poi/hssf/model/RecordStream.java index 1a06873954..8869a9cf03 100755 --- a/src/java/org/apache/poi/hssf/model/RecordStream.java +++ b/src/java/org/apache/poi/hssf/model/RecordStream.java @@ -30,19 +30,28 @@ public final class RecordStream { private final List _list; private int _nextIndex; private int _countRead; + private final int _endIx; - public RecordStream(List inputList, int startIndex) { + /** + * Creates a RecordStream bounded by startIndex and endIndex + */ + public RecordStream(List inputList, int startIndex, int endIx) { _list = inputList; _nextIndex = startIndex; + _endIx = endIx; _countRead = 0; } + public RecordStream(List records, int startIx) { + this(records, startIx, records.size()); + } + public boolean hasNext() { - return _nextIndex < _list.size(); + return _nextIndex < _endIx; } public Record getNext() { - if(_nextIndex >= _list.size()) { + if(!hasNext()) { throw new RuntimeException("Attempt to read past end of record stream"); } _countRead ++; @@ -53,14 +62,17 @@ public final class RecordStream { * @return the {@link Class} of the next Record. null if this stream is exhausted. */ public Class peekNextClass() { - if(_nextIndex >= _list.size()) { + if(!hasNext()) { return null; } return _list.get(_nextIndex).getClass(); } + /** + * @return -1 if at end of records + */ public int peekNextSid() { - if(_nextIndex >= _list.size()) { + if(!hasNext()) { return -1; } return ((Record)_list.get(_nextIndex)).getSid(); diff --git a/src/java/org/apache/poi/hssf/record/FormulaRecord.java b/src/java/org/apache/poi/hssf/record/FormulaRecord.java index 9f6bb45866..1074c3c21e 100644 --- a/src/java/org/apache/poi/hssf/record/FormulaRecord.java +++ b/src/java/org/apache/poi/hssf/record/FormulaRecord.java @@ -17,12 +17,13 @@ package org.apache.poi.hssf.record; +import java.util.Arrays; import java.util.List; -import java.util.Stack; import org.apache.poi.hssf.record.formula.Ptg; import org.apache.poi.util.BitField; import org.apache.poi.util.BitFieldFactory; +import org.apache.poi.util.HexDump; import org.apache.poi.util.LittleEndian; /** @@ -33,69 +34,60 @@ import org.apache.poi.util.LittleEndian; * @version 2.0-pre */ 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 final BitField alwaysCalc = BitFieldFactory.getInstance(0x0001); private static final BitField calcOnLoad = BitFieldFactory.getInstance(0x0002); - private static final BitField sharedFormula = BitFieldFactory.getInstance(0x0008); + private static final BitField sharedFormula = BitFieldFactory.getInstance(0x0008); - private int field_1_row; + private int field_1_row; private short field_2_column; private short field_3_xf; private double field_4_value; private short field_5_options; private int field_6_zero; - private short field_7_expression_len; - private Stack field_8_parsed_expr; - + private Ptg[] field_8_parsed_expr; + /** * Since the NaN support seems sketchy (different constants) we'll store and spit it out directly */ - private byte[] value_data; - private byte[] all_data; //if formula support is not enabled then - //we'll just store/reserialize + private byte[] value_data; /** Creates new FormulaRecord */ - public FormulaRecord() - { - field_8_parsed_expr = new Stack(); + public FormulaRecord() { + field_8_parsed_expr = Ptg.EMPTY_PTG_ARRAY; } /** * Constructs a Formula record and sets its fields appropriately. - * Note - id must be 0x06 (NOT 0x406 see MSKB #Q184647 for an + * Note - id must be 0x06 (NOT 0x406 see MSKB #Q184647 for an * "explanation of this bug in the documentation) or an exception * will be throw upon validation * * @param in the RecordInputstream to read the record from */ - public FormulaRecord(RecordInputStream in) - { + public FormulaRecord(RecordInputStream in) { super(in); } - protected void fillFields(RecordInputStream in) - { - try { + protected void fillFields(RecordInputStream in) { field_1_row = in.readUShort(); field_2_column = in.readShort(); field_3_xf = in.readShort(); field_4_value = in.readDouble(); field_5_options = in.readShort(); - + if (Double.isNaN(field_4_value)) { value_data = in.getNANData(); } - + field_6_zero = in.readInt(); - field_7_expression_len = in.readShort(); - field_8_parsed_expr = Ptg.createParsedExpressionTokens(field_7_expression_len, in); - } catch (java.lang.UnsupportedOperationException uoe) { - throw new RecordFormatException(uoe); - } + 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 @@ -103,19 +95,15 @@ public final class FormulaRecord extends Record implements CellValueRecordInterf } } - //public void setRow(short row) - public void setRow(int row) - { + public void setRow(int row) { field_1_row = row; } - public void setColumn(short column) - { + public void setColumn(short column) { field_2_column = column; } - public void setXFIndex(short xf) - { + public void setXFIndex(short xf) { field_3_xf = xf; } @@ -124,9 +112,7 @@ public final class FormulaRecord extends Record implements CellValueRecordInterf * * @param value calculated value */ - - public void setValue(double value) - { + public void setValue(double value) { field_4_value = value; } @@ -135,35 +121,19 @@ public final class FormulaRecord extends Record implements CellValueRecordInterf * * @param options bitmask */ - - public void setOptions(short options) - { + public void setOptions(short options) { field_5_options = options; } - /** - * set the length (in number of tokens) of the expression - * @param len length - */ - - public void setExpressionLength(short len) - { - field_7_expression_len = len; - } - - //public short getRow() - public int getRow() - { + public int getRow() { return field_1_row; } - public short getColumn() - { + public short getColumn() { return field_2_column; } - public short getXFIndex() - { + public short getXFIndex() { return field_3_xf; } @@ -172,8 +142,7 @@ public final class FormulaRecord extends Record implements CellValueRecordInterf * * @return calculated value */ - public double getValue() - { + public double getValue() { return field_4_value; } @@ -182,108 +151,51 @@ public final class FormulaRecord extends Record implements CellValueRecordInterf * * @return bitmask */ - public short getOptions() - { + public short getOptions() { return field_5_options; - } - + } + public boolean isSharedFormula() { return sharedFormula.isSet(field_5_options); } public void setSharedFormula(boolean flag) { - field_5_options = - sharedFormula.setShortBoolean(field_5_options, flag); + field_5_options = + sharedFormula.setShortBoolean(field_5_options, flag); } - + public boolean isAlwaysCalc() { - return alwaysCalc.isSet(field_5_options); + return alwaysCalc.isSet(field_5_options); } public void setAlwaysCalc(boolean flag) { - field_5_options = - alwaysCalc.setShortBoolean(field_5_options, flag); + field_5_options = + alwaysCalc.setShortBoolean(field_5_options, flag); } - + public boolean isCalcOnLoad() { - return calcOnLoad.isSet(field_5_options); + return calcOnLoad.isSet(field_5_options); } public void setCalcOnLoad(boolean flag) { - field_5_options = - calcOnLoad.setShortBoolean(field_5_options, flag); - } - - /** - * get the length (in number of tokens) of the expression - * @return expression length - */ - - public short getExpressionLength() - { - return field_7_expression_len; - } - - /** - * push a token onto the stack - * - * @param ptg the token - */ - - public void pushExpressionToken(Ptg ptg) - { - field_8_parsed_expr.push(ptg); - } - - /** - * pop a token off of the stack - * - * @return Ptg - the token - */ - - public Ptg popExpressionToken() - { - return ( Ptg ) field_8_parsed_expr.pop(); - } - - /** - * peek at the token on the top of stack - * - * @return Ptg - the token - */ - - public Ptg peekExpressionToken() - { - return ( Ptg ) field_8_parsed_expr.peek(); + field_5_options = + calcOnLoad.setShortBoolean(field_5_options, flag); } /** * get the size of the stack * @return size of the stack */ - - public int getNumberOfExpressionTokens() - { - if (this.field_8_parsed_expr == null) { - return 0; - } else { - return field_8_parsed_expr.size(); - } + public int getNumberOfExpressionTokens() { + return field_8_parsed_expr.length; } /** - * get the stack 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 list of formula tokens. never null */ - - public List getParsedExpression() - { - return field_8_parsed_expr; + public List getParsedExpression() { + return Arrays.asList(field_8_parsed_expr); // TODO - return array } - - public void setParsedExpression(Stack ptgs) { - field_8_parsed_expr = ptgs; + + public void setParsedExpression(Ptg[] ptgs) { + field_8_parsed_expr = ptgs; } /** @@ -292,156 +204,86 @@ public final class FormulaRecord extends Record implements CellValueRecordInterf * * @param id alleged id for this record */ - - protected void validateSid(short id) - { - if (id != sid) - { + protected void validateSid(short id) { + if (id != sid) { throw new RecordFormatException("NOT A FORMULA RECORD"); } } - public short getSid() - { + public short getSid() { return sid; } - /** - * called by the class that is responsible for writing this sucker. - * Subclasses should implement this so that their data is passed back in a - * byte array. - * - * @return byte array containing instance data - */ + private int getDataSize() { + return FIXED_SIZE + Ptg.getEncodedSize(field_8_parsed_expr); + } + public int serialize(int offset, byte [] data) { - public int serialize(int offset, byte [] data) - { - if (this.field_8_parsed_expr != null) { - int ptgSize = getTotalPtgSize(); + int dataSize = getDataSize(); LittleEndian.putShort(data, 0 + offset, sid); - LittleEndian.putShort(data, 2 + offset, ( short ) (22 + ptgSize)); - //LittleEndian.putShort(data, 4 + offset, getRow()); - LittleEndian.putShort(data, 4 + offset, ( short ) getRow()); + LittleEndian.putUShort(data, 2 + offset, dataSize); + LittleEndian.putUShort(data, 4 + offset, getRow()); LittleEndian.putShort(data, 6 + offset, getColumn()); LittleEndian.putShort(data, 8 + offset, getXFIndex()); - + //only reserialize if the value is still NaN and we have old nan data - if (Double.isNaN(this.getValue()) && value_data != null) { - System.arraycopy(value_data,0,data,10 + offset,value_data.length); + if (Double.isNaN(getValue()) && value_data != null) { + System.arraycopy(value_data,0,data,10 + offset,value_data.length); } else { - LittleEndian.putDouble(data, 10 + offset, field_4_value); + LittleEndian.putDouble(data, 10 + offset, field_4_value); } - + LittleEndian.putShort(data, 18 + offset, 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); - LittleEndian.putShort(data, 24 + offset, getExpressionLength()); - Ptg.serializePtgStack(field_8_parsed_expr, data, 26+offset); - } else { - System.arraycopy(all_data,0,data,offset,all_data.length); - } - return getRecordSize(); + 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; } - - - - - public int getRecordSize() - { - int retval =0; - - if (this.field_8_parsed_expr != null) { - retval = getTotalPtgSize() + 26; - } else { - retval =all_data.length; - } - return retval; - // return getTotalPtgSize() + 28; - } - - private int getTotalPtgSize() - { - List list = getParsedExpression(); - int retval = 0; - - for (int k = 0; k < list.size(); k++) - { - Ptg ptg = ( Ptg ) list.get(k); - - retval += ptg.getSize(); - } - return retval; + public int getRecordSize() { + return 4 + getDataSize(); } - public boolean isInValueSection() - { + public boolean isInValueSection() { return true; } - public boolean isValue() - { + public boolean isValue() { return true; } - - public String toString() - { - StringBuffer buffer = new StringBuffer(); - buffer.append("[FORMULA]\n"); - buffer.append(" .row = ") - .append(Integer.toHexString(getRow())).append("\n"); - buffer.append(" .column = ") - .append(Integer.toHexString(getColumn())) - .append("\n"); - buffer.append(" .xf = ") - .append(Integer.toHexString(getXFIndex())).append("\n"); - if (Double.isNaN(this.getValue()) && value_data != null) - buffer.append(" .value (NaN) = ") - .append(org.apache.poi.util.HexDump.dump(value_data,0,0)) - .append("\n"); - else - buffer.append(" .value = ").append(getValue()) - .append("\n"); - buffer.append(" .options = ").append(getOptions()) - .append("\n"); - buffer.append(" .alwaysCalc = ").append(alwaysCalc.isSet(getOptions())) - .append("\n"); - buffer.append(" .calcOnLoad = ").append(calcOnLoad.isSet(getOptions())) - .append("\n"); - buffer.append(" .sharedFormula = ").append(sharedFormula.isSet(getOptions())) - .append("\n"); - buffer.append(" .zero = ").append(field_6_zero) - .append("\n"); - buffer.append(" .expressionlength= ").append(getExpressionLength()) - .append("\n"); - - if (field_8_parsed_expr != null) { - buffer.append(" .numptgsinarray = ").append(field_8_parsed_expr.size()) - .append("\n"); - - - for (int k = 0; k < field_8_parsed_expr.size(); k++ ) { - buffer.append(" Ptg(") - .append(k) - .append(")=") - .append(field_8_parsed_expr.get(k).toString()) - .append("\n") - .append(((Ptg)field_8_parsed_expr.get(k)).toDebugString()) - .append("\n"); - } - }else { - buffer.append("Formula full data \n") - .append(org.apache.poi.util.HexDump.dump(this.all_data,0,0)); - } - - - buffer.append("[/FORMULA]\n"); - return buffer.toString(); + + public String toString() { + + 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 = "); + if (Double.isNaN(this.getValue()) && value_data != null) { + sb.append("(NaN)").append(HexDump.dump(value_data,0,0)).append("\n"); + } else { + sb.append(getValue()).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("]="); + sb.append(field_8_parsed_expr[k].toString()).append("\n"); + } + sb.append("[/FORMULA]\n"); + return sb.toString(); } - + public Object clone() { FormulaRecord rec = new FormulaRecord(); rec.field_1_row = field_1_row; @@ -450,18 +292,14 @@ 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; - rec.field_7_expression_len = field_7_expression_len; - rec.field_8_parsed_expr = new Stack(); - int size = 0; - if (field_8_parsed_expr != null) - size = field_8_parsed_expr.size(); - for (int i=0; i< size; i++) { - Ptg ptg = ((Ptg)field_8_parsed_expr.get(i)).copy(); - rec.field_8_parsed_expr.add(i, ptg); + 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.value_data = value_data; - rec.all_data = all_data; return rec; } - } + diff --git a/src/java/org/apache/poi/hssf/record/SharedFormulaRecord.java b/src/java/org/apache/poi/hssf/record/SharedFormulaRecord.java index a8aeed0dae..3f3a047e64 100755 --- a/src/java/org/apache/poi/hssf/record/SharedFormulaRecord.java +++ b/src/java/org/apache/poi/hssf/record/SharedFormulaRecord.java @@ -14,17 +14,22 @@ See the License for the specific language governing permissions and limitations under the License. ==================================================================== */ - + package org.apache.poi.hssf.record; +import java.util.List; import java.util.Stack; -import org.apache.poi.hssf.record.formula.*; +import org.apache.poi.hssf.record.formula.AreaNPtg; +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; /** * Title: SharedFormulaRecord * Description: Primarily used as an excel optimization so that multiple similar formulas - * are not written out too many times. We should recognize this record and + * are not written out too many times. We should recognize this record and * serialize as is since this is used when reading templates. *

* Note: the documentation says that the SID is BC where biffviewer reports 4BC. The hex dump shows @@ -33,15 +38,15 @@ import org.apache.poi.hssf.record.formula.*; * @author Danny Mui at apache dot org */ public final class SharedFormulaRecord extends Record { - public final static short sid = 0x4BC; - + public final static short sid = 0x04BC; + private int field_1_first_row; private int field_2_last_row; private short field_3_first_column; private short field_4_last_column; private int field_5_reserved; private short field_6_expression_len; - private Stack field_7_parsed_expr; + private Stack field_7_parsed_expr; public SharedFormulaRecord() { @@ -55,15 +60,15 @@ public final class SharedFormulaRecord extends Record { { super(in); } - + protected void validateSid(short id) { if (id != this.sid) { throw new RecordFormatException("Not a valid SharedFormula"); - } - } - + } + } + public int getFirstRow() { return field_1_first_row; } @@ -139,7 +144,7 @@ public final class SharedFormulaRecord extends Record { .append(field_7_parsed_expr.get(k).toString()) .append("\n"); } - + buffer.append("[/SHARED FORMULA RECORD]\n"); return buffer.toString(); } @@ -163,7 +168,7 @@ public final class SharedFormulaRecord extends Record { private Stack getParsedExpressionTokens(RecordInputStream in) { Stack stack = new Stack(); - + while (in.remaining() != 0) { Ptg ptg = Ptg.createPtg(in); stack.push(ptg); @@ -180,15 +185,15 @@ public final class SharedFormulaRecord extends Record { return ((getFirstRow() <= formulaRow) && (getLastRow() >= formulaRow) && (getFirstColumn() <= formulaColumn) && (getLastColumn() >= formulaColumn)); } - + /** - * Creates a non shared formula from the shared formula + * Creates a non shared formula from the shared formula * counter part */ protected static Stack convertSharedFormulas(Stack ptgs, int formulaRow, int formulaColumn) { if(false) { /* - * TODO - (May-2008) Stop converting relative ref Ptgs in shared formula records. + * 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. @@ -225,31 +230,30 @@ public final class SharedFormulaRecord extends Record { if (!ptg.isBaseToken()) { ptg.setClass(originalOperandClass); } - + newPtgStack.add(ptg); } return newPtgStack; } - /** - * Creates a non shared formula from the shared formula + /** + * Creates a non shared formula from the shared formula * counter part */ public void convertSharedFormulaRecord(FormulaRecord formula) { //Sanity checks - final int formulaRow = formula.getRow(); - final int formulaColumn = formula.getColumn(); - if (isFormulaInShared(formula)) { - formula.setExpressionLength(getExpressionLength()); - - Stack newPtgStack = - convertSharedFormulas(field_7_parsed_expr, formulaRow, formulaColumn); - formula.setParsedExpression(newPtgStack); + if (!isFormulaInShared(formula)) { + throw new RuntimeException("Shared Formula Conversion: Coding Error"); + } + final int formulaRow = formula.getRow(); + final int formulaColumn = formula.getColumn(); + + List ptgList = convertSharedFormulas(field_7_parsed_expr, formulaRow, formulaColumn); + Ptg[] ptgs = new Ptg[ptgList.size()]; + ptgList.toArray(ptgs); + formula.setParsedExpression(ptgs); //Now its not shared! formula.setSharedFormula(false); - } else { - throw new RuntimeException("Shared Formula Conversion: Coding Error"); - } } private static int fixupRelativeColumn(int currentcolumn, int column, boolean relative) { diff --git a/src/java/org/apache/poi/hssf/record/aggregates/FormulaRecordAggregate.java b/src/java/org/apache/poi/hssf/record/aggregates/FormulaRecordAggregate.java index 3359ca55a4..393f1c0c05 100644 --- a/src/java/org/apache/poi/hssf/record/aggregates/FormulaRecordAggregate.java +++ b/src/java/org/apache/poi/hssf/record/aggregates/FormulaRecordAggregate.java @@ -17,9 +17,12 @@ package org.apache.poi.hssf.record.aggregates; +import org.apache.poi.hssf.model.RecordStream; import org.apache.poi.hssf.record.CellValueRecordInterface; import org.apache.poi.hssf.record.FormulaRecord; +import org.apache.poi.hssf.record.SharedFormulaRecord; import org.apache.poi.hssf.record.StringRecord; +import org.apache.poi.hssf.record.TableRecord; /** * The formula record aggregate is used to join together the formula record and it's @@ -29,61 +32,67 @@ import org.apache.poi.hssf.record.StringRecord; */ public final class FormulaRecordAggregate extends RecordAggregate implements CellValueRecordInterface { - private FormulaRecord _formulaRecord; + private final FormulaRecord _formulaRecord; + /** caches the calculated result of the formula */ private StringRecord _stringRecord; + private TableRecord _tableRecord; - public FormulaRecordAggregate( FormulaRecord formulaRecord, StringRecord stringRecord ) - { + public FormulaRecordAggregate(FormulaRecord formulaRecord) { _formulaRecord = formulaRecord; - _stringRecord = stringRecord; + _stringRecord = null; } - - public void setStringRecord( StringRecord stringRecord ) { - _stringRecord = stringRecord; + public FormulaRecordAggregate(FormulaRecord formulaRecord, RecordStream rs) { + _formulaRecord = formulaRecord; + Class nextClass = rs.peekNextClass(); + if (nextClass == SharedFormulaRecord.class) { + // For (text) shared formulas, the SharedFormulaRecord comes before the StringRecord. + // In any case it is OK to skip SharedFormulaRecords because they were collected + // before constructing the ValueRecordsAggregate. + rs.getNext(); // skip the shared formula record + nextClass = rs.peekNextClass(); + } + if (nextClass == StringRecord.class) { + _stringRecord = (StringRecord) rs.getNext(); + } else if (nextClass == TableRecord.class) { + _tableRecord = (TableRecord) rs.getNext(); + } } - public void setFormulaRecord( FormulaRecord formulaRecord ) - { - _formulaRecord = formulaRecord; + public void setStringRecord(StringRecord stringRecord) { + _stringRecord = stringRecord; + _tableRecord = null; // probably can't have both present at the same time + // TODO - establish rules governing when each of these sub records may exist } - public FormulaRecord getFormulaRecord() - { + public FormulaRecord getFormulaRecord() { return _formulaRecord; } - public StringRecord getStringRecord() - { + public StringRecord getStringRecord() { return _stringRecord; } - public short getXFIndex() - { + public short getXFIndex() { return _formulaRecord.getXFIndex(); } - public void setXFIndex(short xf) - { - _formulaRecord.setXFIndex( xf ); + public void setXFIndex(short xf) { + _formulaRecord.setXFIndex(xf); } - public void setColumn(short col) - { - _formulaRecord.setColumn( col ); + public void setColumn(short col) { + _formulaRecord.setColumn(col); } - public void setRow(int row) - { - _formulaRecord.setRow( row ); + public void setRow(int row) { + _formulaRecord.setRow(row); } - public short getColumn() - { + public short getColumn() { return _formulaRecord.getColumn(); } - public int getRow() - { + public int getRow() { return _formulaRecord.getRow(); } @@ -94,8 +103,11 @@ public final class FormulaRecordAggregate extends RecordAggregate implements Cel public void visitContainedRecords(RecordVisitor rv) { rv.visitRecord(_formulaRecord); if (_stringRecord != null) { - rv.visitRecord(_stringRecord); + rv.visitRecord(_stringRecord); } + if (_tableRecord != null) { + rv.visitRecord(_tableRecord); + } } public String getStringValue() { diff --git a/src/java/org/apache/poi/hssf/record/aggregates/RowRecordsAggregate.java b/src/java/org/apache/poi/hssf/record/aggregates/RowRecordsAggregate.java index d839ecfab6..fcbc89f63b 100644 --- a/src/java/org/apache/poi/hssf/record/aggregates/RowRecordsAggregate.java +++ b/src/java/org/apache/poi/hssf/record/aggregates/RowRecordsAggregate.java @@ -82,7 +82,7 @@ public final class RowRecordsAggregate extends RecordAggregate { if (!rec.isValue()) { throw new RuntimeException("Unexpected record type (" + rec.getClass().getName() + ")"); } - i += _valuesAgg.construct(recs, i, endIx, sfh); + i += _valuesAgg.construct(recs, i, endIx, sfh)-1; } "".length(); } diff --git a/src/java/org/apache/poi/hssf/record/aggregates/ValueRecordsAggregate.java b/src/java/org/apache/poi/hssf/record/aggregates/ValueRecordsAggregate.java index 0db1201432..886bb617d5 100644 --- a/src/java/org/apache/poi/hssf/record/aggregates/ValueRecordsAggregate.java +++ b/src/java/org/apache/poi/hssf/record/aggregates/ValueRecordsAggregate.java @@ -21,6 +21,7 @@ import java.util.ArrayList; import java.util.Iterator; import java.util.List; +import org.apache.poi.hssf.model.RecordStream; import org.apache.poi.hssf.record.CellValueRecordInterface; import org.apache.poi.hssf.record.DBCellRecord; import org.apache.poi.hssf.record.FormulaRecord; @@ -111,12 +112,12 @@ public final class ValueRecordsAggregate { public void removeAllCellsValuesForRow(int rowIndex) { if (rowIndex >= records.length) { - throw new IllegalArgumentException("Specified rowIndex " + rowIndex + throw new IllegalArgumentException("Specified rowIndex " + rowIndex + " is outside the allowable range (0.." +records.length + ")"); } records[rowIndex] = null; } - + public int getPhysicalNumberOfCells() { @@ -142,62 +143,48 @@ public final class ValueRecordsAggregate { } /** - * Processes a sequential group of cell value records. Stops at endIx or the first + * Processes a sequential group of cell value records. Stops at endIx or the first * non-value record encountered. * @param sfh used to resolve any shared formulas for the current sheet * @return the number of records consumed */ public int construct(List records, int offset, int endIx, SharedFormulaHolder sfh) { - int k = 0; + RecordStream rs = new RecordStream(records, offset, endIx); - FormulaRecordAggregate lastFormulaAggregate = null; - // Now do the main processing sweep - for (k = offset; k < endIx; k++) { - Record rec = ( Record ) records.get(k); - - if (rec instanceof StringRecord) { - if (lastFormulaAggregate == null) { - throw new RuntimeException("StringRecord found without preceding FormulaRecord"); - } - if (lastFormulaAggregate.getStringRecord() != null) { - throw new RuntimeException("Multiple StringRecords found after FormulaRecord"); - } - lastFormulaAggregate.setStringRecord((StringRecord)rec); - lastFormulaAggregate = null; - continue; - } - - if (rec instanceof TableRecord) { - // TODO - don't loose this record - // DATATABLE probably belongs in formula record aggregate - if (lastFormulaAggregate == null) { - throw new RuntimeException("No preceding formula record found"); - } - lastFormulaAggregate = null; - continue; + while (rs.hasNext()) { + Class recClass = rs.peekNextClass(); + if (recClass == StringRecord.class) { + throw new RuntimeException("Loose StringRecord found without preceding FormulaRecord"); } - - if (rec instanceof SharedFormulaRecord) { - // Already handled, not to worry - continue; + + if (recClass == TableRecord.class) { + throw new RuntimeException("Loose TableRecord found without preceding FormulaRecord"); } - if (rec instanceof UnknownRecord) { + if (recClass == UnknownRecord.class) { break; } - if (rec instanceof RowRecord) { - break; + if (recClass == RowRecord.class) { + break; } - if (rec instanceof DBCellRecord) { + if (recClass == DBCellRecord.class) { // end of 'Row Block'. This record is ignored by POI break; } - if (rec instanceof MergeCellsRecord) { + + Record rec = rs.getNext(); + + if (recClass == SharedFormulaRecord.class) { + // Already handled, not to worry + continue; + } + if (recClass == MergeCellsRecord.class) { // doesn't really belong here // can safely be ignored, because it has been processed in a higher method continue; } + if (!rec.isValue()) { throw new RuntimeException("bad record type"); } @@ -206,14 +193,13 @@ public final class ValueRecordsAggregate { if (formula.isSharedFormula()) { sfh.convertSharedFormulaRecord(formula); } - - lastFormulaAggregate = new FormulaRecordAggregate((FormulaRecord)rec, null); - insertCell( lastFormulaAggregate ); + + insertCell(new FormulaRecordAggregate((FormulaRecord)rec, rs)); continue; } insertCell(( CellValueRecordInterface ) rec); } - return k - offset - 1; + return rs.getCountRead(); } /** Tallies a count of the size of the cell records @@ -235,7 +221,7 @@ public final class ValueRecordsAggregate { /** Returns true if the row has cells attached to it */ public boolean rowHasCells(int row) { - if (row > records.length-1) //previously this said row > records.length which means if + if (row > records.length-1) //previously this said row > records.length which means if return false; // if records.length == 60 and I pass "60" here I get array out of bounds CellValueRecordInterface[] rowCells=records[row]; //because a 60 length array has the last index = 59 if(rowCells==null) return false; @@ -260,7 +246,7 @@ public final class ValueRecordsAggregate { } return pos - offset; } - + public int visitCellsForRow(int rowIndex, RecordVisitor rv) { int result = 0; CellValueRecordInterface[] cellRecs = records[rowIndex]; @@ -292,7 +278,7 @@ public final class ValueRecordsAggregate { public CellValueRecordInterface[] getValueRecords() { List temp = new ArrayList(); - + for (int i = 0; i < records.length; i++) { CellValueRecordInterface[] rowCells = records[i]; if (rowCells == null) { @@ -305,7 +291,7 @@ public final class ValueRecordsAggregate { } } } - + CellValueRecordInterface[] result = new CellValueRecordInterface[temp.size()]; temp.toArray(result); return result; @@ -314,7 +300,7 @@ public final class ValueRecordsAggregate { { return new MyIterator(); } - + private final class MyIterator implements Iterator { short nextColumn=-1; int nextRow,lastRow; @@ -325,7 +311,7 @@ public final class ValueRecordsAggregate { this.lastRow=records.length-1; findNext(); } - + public MyIterator(int firstRow,int lastRow) { this.nextRow=firstRow; diff --git a/src/java/org/apache/poi/hssf/record/formula/ArrayPtg.java b/src/java/org/apache/poi/hssf/record/formula/ArrayPtg.java index 97fad47b9e..f2e836ffcc 100644 --- a/src/java/org/apache/poi/hssf/record/formula/ArrayPtg.java +++ b/src/java/org/apache/poi/hssf/record/formula/ArrayPtg.java @@ -39,6 +39,11 @@ public final class ArrayPtg extends Ptg { public static final byte sid = 0x20; private static final int RESERVED_FIELD_LEN = 7; + /** + * The size of the plain tArray token written within the standard formula tokens + * (not including the data which comes after all formula tokens) + */ + public static final int PLAIN_TOKEN_SIZE = 1+RESERVED_FIELD_LEN; // TODO - fix up field visibility and subclasses private byte[] field_1_reserved; @@ -123,7 +128,7 @@ public final class ArrayPtg extends Ptg { public int writeTokenValueBytes(byte[] data, int offset) { LittleEndian.putByte(data, offset + 0, token_1_columns-1); - LittleEndian.putShort(data, offset + 1, (short)(token_2_rows-1)); + LittleEndian.putUShort(data, offset + 1, token_2_rows-1); ConstantValueParser.encode(data, offset + 3, token_3_arrayValues); return 3 + ConstantValueParser.getEncodedSize(token_3_arrayValues); } @@ -137,11 +142,11 @@ public final class ArrayPtg extends Ptg { } /** This size includes the size of the array Ptg plus the Array Ptg Token value size*/ - public int getSize() - { - int size = 1+7+1+2; - size += ConstantValueParser.getEncodedSize(token_3_arrayValues); - return size; + public int getSize() { + return PLAIN_TOKEN_SIZE + // data written after the all tokens: + + 1 + 2 // column, row + + ConstantValueParser.getEncodedSize(token_3_arrayValues); } public String toFormulaString(HSSFWorkbook book) diff --git a/src/java/org/apache/poi/hssf/record/formula/Ptg.java b/src/java/org/apache/poi/hssf/record/formula/Ptg.java index d1ea411cb9..3af4991d4e 100644 --- a/src/java/org/apache/poi/hssf/record/formula/Ptg.java +++ b/src/java/org/apache/poi/hssf/record/formula/Ptg.java @@ -221,13 +221,6 @@ public abstract class Ptg implements Cloneable { } throw new RuntimeException("Unexpected base token id (" + id + ")"); } - /** - * - * - */ - public static int getEncodedSize(Stack ptgs) { - return getEncodedSize(toPtgArray(ptgs)); - } /** * @return a distinct copy of this Ptg if the class is mutable, or the same instance * if the class is immutable. @@ -265,6 +258,11 @@ public abstract class Ptg implements Cloneable { } return result; } + /** + * This method will return the same result as {@link #getEncodedSizeWithoutArrayData(Ptg[])} + * if there are no array tokens present. + * @return the full size taken to encode the specified Ptgs + */ // TODO - several duplicates of this code should be refactored here public static int getEncodedSize(Ptg[] ptgs) { int result = 0; @@ -273,6 +271,22 @@ public abstract class Ptg implements Cloneable { } return result; } + /** + * Used to calculate value that should be encoded at the start of the encoded Ptg token array; + * @return the size of the encoded Ptg tokens not including any trailing array data. + */ + public static int getEncodedSizeWithoutArrayData(Ptg[] ptgs) { + int result = 0; + for (int i = 0; i < ptgs.length; i++) { + Ptg ptg = ptgs[i]; + if (ptg instanceof ArrayPtg) { + result += ArrayPtg.PLAIN_TOKEN_SIZE; + } else { + result += ptg.getSize(); + } + } + return result; + } /** * Writes the ptgs to the data buffer, starting at the specified offset. * diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFCell.java b/src/java/org/apache/poi/hssf/usermodel/HSSFCell.java index 61665fdb62..17e1778c86 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFCell.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFCell.java @@ -292,7 +292,7 @@ public final class HSSFCell { if (cellType != this.cellType) { - frec = new FormulaRecordAggregate(new FormulaRecord(),null); + frec = new FormulaRecordAggregate(new FormulaRecord()); } else { @@ -584,41 +584,27 @@ public final class HSSFCell { int row=record.getRow(); short col=record.getColumn(); short styleIndex=record.getXFIndex(); - //Workbook.currentBook=book; - if (formula==null) { - setCellType(CELL_TYPE_BLANK,false,row,col,styleIndex); - } else { - setCellType(CELL_TYPE_FORMULA,false,row,col,styleIndex); - FormulaRecordAggregate rec = (FormulaRecordAggregate) record; - FormulaRecord frec = rec.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); - Ptg[] ptgs = FormulaParser.parse(formula, book); - int size = 0; - - // clear the Ptg Stack - for (int i=0, iSize=frec.getNumberOfExpressionTokens(); i>>= 4; + } while (charPos > 1); + + // Prefix added to avoid ambiguity + result[0] = '0'; + result[1] = 'x'; + return result; + } + /** + * @return char array of 4 (zero padded) uppercase hex chars and prefixed with '0x' + */ + public static char[] longToHex(long value) { + return toHexChars(value, 8); + } + /** + * @return char array of 4 (zero padded) uppercase hex chars and prefixed with '0x' + */ + public static char[] intToHex(int value) { + return toHexChars(value, 4); + } + /** + * @return char array of 2 (zero padded) uppercase hex chars and prefixed with '0x' + */ + public static char[] shortToHex(int value) { + return toHexChars(value, 2); + } + /** + * @return char array of 1 (zero padded) uppercase hex chars and prefixed with '0x' + */ + public static char[] byteToHex(int value) { + return toHexChars(value, 1); + } public static void main(String[] args) throws Exception { File file = new File(args[0]); diff --git a/src/testcases/org/apache/poi/hssf/record/aggregates/TestFormulaRecordAggregate.java b/src/testcases/org/apache/poi/hssf/record/aggregates/TestFormulaRecordAggregate.java index 88b5477783..b7e43ec4d4 100644 --- a/src/testcases/org/apache/poi/hssf/record/aggregates/TestFormulaRecordAggregate.java +++ b/src/testcases/org/apache/poi/hssf/record/aggregates/TestFormulaRecordAggregate.java @@ -35,7 +35,8 @@ public final class TestFormulaRecordAggregate extends junit.framework.TestCase { FormulaRecord f = new FormulaRecord(); StringRecord s = new StringRecord(); s.setString("abc"); - FormulaRecordAggregate fagg = new FormulaRecordAggregate(f,s); + FormulaRecordAggregate fagg = new FormulaRecordAggregate(f); + fagg.setStringRecord(s); assertEquals("abc", fagg.getStringValue()); } } diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestBug42464.java b/src/testcases/org/apache/poi/hssf/usermodel/TestBug42464.java index 530ccc0559..1f1eca4fc2 100644 --- a/src/testcases/org/apache/poi/hssf/usermodel/TestBug42464.java +++ b/src/testcases/org/apache/poi/hssf/usermodel/TestBug42464.java @@ -71,7 +71,7 @@ public final class TestBug42464 extends TestCase { if(false && cellRef.equals("BP24")) { // TODO - replace System.out.println()s with asserts System.out.print(cellRef); System.out.println(" - has " + r.getNumberOfExpressionTokens() - + " ptgs over " + r.getExpressionLength() + " tokens:"); + + " ptgs:"); for(int i=0; i= 32) && (c <= 126)) - { + if (c >= 32 && c <= 126) { rval = ( char ) c; } return rval; } - - /** - * main method to run the unit tests - * - * @param ignored_args - */ - - public static void main(String [] ignored_args) - { - System.out.println("Testing util.HexDump functionality"); - junit.textui.TestRunner.run(TestHexDump.class); - } } -- 2.39.5