diff --git a/src/java/org/apache/poi/hssf/record/ArrayRecord.java b/src/java/org/apache/poi/hssf/record/ArrayRecord.java new file mode 100644 index 000000000..32562a672 --- /dev/null +++ b/src/java/org/apache/poi/hssf/record/ArrayRecord.java @@ -0,0 +1,126 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + +package org.apache.poi.hssf.record; + +import org.apache.poi.hssf.record.formula.Ptg; +import org.apache.poi.hssf.util.CellRangeAddress8Bit; +import org.apache.poi.util.HexDump; +import org.apache.poi.util.LittleEndian; + +/** + * ARRAY (0x0221)

+ * + * Treated in a similar way to SharedFormulaRecord + * + * @author Josh Micich + */ +public final class ArrayRecord extends Record { + + public final static short sid = 0x0221; + private static final int OPT_ALWAYS_RECALCULATE = 0x0001; + private static final int OPT_CALCULATE_ON_OPEN = 0x0002; + + private CellRangeAddress8Bit _range; + + private int _options; + private int _field3notUsed; + private Ptg[] _formulaTokens; + + public ArrayRecord(RecordInputStream in) { + super(in); + } + + public boolean isAlwaysRecalculate() { + return (_options & OPT_ALWAYS_RECALCULATE) != 0; + } + public boolean isCalculateOnOpen() { + return (_options & OPT_CALCULATE_ON_OPEN) != 0; + } + + protected void validateSid(short id) { + if (id != sid) { + throw new RecordFormatException("NOT A valid Array RECORD"); + } + } + + private int getDataSize(){ + return CellRangeAddress8Bit.ENCODED_SIZE + + 2 + 4 + + getFormulaSize(); + } + + public int serialize( int offset, byte[] data ) { + int dataSize = getDataSize(); + + LittleEndian.putShort(data, 0 + offset, sid); + LittleEndian.putUShort(data, 2 + offset, dataSize); + + int pos = offset+4; + _range.serialize(pos, data); + pos += CellRangeAddress8Bit.ENCODED_SIZE; + LittleEndian.putUShort(data, pos, _options); + pos+=2; + LittleEndian.putInt(data, pos, _field3notUsed); + pos+=4; + int tokenSize = Ptg.getEncodedSizeWithoutArrayData(_formulaTokens); + LittleEndian.putUShort(data, pos, tokenSize); + pos+=2; + Ptg.serializePtgs(_formulaTokens, data, pos); + return dataSize + 4; + } + + private int getFormulaSize() { + int result = 0; + for (int i = 0; i < _formulaTokens.length; i++) { + result += _formulaTokens[i].getSize(); + } + return result; + } + + + public int getRecordSize(){ + return 4 + getDataSize(); + } + + + protected void fillFields(RecordInputStream in) { + _range = new CellRangeAddress8Bit(in); + _options = in.readUShort(); + _field3notUsed = in.readInt(); + int formulaLen = in.readUShort(); + _formulaTokens = Ptg.readTokens(formulaLen, in); + } + + public short getSid() { + return sid; + } + + public String toString() { + StringBuffer sb = new StringBuffer(); + sb.append(getClass().getName()).append(" [ARRAY]\n"); + sb.append(" range=").append(_range.toString()).append("\n"); + sb.append(" options=").append(HexDump.shortToHex(_options)).append("\n"); + sb.append(" notUsed=").append(HexDump.intToHex(_field3notUsed)).append("\n"); + sb.append(" formula:").append("\n"); + for (int i = 0; i < _formulaTokens.length; i++) { + sb.append(_formulaTokens[i].toString()); + } + sb.append("]"); + return sb.toString(); + } +} diff --git a/src/java/org/apache/poi/hssf/record/SelectionRecord.java b/src/java/org/apache/poi/hssf/record/SelectionRecord.java index 2f36ea644..e1849a7d7 100644 --- a/src/java/org/apache/poi/hssf/record/SelectionRecord.java +++ b/src/java/org/apache/poi/hssf/record/SelectionRecord.java @@ -17,6 +17,7 @@ package org.apache.poi.hssf.record; +import org.apache.poi.hssf.util.CellRangeAddress8Bit; import org.apache.poi.util.LittleEndian; /** @@ -35,66 +36,25 @@ public final class SelectionRecord extends Record { private int field_2_row_active_cell; private int field_3_col_active_cell; private int field_4_active_cell_ref_index; - private Reference[] field_6_refs; - - /** - * Note - column values are 8-bit so cannot use CellRangeAddressList - */ - public class Reference { - /* package */ static final int ENCODED_SIZE = 6; - private int _firstRow; - private int _lastRow; - private int _firstCol; - private int _lastCol; - - /* package */ Reference(int firstRow, int lastRow, int firstColumn, int lastColumn) { - _firstRow = firstRow; - _lastRow = lastRow; - _firstCol = firstColumn; - _lastCol = lastColumn; - } - /* package */ Reference(RecordInputStream in) { - this(in.readUShort(), in.readUShort(), in.readUByte(), in.readUByte()); - } - public void serialize(int offset, byte[] data) { - LittleEndian.putUShort(data, offset + 0, _firstRow); - LittleEndian.putUShort(data, offset + 2, _lastRow); - LittleEndian.putByte(data, offset + 4, _firstCol); - LittleEndian.putByte(data, offset + 6, _lastCol); - } - - public int getFirstRow() { - return _firstRow; - } - public int getLastRow() { - return _lastRow; - } - public int getFirstColumn() { - return _firstCol; - } - public int getLastColumn() { - return _lastCol; - } - } + private CellRangeAddress8Bit[] field_6_refs; /** * Creates a default selection record (cell A1, in pane ID 3) */ public SelectionRecord(int activeCellRow, int activeCellCol) { - field_1_pane = 3; // pane id 3 is always present. see OOO sec 5.75 'PANE' - field_2_row_active_cell = activeCellRow; - field_3_col_active_cell = activeCellCol; - field_4_active_cell_ref_index = 0; - field_6_refs = new Reference[] { - new Reference(activeCellRow, activeCellRow, activeCellCol, activeCellCol), - }; + field_1_pane = 3; // pane id 3 is always present. see OOO sec 5.75 'PANE' + field_2_row_active_cell = activeCellRow; + field_3_col_active_cell = activeCellCol; + field_4_active_cell_ref_index = 0; + field_6_refs = new CellRangeAddress8Bit[] { + new CellRangeAddress8Bit(activeCellRow, activeCellRow, activeCellCol, activeCellCol), + }; } /** * Constructs a Selection record and sets its fields appropriately. * @param in the RecordInputstream to read the record from */ - public SelectionRecord(RecordInputStream in) { super(in); } @@ -112,10 +72,10 @@ public final class SelectionRecord extends Record { field_4_active_cell_ref_index = in.readShort(); int field_5_num_refs = in.readUShort(); - field_6_refs = new Reference[field_5_num_refs]; + field_6_refs = new CellRangeAddress8Bit[field_5_num_refs]; for (int i = 0; i < field_6_refs.length; i++) { - field_6_refs[i] = new Reference(in); - } + field_6_refs[i] = new CellRangeAddress8Bit(in); + } } /** @@ -180,8 +140,7 @@ public final class SelectionRecord extends Record { return (short)field_4_active_cell_ref_index; } - public String toString() - { + public String toString() { StringBuffer buffer = new StringBuffer(); buffer.append("[SELECTION]\n"); @@ -199,11 +158,11 @@ public final class SelectionRecord extends Record { return buffer.toString(); } private int getDataSize() { - return 9 // 1 byte + 4 shorts - + field_6_refs.length * Reference.ENCODED_SIZE; + return 9 // 1 byte + 4 shorts + + CellRangeAddress8Bit.getEncodedSize(field_6_refs.length); } public int serialize(int offset, byte [] data) { - int dataSize = getDataSize(); + int dataSize = getDataSize(); LittleEndian.putUShort(data, 0 + offset, sid); LittleEndian.putUShort(data, 2 + offset, dataSize); LittleEndian.putByte(data, 4 + offset, getPane()); @@ -213,9 +172,9 @@ public final class SelectionRecord extends Record { int nRefs = field_6_refs.length; LittleEndian.putUShort(data, 11 + offset, nRefs); for (int i = 0; i < field_6_refs.length; i++) { - Reference r = field_6_refs[i]; - r.serialize(offset + 13 + i * Reference.ENCODED_SIZE, data); - } + CellRangeAddress8Bit r = field_6_refs[i]; + r.serialize(offset + 13 + i * CellRangeAddress8Bit.ENCODED_SIZE, data); + } return 4 + dataSize; } diff --git a/src/java/org/apache/poi/hssf/record/SharedFormulaRecord.java b/src/java/org/apache/poi/hssf/record/SharedFormulaRecord.java index 3f3a047e6..2fc6730c5 100755 --- a/src/java/org/apache/poi/hssf/record/SharedFormulaRecord.java +++ b/src/java/org/apache/poi/hssf/record/SharedFormulaRecord.java @@ -17,17 +17,16 @@ package org.apache.poi.hssf.record; -import java.util.List; -import java.util.Stack; - 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; +import org.apache.poi.hssf.util.CellRangeAddress8Bit; +import org.apache.poi.util.HexDump; /** - * Title: SharedFormulaRecord + * Title: SHAREDFMLA (0x04BC) 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 * serialize as is since this is used when reading templates. @@ -40,58 +39,46 @@ import org.apache.poi.hssf.record.formula.RefPtg; public final class SharedFormulaRecord extends Record { 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 CellRangeAddress8Bit _range; + private int field_5_reserved; + private Ptg[] field_7_parsed_expr; - public SharedFormulaRecord() - { + public SharedFormulaRecord() { + _range = new CellRangeAddress8Bit(0, 0, 0, 0); + field_7_parsed_expr = Ptg.EMPTY_PTG_ARRAY; } /** * @param in the RecordInputstream to read the record from */ - - public SharedFormulaRecord(RecordInputStream in) - { + public SharedFormulaRecord(RecordInputStream in) { super(in); } - protected void validateSid(short id) - { - if (id != this.sid) - { + protected void validateSid(short id) { + if (id != this.sid) { throw new RecordFormatException("Not a valid SharedFormula"); } } public int getFirstRow() { - return field_1_first_row; + return _range.getFirstRow(); } public int getLastRow() { - return field_2_last_row; + return _range.getLastRow(); } public short getFirstColumn() { - return field_3_first_column; + return (short) _range.getFirstColumn(); } public short getLastColumn() { - return field_4_last_column; - } - - public short getExpressionLength() - { - return field_6_expression_len; + return (short) _range.getLastColumn(); } /** - * spit the record out AS IS. no interperatation or identification + * spit the record out AS IS. no interpretation or identification */ public int serialize(int offset, byte [] data) @@ -115,65 +102,28 @@ public final class SharedFormulaRecord extends Record { { StringBuffer buffer = new StringBuffer(); - buffer.append("[SHARED FORMULA RECORD:" + Integer.toHexString(sid) + "]\n"); - buffer.append(" .id = ").append(Integer.toHexString(sid)) - .append("\n"); - buffer.append(" .first_row = ") - .append(Integer.toHexString(getFirstRow())).append("\n"); - buffer.append(" .last_row = ") - .append(Integer.toHexString(getLastRow())) - .append("\n"); - buffer.append(" .first_column = ") - .append(Integer.toHexString(getFirstColumn())).append("\n"); - buffer.append(" .last_column = ") - .append(Integer.toHexString(getLastColumn())) - .append("\n"); - buffer.append(" .reserved = ") - .append(Integer.toHexString(field_5_reserved)) - .append("\n"); - buffer.append(" .expressionlength= ").append(getExpressionLength()) - .append("\n"); + buffer.append("[SHARED FORMULA (").append(HexDump.intToHex(sid)).append("]\n"); + buffer.append(" .range = ").append(_range.toString()).append("\n"); + buffer.append(" .reserved = ").append(HexDump.shortToHex(field_5_reserved)).append("\n"); - buffer.append(" .numptgsinarray = ").append(field_7_parsed_expr.size()) - .append("\n"); - - for (int k = 0; k < field_7_parsed_expr.size(); k++ ) { - buffer.append("Formula ") - .append(k) - .append("\n") - .append(field_7_parsed_expr.get(k).toString()) - .append("\n"); + for (int k = 0; k < field_7_parsed_expr.length; k++ ) { + buffer.append("Formula[").append(k).append("]"); + buffer.append(field_7_parsed_expr[k].toString()).append("\n"); } - buffer.append("[/SHARED FORMULA RECORD]\n"); + buffer.append("[/SHARED FORMULA]\n"); return buffer.toString(); } - public short getSid() - { + public short getSid() { return sid; } - protected void fillFields(RecordInputStream in) - { - field_1_first_row = in.readUShort(); - field_2_last_row = in.readUShort(); - field_3_first_column = in.readUByte(); - field_4_last_column = in.readUByte(); - field_5_reserved = in.readShort(); - field_6_expression_len = in.readShort(); - field_7_parsed_expr = getParsedExpressionTokens(in); - } - - private Stack getParsedExpressionTokens(RecordInputStream in) - { - Stack stack = new Stack(); - - while (in.remaining() != 0) { - Ptg ptg = Ptg.createPtg(in); - stack.push(ptg); - } - return stack; + protected void fillFields(RecordInputStream in) { + _range = new CellRangeAddress8Bit(in); + field_5_reserved = in.readShort(); + int field_6_expression_len = in.readShort(); + field_7_parsed_expr = Ptg.readTokens(field_6_expression_len, in); } /** @@ -190,7 +140,7 @@ public final class SharedFormulaRecord extends Record { * Creates a non shared formula from the shared formula * counter part */ - protected static Stack convertSharedFormulas(Stack ptgs, int formulaRow, int formulaColumn) { + protected static Ptg[] convertSharedFormulas(Ptg[] ptgs, int formulaRow, int formulaColumn) { if(false) { /* * TODO - (May-2008) Stop converting relative ref Ptgs in shared formula records. @@ -201,11 +151,10 @@ public final class SharedFormulaRecord extends Record { */ return ptgs; } - Stack newPtgStack = new Stack(); + Ptg[] newPtgStack = new Ptg[ptgs.length]; - if (ptgs != null) - for (int k = 0; k < ptgs.size(); k++) { - Ptg ptg = (Ptg) ptgs.get(k); + for (int k = 0; k < ptgs.length; k++) { + Ptg ptg = ptgs[k]; byte originalOperandClass = -1; if (!ptg.isBaseToken()) { originalOperandClass = ptg.getPtgClass(); @@ -226,12 +175,16 @@ public final class SharedFormulaRecord extends Record { areaNPtg.isLastRowRelative(), areaNPtg.isFirstColRelative(), areaNPtg.isLastColRelative()); + } else { + if (false) {// do we need a ptg clone here? + ptg = ptg.copy(); + } } if (!ptg.isBaseToken()) { ptg.setClass(originalOperandClass); } - newPtgStack.add(ptg); + newPtgStack[k] = ptg; } return newPtgStack; } @@ -248,9 +201,7 @@ public final class SharedFormulaRecord extends Record { 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); + Ptg[] ptgs = convertSharedFormulas(field_7_parsed_expr, formulaRow, formulaColumn); formula.setParsedExpression(ptgs); //Now its not shared! formula.setSharedFormula(false); diff --git a/src/java/org/apache/poi/hssf/record/TableRecord.java b/src/java/org/apache/poi/hssf/record/TableRecord.java index 237b2cb40..e9a6ae565 100644 --- a/src/java/org/apache/poi/hssf/record/TableRecord.java +++ b/src/java/org/apache/poi/hssf/record/TableRecord.java @@ -18,47 +18,46 @@ package org.apache.poi.hssf.record; import org.apache.poi.hssf.record.formula.TblPtg; +import org.apache.poi.hssf.util.CellRangeAddress8Bit; +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; /** + * DATATABLE (0x0236)

+ * * TableRecord - The record specifies a data table. * This record is preceded by a single Formula record that * defines the first cell in the data table, which should * only contain a single Ptg, {@link TblPtg}. - * + * * See p536 of the June 08 binary docs */ public final class TableRecord extends Record { - public static final short sid = 566; - - private static final BitField alwaysCalc = BitFieldFactory.getInstance(0x0001); - private static final BitField reserved1 = BitFieldFactory.getInstance(0x0002); - private static final BitField rowOrColInpCell = BitFieldFactory.getInstance(0x0004); - private static final BitField oneOrTwoVar = BitFieldFactory.getInstance(0x0008); - private static final BitField rowDeleted = BitFieldFactory.getInstance(0x0010); - private static final BitField colDeleted = BitFieldFactory.getInstance(0x0020); - private static final BitField reserved2 = BitFieldFactory.getInstance(0x0040); - private static final BitField reserved3 = BitFieldFactory.getInstance(0x0080); - - private short field_1_ref_rowFirst; - private short field_2_ref_rowLast; - private short field_3_ref_colFirst; - private short field_4_ref_colLast; - - private byte field_5_flags; - private byte field_6_res; - private short field_7_rowInputRow; - private short field_8_colInputRow; - private short field_9_rowInputCol; - private short field_10_colInputCol; - + public static final short sid = 0x0236; + + private static final BitField alwaysCalc = BitFieldFactory.getInstance(0x0001); + private static final BitField reserved1 = BitFieldFactory.getInstance(0x0002); + private static final BitField rowOrColInpCell = BitFieldFactory.getInstance(0x0004); + private static final BitField oneOrTwoVar = BitFieldFactory.getInstance(0x0008); + private static final BitField rowDeleted = BitFieldFactory.getInstance(0x0010); + private static final BitField colDeleted = BitFieldFactory.getInstance(0x0020); + private static final BitField reserved2 = BitFieldFactory.getInstance(0x0040); + private static final BitField reserved3 = BitFieldFactory.getInstance(0x0080); + + private CellRangeAddress8Bit _range; + + private int field_5_flags; + private int field_6_res; + private int field_7_rowInputRow; + private int field_8_colInputRow; + private int field_9_rowInputCol; + private int field_10_colInputCol; + protected void fillFields(RecordInputStream in) { - field_1_ref_rowFirst = in.readShort(); - field_2_ref_rowLast = in.readShort(); - field_3_ref_colFirst = in.readUByte(); - field_4_ref_colLast = in.readUByte(); + _range = new CellRangeAddress8Bit(in); field_5_flags = in.readByte(); field_6_res = in.readByte(); field_7_rowInputRow = in.readShort(); @@ -66,183 +65,146 @@ public final class TableRecord extends Record { field_9_rowInputCol = in.readShort(); field_10_colInputCol = in.readShort(); } - - public TableRecord(RecordInputStream in) { - super(in); - } - public TableRecord() { - super(); - } - - public short getRowFirst() { - return field_1_ref_rowFirst; + public TableRecord(RecordInputStream in) { + super(in); } - public void setRowFirst(short field_1_ref_rowFirst) { - this.field_1_ref_rowFirst = field_1_ref_rowFirst; + public TableRecord(CellRangeAddress8Bit range) { + _range = range; + field_6_res = 0; } - public short getRowLast() { - return field_2_ref_rowLast; - } - public void setRowLast(short field_2_ref_rowLast) { - this.field_2_ref_rowLast = field_2_ref_rowLast; + public CellRangeAddress8Bit getRange() { + return _range; } - public short getColFirst() { - return field_3_ref_colFirst; - } - public void setColFirst(short field_3_ref_colFirst) { - this.field_3_ref_colFirst = field_3_ref_colFirst; - } - - public short getColLast() { - return field_4_ref_colLast; - } - public void setColLast(short field_4_ref_colLast) { - this.field_4_ref_colLast = field_4_ref_colLast; - } - - public byte getFlags() { + public int getFlags() { return field_5_flags; } - public void setFlags(byte field_5_flags) { - this.field_5_flags = field_5_flags; + public void setFlags(int flags) { + field_5_flags = flags; } - public byte getReserved() { - return field_6_res; - } - public void setReserved(byte field_6_res) { - this.field_6_res = field_6_res; - } - - public short getRowInputRow() { + public int getRowInputRow() { return field_7_rowInputRow; } - public void setRowInputRow(short field_7_rowInputRow) { - this.field_7_rowInputRow = field_7_rowInputRow; + public void setRowInputRow(int rowInputRow) { + field_7_rowInputRow = rowInputRow; } - public short getColInputRow() { + public int getColInputRow() { return field_8_colInputRow; } - public void setColInputRow(short field_8_colInputRow) { - this.field_8_colInputRow = field_8_colInputRow; + public void setColInputRow(int colInputRow) { + field_8_colInputRow = colInputRow; } - public short getRowInputCol() { + public int getRowInputCol() { return field_9_rowInputCol; } - public void setRowInputCol(short field_9_rowInputCol) { - this.field_9_rowInputCol = field_9_rowInputCol; + public void setRowInputCol(int rowInputCol) { + field_9_rowInputCol = rowInputCol; } - public short getColInputCol() { + public int getColInputCol() { return field_10_colInputCol; } - public void setColInputCol(short field_10_colInputCol) { - this.field_10_colInputCol = field_10_colInputCol; + public void setColInputCol(int colInputCol) { + field_10_colInputCol = colInputCol; } - - + + public boolean isAlwaysCalc() { return alwaysCalc.isSet(field_5_flags); } public void setAlwaysCalc(boolean flag) { - field_5_flags = alwaysCalc.setByteBoolean(field_5_flags, flag); + field_5_flags = alwaysCalc.setBoolean(field_5_flags, flag); } - + public boolean isRowOrColInpCell() { return rowOrColInpCell.isSet(field_5_flags); } public void setRowOrColInpCell(boolean flag) { - field_5_flags = rowOrColInpCell.setByteBoolean(field_5_flags, flag); + field_5_flags = rowOrColInpCell.setBoolean(field_5_flags, flag); } - + public boolean isOneNotTwoVar() { return oneOrTwoVar.isSet(field_5_flags); } public void setOneNotTwoVar(boolean flag) { - field_5_flags = oneOrTwoVar.setByteBoolean(field_5_flags, flag); + field_5_flags = oneOrTwoVar.setBoolean(field_5_flags, flag); } - + public boolean isColDeleted() { return colDeleted.isSet(field_5_flags); } public void setColDeleted(boolean flag) { - field_5_flags = colDeleted.setByteBoolean(field_5_flags, flag); + field_5_flags = colDeleted.setBoolean(field_5_flags, flag); } - + public boolean isRowDeleted() { return rowDeleted.isSet(field_5_flags); } public void setRowDeleted(boolean flag) { - field_5_flags = rowDeleted.setByteBoolean(field_5_flags, flag); + field_5_flags = rowDeleted.setBoolean(field_5_flags, flag); } - + public short getSid() { return sid; } public int serialize(int offset, byte[] data) { - LittleEndian.putShort(data, 0 + offset, sid); - LittleEndian.putShort(data, 2 + offset, ( short ) (16)); - - LittleEndian.putShort(data, 4 + offset, field_1_ref_rowFirst); - LittleEndian.putShort(data, 6 + offset, field_2_ref_rowLast); - LittleEndian.putByte(data, 8 + offset, field_3_ref_colFirst); - LittleEndian.putByte(data, 9 + offset, field_4_ref_colLast); - LittleEndian.putByte(data, 10 + offset, field_5_flags); - LittleEndian.putByte(data, 11 + offset, field_6_res); - LittleEndian.putShort(data, 12 + offset, field_7_rowInputRow); - LittleEndian.putShort(data, 14 + offset, field_8_colInputRow); - LittleEndian.putShort(data, 16 + offset, field_9_rowInputCol); - LittleEndian.putShort(data, 18 + offset, field_10_colInputCol); - - return getRecordSize(); + int dataSize = getDataSize(); + LittleEndian.putShort(data, 0 + offset, sid); + LittleEndian.putUShort(data, 2 + offset, dataSize); + + _range.serialize(4 + offset, data); + LittleEndian.putByte(data, 10 + offset, field_5_flags); + LittleEndian.putByte(data, 11 + offset, field_6_res); + LittleEndian.putUShort(data, 12 + offset, field_7_rowInputRow); + LittleEndian.putUShort(data, 14 + offset, field_8_colInputRow); + LittleEndian.putUShort(data, 16 + offset, field_9_rowInputCol); + LittleEndian.putUShort(data, 18 + offset, field_10_colInputCol); + + return 4 + dataSize; } + private int getDataSize() { + return CellRangeAddress8Bit.ENCODED_SIZE + + 2 // 2 byte fields + + 8; // 4 short fields + } + public int getRecordSize() { - return 4+16; + return 4+getDataSize(); } - + protected void validateSid(short id) { - if (id != sid) - { - throw new RecordFormatException("NOT A TABLE RECORD"); - } + if (id != sid) + { + throw new RecordFormatException("NOT A TABLE RECORD"); + } + } + + public String toString() { + StringBuffer buffer = new StringBuffer(); + buffer.append("[TABLE]\n"); + buffer.append(" .range = ").append(_range.toString()).append("\n"); + buffer.append(" .flags = ") .append(HexDump.byteToHex(field_5_flags)).append("\n"); + buffer.append(" .alwaysClc= ").append(isAlwaysCalc()).append("\n"); + buffer.append(" .reserved = ").append(HexDump.intToHex(field_6_res)).append("\n"); + CellReference crRowInput = cr(field_7_rowInputRow, field_8_colInputRow); + CellReference crColInput = cr(field_9_rowInputCol, field_10_colInputCol); + buffer.append(" .rowInput = ").append(crRowInput.formatAsString()).append("\n"); + buffer.append(" .colInput = ").append(crColInput.formatAsString()).append("\n"); + buffer.append("[/TABLE]\n"); + return buffer.toString(); + } + + private static CellReference cr(int rowIx, int colIxAndFlags) { + int colIx = colIxAndFlags & 0x00FF; + boolean isRowAbs = (colIxAndFlags & 0x8000) == 0; + boolean isColAbs = (colIxAndFlags & 0x4000) == 0; + return new CellReference(rowIx, colIx, isRowAbs, isColAbs); } - - public String toString() - { - StringBuffer buffer = new StringBuffer(); - buffer.append("[TABLE]\n"); - buffer.append(" .row from = ") - .append(Integer.toHexString(field_1_ref_rowFirst)).append("\n"); - buffer.append(" .row to = ") - .append(Integer.toHexString(field_2_ref_rowLast)).append("\n"); - buffer.append(" .column from = ") - .append(Integer.toHexString(field_3_ref_colFirst)).append("\n"); - buffer.append(" .column to = ") - .append(Integer.toHexString(field_4_ref_colLast)).append("\n"); - - buffer.append(" .flags = ") - .append(Integer.toHexString(field_5_flags)).append("\n"); - buffer.append(" .always calc =") - .append(isAlwaysCalc()).append("\n"); - - buffer.append(" .reserved = ") - .append(Integer.toHexString(field_6_res)).append("\n"); - buffer.append(" .row input row = ") - .append(Integer.toHexString(field_7_rowInputRow)).append("\n"); - buffer.append(" .col input row = ") - .append(Integer.toHexString(field_8_colInputRow)).append("\n"); - buffer.append(" .row input col = ") - .append(Integer.toHexString(field_9_rowInputCol)).append("\n"); - buffer.append(" .col input col = ") - .append(Integer.toHexString(field_10_colInputCol)).append("\n"); - buffer.append("[/TABLE]\n"); - return buffer.toString(); - } } diff --git a/src/java/org/apache/poi/hssf/util/CellRangeAddress.java b/src/java/org/apache/poi/hssf/util/CellRangeAddress.java index d6189c368..883932cb9 100644 --- a/src/java/org/apache/poi/hssf/util/CellRangeAddress.java +++ b/src/java/org/apache/poi/hssf/util/CellRangeAddress.java @@ -26,153 +26,41 @@ import org.apache.poi.util.LittleEndian; * Note - {@link SelectionRecord} uses the BIFF5 version of this structure * @author Dragos Buleandra (dragos.buleandra@trade2b.ro) */ -public final class CellRangeAddress { +public final class CellRangeAddress extends CellRangeAddressBase { /* * TODO - replace org.apache.poi.hssf.util.Region */ public static final int ENCODED_SIZE = 8; - /** max 65536 rows in BIFF8 */ - private static final int LAST_ROW_INDEX = 0x00FFFF; - /** max 256 columns in BIFF8 */ - private static final int LAST_COLUMN_INDEX = 0x00FF; - - - private int _firstRow; - private int _firstCol; - private int _lastRow; - private int _lastCol; - public CellRangeAddress(int firstRow, int lastRow, int firstCol, int lastCol) { - if(!isValid(firstRow, lastRow, firstCol, lastCol)) { - throw new IllegalArgumentException("invalid cell range (" + firstRow + ", " + lastRow - + ", " + firstCol + ", " + lastCol + ")"); - } - _firstRow = firstRow; - _lastRow = convertM1ToMax(lastRow, LAST_ROW_INDEX); - _firstCol = firstCol; - _lastCol = convertM1ToMax(lastCol, LAST_COLUMN_INDEX); + super(firstRow, lastRow, firstCol, lastCol); } - private static boolean isValid(int firstRow, int lastRow, int firstColumn, int lastColumn) - { - if(lastRow < 0 || lastRow > LAST_ROW_INDEX) { - return false; - } - if(firstRow < 0 || firstRow > LAST_ROW_INDEX) { - return false; - } - - if(lastColumn < 0 || lastColumn > LAST_COLUMN_INDEX) { - return false; - } - if(firstColumn < 0 || firstColumn > LAST_COLUMN_INDEX) { - return false; - } - return true; - } - /** - * Range arithmetic is easier when using a large positive number for 'max row or column' - * instead of -1. - */ - private static int convertM1ToMax(int lastIx, int maxIndex) { - if(lastIx < 0) { - return maxIndex; - } - return lastIx; - } - - public CellRangeAddress(RecordInputStream in) { + super(readUShortAndCheck(in), in.readUShort(), in.readUShort(), in.readUShort()); + } + + private static int readUShortAndCheck(RecordInputStream in) { if (in.remaining() < ENCODED_SIZE) { // Ran out of data throw new RuntimeException("Ran out of data reading CellRangeAddress"); - } - _firstRow = in.readUShort(); - _lastRow = in.readUShort(); - _firstCol = in.readUShort(); - _lastCol = in.readUShort(); - } - public boolean isFullColumnRange() { - return _firstRow == 0 && _lastRow == LAST_ROW_INDEX; - } - public boolean isFullRowRange() { - return _firstCol == 0 && _lastCol == LAST_COLUMN_INDEX; - } - - /** - * @return column number for the upper left hand corner - */ - public int getFirstColumn() { - return _firstCol; - } - - /** - * @return row number for the upper left hand corner - */ - public int getFirstRow() { - return _firstRow; - } - - /** - * @return column number for the lower right hand corner - */ - public int getLastColumn() { - return _lastCol; - } - - /** - * @return row number for the lower right hand corner - */ - public int getLastRow() { - return _lastRow; - } - - /** - * @param _firstCol column number for the upper left hand corner - */ - public void setFirstColumn(int firstCol) { - _firstCol = firstCol; - } - - /** - * @param rowFrom row number for the upper left hand corner - */ - public void setFirstRow(int firstRow) { - _firstRow = firstRow; - } - - /** - * @param colTo column number for the lower right hand corner - */ - public void setLastColumn(int lastCol) { - _lastCol = lastCol; - } - - /** - * @param rowTo row number for the lower right hand corner - */ - public void setLastRow(int lastRow) { - _lastRow = lastRow; + } + return in.readUShort(); } public int serialize(int offset, byte[] data) { - LittleEndian.putUShort(data, offset + 0, _firstRow); - LittleEndian.putUShort(data, offset + 2, _lastRow); - LittleEndian.putUShort(data, offset + 4, _firstCol); - LittleEndian.putUShort(data, offset + 6, _lastCol); + LittleEndian.putUShort(data, offset + 0, getFirstRow()); + LittleEndian.putUShort(data, offset + 2, getLastRow()); + LittleEndian.putUShort(data, offset + 4, getFirstColumn()); + LittleEndian.putUShort(data, offset + 6, getLastColumn()); return ENCODED_SIZE; } public CellRangeAddress copy() { - return new CellRangeAddress(_firstRow, _lastRow, _firstCol, _lastCol); + return new CellRangeAddress(getFirstRow(), getLastRow(), getFirstColumn(), getLastColumn()); } public static int getEncodedSize(int numberOfItems) { return numberOfItems * ENCODED_SIZE; } - - public String toString() { - return getClass().getName() + " ["+_firstRow+", "+_lastRow+", "+_firstCol+", "+_lastCol+"]"; - } } \ No newline at end of file diff --git a/src/java/org/apache/poi/hssf/util/CellRangeAddress8Bit.java b/src/java/org/apache/poi/hssf/util/CellRangeAddress8Bit.java new file mode 100644 index 000000000..ef949f4f5 --- /dev/null +++ b/src/java/org/apache/poi/hssf/util/CellRangeAddress8Bit.java @@ -0,0 +1,64 @@ +/* ==================================================================== + Copyright 2002-2004 Apache Software Foundation + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + +package org.apache.poi.hssf.util; + +import org.apache.poi.hssf.record.RecordInputStream; +import org.apache.poi.util.LittleEndian; + +/** + * See OOO documentation: excelfileformat.pdf sec 2.5.14 - 'Cell Range Address'

+ * + * Like {@link CellRangeAddress} except column fields are 8-bit. + * + * @author Josh Micich + */ +public final class CellRangeAddress8Bit extends CellRangeAddressBase { + + public static final int ENCODED_SIZE = 6; + + public CellRangeAddress8Bit(int firstRow, int lastRow, int firstCol, int lastCol) { + super(firstRow, lastRow, firstCol, lastCol); + } + + public CellRangeAddress8Bit(RecordInputStream in) { + super(readUShortAndCheck(in), in.readUShort(), in.readUByte(), in.readUByte()); + } + + private static int readUShortAndCheck(RecordInputStream in) { + if (in.remaining() < ENCODED_SIZE) { + // Ran out of data + throw new RuntimeException("Ran out of data reading CellRangeAddress"); + } + return in.readUShort(); + } + + public int serialize(int offset, byte[] data) { + LittleEndian.putUShort(data, offset + 0, getFirstRow()); + LittleEndian.putUShort(data, offset + 2, getLastRow()); + LittleEndian.putByte(data, offset + 4, getFirstColumn()); + LittleEndian.putByte(data, offset + 5, getLastColumn()); + return ENCODED_SIZE; + } + + public CellRangeAddress8Bit copy() { + return new CellRangeAddress8Bit(getFirstRow(), getLastRow(), getFirstColumn(), getLastColumn()); + } + + public static int getEncodedSize(int numberOfItems) { + return numberOfItems * ENCODED_SIZE; + } +} \ No newline at end of file diff --git a/src/java/org/apache/poi/hssf/util/CellRangeAddressBase.java b/src/java/org/apache/poi/hssf/util/CellRangeAddressBase.java new file mode 100644 index 000000000..30bb2f27f --- /dev/null +++ b/src/java/org/apache/poi/hssf/util/CellRangeAddressBase.java @@ -0,0 +1,134 @@ +/* ==================================================================== + Copyright 2002-2004 Apache Software Foundation + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + +package org.apache.poi.hssf.util; + + +/** + * See OOO documentation: excelfileformat.pdf sec 2.5.14 - 'Cell Range Address'

+ * + * Common subclass of 8-bit and 16-bit versions + * + * @author Josh Micich + */ +abstract class CellRangeAddressBase { + + /** max 65536 rows in BIFF8 */ + private static final int LAST_ROW_INDEX = 0x00FFFF; + /** max 256 columns in BIFF8 */ + private static final int LAST_COLUMN_INDEX = 0x00FF; + + private int _firstRow; + private int _firstCol; + private int _lastRow; + private int _lastCol; + + protected CellRangeAddressBase(int firstRow, int lastRow, int firstCol, int lastCol) { + if(!isValid(firstRow, lastRow, firstCol, lastCol)) { + throw new IllegalArgumentException("invalid cell range (" + firstRow + ", " + lastRow + + ", " + firstCol + ", " + lastCol + ")"); + } + _firstRow = firstRow; + _lastRow =lastRow; + _firstCol = firstCol; + _lastCol = lastCol; + } + private static boolean isValid(int firstRow, int lastRow, int firstColumn, int lastColumn) + { + if(lastRow < 0 || lastRow > LAST_ROW_INDEX) { + return false; + } + if(firstRow < 0 || firstRow > LAST_ROW_INDEX) { + return false; + } + + if(lastColumn < 0 || lastColumn > LAST_COLUMN_INDEX) { + return false; + } + if(firstColumn < 0 || firstColumn > LAST_COLUMN_INDEX) { + return false; + } + return true; + } + + + public final boolean isFullColumnRange() { + return _firstRow == 0 && _lastRow == LAST_ROW_INDEX; + } + public final boolean isFullRowRange() { + return _firstCol == 0 && _lastCol == LAST_COLUMN_INDEX; + } + + /** + * @return column number for the upper left hand corner + */ + public final int getFirstColumn() { + return _firstCol; + } + + /** + * @return row number for the upper left hand corner + */ + public final int getFirstRow() { + return _firstRow; + } + + /** + * @return column number for the lower right hand corner + */ + public final int getLastColumn() { + return _lastCol; + } + + /** + * @return row number for the lower right hand corner + */ + public final int getLastRow() { + return _lastRow; + } + + /** + * @param _firstCol column number for the upper left hand corner + */ + public final void setFirstColumn(int firstCol) { + _firstCol = firstCol; + } + + /** + * @param rowFrom row number for the upper left hand corner + */ + public final void setFirstRow(int firstRow) { + _firstRow = firstRow; + } + + /** + * @param colTo column number for the lower right hand corner + */ + public final void setLastColumn(int lastCol) { + _lastCol = lastCol; + } + + /** + * @param rowTo row number for the lower right hand corner + */ + public final void setLastRow(int lastRow) { + _lastRow = lastRow; + } + + public final String toString() { + return getClass().getName() + " ["+_firstRow+", "+_lastRow+", "+_firstCol+", "+_lastCol+"]"; + } +} \ No newline at end of file diff --git a/src/testcases/org/apache/poi/hssf/record/TestSharedFormulaRecord.java b/src/testcases/org/apache/poi/hssf/record/TestSharedFormulaRecord.java index 92dc95618..374234236 100644 --- a/src/testcases/org/apache/poi/hssf/record/TestSharedFormulaRecord.java +++ b/src/testcases/org/apache/poi/hssf/record/TestSharedFormulaRecord.java @@ -17,9 +17,6 @@ package org.apache.poi.hssf.record; -import java.util.List; -import java.util.Stack; - import junit.framework.AssertionFailedError; import junit.framework.ComparisonFailure; import junit.framework.TestCase; @@ -63,18 +60,18 @@ public final class TestSharedFormulaRecord extends TestCase { public void testConvertSharedFormulasOperandClasses_bug45123() { TestcaseRecordInputStream in = new TestcaseRecordInputStream(0, SHARED_FORMULA_WITH_REF_ARRAYS_DATA); - short encodedLen = in.readShort(); - Stack sharedFormula = Ptg.createParsedExpressionTokens(encodedLen, in); + int encodedLen = in.readUShort(); + Ptg[] sharedFormula = Ptg.readTokens(encodedLen, in); - Stack convertedFormula = SharedFormulaRecord.convertSharedFormulas(sharedFormula, 100, 200); + Ptg[] convertedFormula = SharedFormulaRecord.convertSharedFormulas(sharedFormula, 100, 200); - RefPtg refPtg = (RefPtg) convertedFormula.get(1); + RefPtg refPtg = (RefPtg) convertedFormula[1]; assertEquals("$C101", refPtg.toFormulaString(null)); if (refPtg.getPtgClass() == Ptg.CLASS_REF) { throw new AssertionFailedError("Identified bug 45123"); } - confirmOperandClasses(toPtgArray(sharedFormula), toPtgArray(convertedFormula)); + confirmOperandClasses(sharedFormula, convertedFormula); } private static void confirmOperandClasses(Ptg[] originalPtgs, Ptg[] convertedPtgs) { @@ -88,10 +85,4 @@ public final class TestSharedFormulaRecord extends TestCase { } } } - - private static Ptg[] toPtgArray(List list) { - Ptg[] result = new Ptg[list.size()]; - list.toArray(result); - return result; - } } diff --git a/src/testcases/org/apache/poi/hssf/record/TestTableRecord.java b/src/testcases/org/apache/poi/hssf/record/TestTableRecord.java index eb17a1ff3..0f562d145 100644 --- a/src/testcases/org/apache/poi/hssf/record/TestTableRecord.java +++ b/src/testcases/org/apache/poi/hssf/record/TestTableRecord.java @@ -1,4 +1,3 @@ - /* ==================================================================== Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with @@ -15,9 +14,10 @@ See the License for the specific language governing permissions and limitations under the License. ==================================================================== */ - + package org.apache.poi.hssf.record; +import org.apache.poi.hssf.util.CellRangeAddress8Bit; import junit.framework.TestCase; @@ -26,48 +26,40 @@ import junit.framework.TestCase; * class works correctly. Test data taken directly from a real * Excel file. */ -public class TestTableRecord - extends TestCase -{ +public final class TestTableRecord extends TestCase { byte[] header = new byte[] { - 0x36, 02, 0x10, 00, // sid=x236, 16 bytes long + 0x36, 02, 0x10, 00, // sid=x236, 16 bytes long + }; + byte[] data = new byte[] { + 03, 00, // from row 3 + 8, 00, // to row 8 + 04, // from col 4 + 06, // to col 6 + 00, 00, // no flags set + 04, 00, // row inp row 4 + 01, 00, // col inp row 1 + 0x76, 0x40, // row inp col 0x4076 (!) + 00, 00 // col inp col 0 }; - byte[] data = new byte[] { - 03, 00, // from row 3 - 8, 00, // to row 8 - 04, // from col 4 - 06, // to col 6 - 00, 00, // no flags set - 04, 00, // row inp row 4 - 01, 00, // col inp row 1 - 0x76, 0x40, // row inp col 0x4076 (!) - 00, 00 // col inp col 0 - }; - public TestTableRecord(String name) - { - super(name); - } + public void testLoad() { - public void testLoad() - throws Exception - { + TableRecord record = new TableRecord(new TestcaseRecordInputStream((short)0x236, (short)data.length, data)); - TableRecord record = new TableRecord(new TestcaseRecordInputStream((short)0x236, (short)data.length, data)); + CellRangeAddress8Bit range = record.getRange(); + assertEquals(3, range.getFirstRow()); + assertEquals(8, range.getLastRow()); + assertEquals(4, range.getFirstColumn()); + assertEquals(6, range.getLastColumn()); + assertEquals(0, record.getFlags()); + assertEquals(4, record.getRowInputRow()); + assertEquals(1, record.getColInputRow()); + assertEquals(0x4076, record.getRowInputCol()); + assertEquals(0, record.getColInputCol()); - assertEquals(3, record.getRowFirst()); - assertEquals(8, record.getRowLast()); - assertEquals(4, record.getColFirst()); - assertEquals(6, record.getColLast()); - assertEquals(0, record.getFlags()); - assertEquals(4, record.getRowInputRow()); - assertEquals(1, record.getColInputRow()); - assertEquals(0x4076, record.getRowInputCol()); - assertEquals(0, record.getColInputCol()); - - assertEquals( 16 + 4, record.getRecordSize() ); - record.validateSid((short)0x236); - } + assertEquals( 16 + 4, record.getRecordSize() ); + record.validateSid((short)0x236); + } public void testStore() { @@ -87,21 +79,17 @@ public class TestTableRecord // .col input col = 0 // [/TABLE] - TableRecord record = new TableRecord(); - record.setRowFirst((short)3); - record.setRowLast((short)8); - record.setColFirst((short)4); - record.setColLast((short)6); - record.setFlags((byte)0); - record.setReserved((byte)0); - record.setRowInputRow((short)4); - record.setColInputRow((short)1); - record.setRowInputCol((short)0x4076); - record.setColInputCol((short)0); + CellRangeAddress8Bit crab = new CellRangeAddress8Bit(3, 8, 4, 6); + TableRecord record = new TableRecord(crab); + record.setFlags((byte)0); + record.setRowInputRow(4); + record.setColInputRow(1); + record.setRowInputCol(0x4076); + record.setColInputCol(0); - byte [] recordBytes = record.serialize(); - assertEquals(recordBytes.length - 4, data.length); - for (int i = 0; i < data.length; i++) - assertEquals("At offset " + i, data[i], recordBytes[i+4]); - } + byte [] recordBytes = record.serialize(); + assertEquals(recordBytes.length - 4, data.length); + for (int i = 0; i < data.length; i++) + assertEquals("At offset " + i, data[i], recordBytes[i+4]); + } }