Consolidated TableRecord inside FormulaRecordAggregate. Simplifications to FormulaRecord

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@689973 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Josh Micich 2008-08-28 20:39:41 +00:00
parent f17e56b30e
commit 43a8618659
13 changed files with 370 additions and 508 deletions

View File

@ -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. <code>null</code> 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();

View File

@ -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 <code>null</code>
*/
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();
}
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;
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;
}
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");
public String toString() {
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();
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;
}
}

View File

@ -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.
* <p>
* 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) {

View File

@ -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 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 setStringRecord( StringRecord stringRecord ) {
public void setStringRecord(StringRecord stringRecord) {
_stringRecord = stringRecord;
}
public void setFormulaRecord( FormulaRecord formulaRecord )
{
_formulaRecord = formulaRecord;
_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() {

View File

@ -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();
}

View File

@ -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;
}
if (rec instanceof SharedFormulaRecord) {
// Already handled, not to worry
continue;
while (rs.hasNext()) {
Class recClass = rs.peekNextClass();
if (recClass == StringRecord.class) {
throw new RuntimeException("Loose StringRecord found without preceding FormulaRecord");
}
if (rec instanceof UnknownRecord) {
if (recClass == TableRecord.class) {
throw new RuntimeException("Loose TableRecord found without preceding FormulaRecord");
}
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;

View File

@ -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)

View File

@ -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 <tt>Ptg</tt> 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 <tt>Ptg</tt>s
*/
// 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.
*

View File

@ -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<iSize; i++) {
frec.popExpressionToken();
}
// fill the Ptg Stack with Ptgs of new formula
for (int k = 0; k < ptgs.length; k++) {
size += ptgs[ k ].getSize();
frec.pushExpressionToken(ptgs[ k ]);
}
rec.getFormulaRecord().setExpressionLength(( short ) size);
//Workbook.currentBook = null;
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();
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);
frec.setParsedExpression(ptgs);
}
public String getCellFormula() {
//Workbook.currentBook=book;
String retval = FormulaParser.toFormulaString(book, ((FormulaRecordAggregate)record).getFormulaRecord().getParsedExpression());
//Workbook.currentBook=null;
return retval;
return FormulaParser.toFormulaString(book, ((FormulaRecordAggregate)record).getFormulaRecord().getParsedExpression());
}

View 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
@ -15,11 +14,17 @@
See the License for the specific language governing permissions and
limitations under the License.
==================================================================== */
package org.apache.poi.util;
import java.io.*;
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.text.DecimalFormat;
/**
@ -29,27 +34,16 @@ import java.text.DecimalFormat;
* @author Marc Johnson
* @author Glen Stampoultzis (glens at apache.org)
*/
public class HexDump
{
public static final String EOL =
System.getProperty("line.separator");
// private static final StringBuffer _lbuffer = new StringBuffer(8);
// private static final StringBuffer _cbuffer = new StringBuffer(2);
private static final char _hexcodes[] =
{
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D',
'E', 'F'
};
private static final int _shifts[] =
public class HexDump {
public static final String EOL = System.getProperty("line.separator");
private static final char _hexcodes[] = "0123456789ABCDEF".toCharArray();
private static final int _shifts[] =
{
60, 56, 52, 48, 44, 40, 36, 32, 28, 24, 20, 16, 12, 8, 4, 0
};
// all static methods, so no need for a public constructor
private HexDump()
{
private HexDump() {
// all static methods, so no need for a public constructor
}
/**
@ -69,7 +63,7 @@ public class HexDump
* @exception IllegalArgumentException if the output stream is
* null
*/
public synchronized static void dump(final byte [] data, final long offset,
public static void dump(final byte [] data, final long offset,
final OutputStream stream, final int index, final int length)
throws IOException, ArrayIndexOutOfBoundsException,
IllegalArgumentException
@ -413,6 +407,50 @@ public class HexDump
byte[] data = buf.toByteArray();
dump(data, 0, out, start, data.length);
}
/**
* @return char array of uppercase hex chars, zero padded and prefixed with '0x'
*/
private static char[] toHexChars(long pValue, int nBytes) {
int charPos = 2 + nBytes*2;
// The return type is char array because most callers will probably append the value to a
// StringBuffer, or write it to a Stream / Writer so there is no need to create a String;
char[] result = new char[charPos];
long value = pValue;
do {
result[--charPos] = _hexcodes[(int) (value & 0x0F)];
value >>>= 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]);

View File

@ -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());
}
}

View File

@ -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<ptgs.size(); i++) {
String c = ptgs.get(i).getClass().toString();
System.out.println("\t" + c.substring(c.lastIndexOf('.')+1) );

View 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
@ -15,54 +14,26 @@
See the License for the specific language governing permissions and
limitations under the License.
==================================================================== */
package org.apache.poi.util;
import junit.framework.*;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.*;
import junit.framework.TestCase;
/**
* @author Glen Stampoultzis (glens at apache.org)
* @author Marc Johnson (mjohnson at apache dot org)
*/
public final class TestHexDump extends TestCase {
public class TestHexDump
extends TestCase
{
/**
* Creates new TestHexDump
*
* @param name
*/
public TestHexDump(String name)
{
super(name);
private static char toHex(int n) {
return Character.toUpperCase(Character.forDigit(n & 0x0F, 16));
}
private char toHex(final int n)
{
char[] hexChars =
{
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C',
'D', 'E', 'F'
};
return hexChars[ n % 16 ];
}
/**
* test dump method
*
* @exception IOException
*/
public void testDump()
throws IOException
{
public void testDump() throws IOException {
byte[] testArray = new byte[ 256 ];
for (int j = 0; j < 256; j++)
@ -245,8 +216,7 @@ public class TestHexDump
// verify proper behavior with negative index
try
{
HexDump.dump(testArray, 0x10000000, new ByteArrayOutputStream(),
-1);
HexDump.dump(testArray, 0x10000000, new ByteArrayOutputStream(), -1);
fail("should have caught ArrayIndexOutOfBoundsException on negative index");
}
catch (ArrayIndexOutOfBoundsException ignored_exception)
@ -287,37 +257,33 @@ public class TestHexDump
}
public void testToHex()
throws Exception
{
assertEquals( "000A", HexDump.toHex((short)0xA));
assertEquals( "0A", HexDump.toHex((byte)0xA));
assertEquals( "0000000A", HexDump.toHex(0xA));
assertEquals( "FFFF", HexDump.toHex((short)0xFFFF));
public void testToHex() {
assertEquals("000A", HexDump.toHex((short)0xA));
assertEquals("0A", HexDump.toHex((byte)0xA));
assertEquals("0000000A", HexDump.toHex(0xA));
assertEquals("FFFF", HexDump.toHex((short)0xFFFF));
confirmStr("0xFE", HexDump.byteToHex(-2));
confirmStr("0x25", HexDump.byteToHex(37));
confirmStr("0xFFFE", HexDump.shortToHex(-2));
confirmStr("0x0005", HexDump.shortToHex(5));
confirmStr("0xFFFFFF9C", HexDump.intToHex(-100));
confirmStr("0x00001001", HexDump.intToHex(4097));
confirmStr("0xFFFFFFFFFFFF0006", HexDump.longToHex(-65530));
confirmStr("0x0000000000003FCD", HexDump.longToHex(16333));
}
private char toAscii(final int c)
{
private static void confirmStr(String expected, char[] actualChars) {
assertEquals(expected, new String(actualChars));
}
private static char toAscii(int c) {
char rval = '.';
if ((c >= 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);
}
}