<!-- Don't forget to update status.xml too! -->
<release version="3.5-beta4" date="2008-??-??">
+ <action dev="POI-DEVELOPERS" type="fix">15716 - memory usage optimisation - converted Ptg arrays into Formula objects</action>
<action dev="POI-DEVELOPERS" type="add">46065 - added implementation for VALUE function</action>
<action dev="POI-DEVELOPERS" type="add">45966 - added implementation for FIND function</action>
<action dev="POI-DEVELOPERS" type="fix">45778 - fixed ObjRecord to read ftLbsData properly</action>
<!-- Don't forget to update changes.xml too! -->
<changes>
<release version="3.5-beta4" date="2008-??-??">
+ <action dev="POI-DEVELOPERS" type="fix">15716 - memory usage optimisation - converted Ptg arrays into Formula objects</action>
<action dev="POI-DEVELOPERS" type="add">46065 - added implementation for VALUE function</action>
<action dev="POI-DEVELOPERS" type="add">45966 - added implementation for FIND function</action>
<action dev="POI-DEVELOPERS" type="fix">45778 - fixed ObjRecord to read ftLbsData properly</action>
package org.apache.poi.hssf.record;\r
\r
import org.apache.poi.hssf.record.formula.Ptg;\r
+import org.apache.poi.ss.formula.Formula;\r
import org.apache.poi.util.HexDump;\r
-import org.apache.poi.util.LittleEndian;\r
+import org.apache.poi.util.LittleEndianOutput;\r
\r
/**\r
* ARRAY (0x0221)<p/>\r
\r
private int _options;\r
private int _field3notUsed;\r
- private Ptg[] _formulaTokens;\r
+ private Formula _formula;\r
\r
public ArrayRecord(RecordInputStream in) {\r
super(in);\r
_options = in.readUShort();\r
_field3notUsed = in.readInt();\r
- int formulaLen = in.readUShort();\r
- _formulaTokens = Ptg.readTokens(formulaLen, in);\r
+ int formulaTokenLen = in.readUShort();\r
+ int totalFormulaLen = in.available();\r
+ _formula = Formula.read(formulaTokenLen, in, totalFormulaLen);\r
}\r
\r
public boolean isAlwaysRecalculate() {\r
\r
protected int getExtraDataSize() {\r
return 2 + 4\r
- + 2 + Ptg.getEncodedSize(_formulaTokens);\r
+ + _formula.getEncodedSize();\r
}\r
- protected void serializeExtraData(int offset, byte[] data) {\r
- int pos = offset;\r
- LittleEndian.putUShort(data, pos, _options);\r
- pos+=2;\r
- LittleEndian.putInt(data, pos, _field3notUsed);\r
- pos+=4;\r
- int tokenSize = Ptg.getEncodedSizeWithoutArrayData(_formulaTokens);\r
- LittleEndian.putUShort(data, pos, tokenSize);\r
- pos+=2;\r
- Ptg.serializePtgs(_formulaTokens, data, pos);\r
+ protected void serializeExtraData(LittleEndianOutput out) {\r
+ out.writeShort(_options);\r
+ out.writeInt(_field3notUsed);\r
+ _formula.serialize(out);\r
}\r
\r
public short getSid() {\r
sb.append(" options=").append(HexDump.shortToHex(_options)).append("\n");\r
sb.append(" notUsed=").append(HexDump.intToHex(_field3notUsed)).append("\n");\r
sb.append(" formula:").append("\n");\r
- for (int i = 0; i < _formulaTokens.length; i++) {\r
- Ptg ptg = _formulaTokens[i];\r
+ Ptg[] ptgs = _formula.getTokens();\r
+ for (int i = 0; i < ptgs.length; i++) {\r
+ Ptg ptg = ptgs[i];\r
sb.append(ptg.toString()).append(ptg.getRVAType()).append("\n");\r
}\r
sb.append("]");\r
import org.apache.poi.hssf.record.cf.PatternFormatting;
import org.apache.poi.hssf.record.formula.Ptg;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
+import org.apache.poi.ss.formula.Formula;
import org.apache.poi.util.BitField;
import org.apache.poi.util.BitFieldFactory;
-import org.apache.poi.util.LittleEndian;
+import org.apache.poi.util.LittleEndianByteArrayOutputStream;
/**
* Conditional Formatting Rule Record.
private FontFormatting fontFormatting;
- private byte field_8_align_text_break;
- private byte field_9_align_text_rotation_angle;
- private short field_10_align_indentation;
- private short field_11_relative_indentation;
- private short field_12_not_used;
-
private BorderFormatting borderFormatting;
private PatternFormatting patternFormatting;
- private Ptg[] field_17_formula1;
- private Ptg[] field_18_formula2;
+ private Formula field_17_formula1;
+ private Formula field_18_formula2;
/** Creates new CFRuleRecord */
private CFRuleRecord(byte conditionType, byte comparisonOperation)
field_6_not_used = (short)0x8002; // Excel seems to write this value, but it doesn't seem to care what it reads
fontFormatting=null;
- field_8_align_text_break = 0;
- field_9_align_text_rotation_angle = 0;
- field_10_align_indentation = 0;
- field_11_relative_indentation = 0;
- field_12_not_used = 0;
borderFormatting=null;
patternFormatting=null;
- field_17_formula1=null;
- field_18_formula2=null;
+ field_17_formula1=Formula.create(Ptg.EMPTY_PTG_ARRAY);
+ field_18_formula2=Formula.create(Ptg.EMPTY_PTG_ARRAY);
}
private CFRuleRecord(byte conditionType, byte comparisonOperation, Ptg[] formula1, Ptg[] formula2) {
this(conditionType, comparisonOperation);
field_1_condition_type = CONDITION_TYPE_CELL_VALUE_IS;
field_2_comparison_operator = comparisonOperation;
- field_17_formula1 = formula1;
- field_18_formula2 = formula2;
+ field_17_formula1 = Formula.create(formula1);
+ field_18_formula2 = Formula.create(formula2);
}
/**
patternFormatting = new PatternFormatting(in);
}
- if (field_3_formula1_len > 0) {
- field_17_formula1 = Ptg.readTokens(field_3_formula1_len, in);
- }
- if (field_4_formula2_len > 0) {
- field_18_formula2 = Ptg.readTokens(field_4_formula2_len, in);
- }
+ // "You may not use unions, intersections or array constants in Conditional Formatting criteria"
+ field_17_formula1 = Formula.read(field_3_formula1_len, in);
+ field_18_formula2 = Formula.read(field_4_formula2_len, in);
}
public byte getConditionType()
public Ptg[] getParsedExpression1()
{
- return field_17_formula1;
+ return field_17_formula1.getTokens();
}
public void setParsedExpression1(Ptg[] ptgs) {
- field_17_formula1 = safeClone(ptgs);
- }
- private static Ptg[] safeClone(Ptg[] ptgs) {
- if (ptgs == null) {
- return null;
- }
- return (Ptg[]) ptgs.clone();
+ field_17_formula1 = Formula.create(ptgs);
}
/**
* get the stack of the 2nd expression as a list
*
- * @return list of tokens (casts stack to a list and returns it!)
- * this method can return null is we are unable to create Ptgs from
- * existing excel file
- * callers should check for null!
+ * @return array of {@link Ptg}s, possibly <code>null</code>
*/
-
- public Ptg[] getParsedExpression2()
- {
- return field_18_formula2;
+ public Ptg[] getParsedExpression2() {
+ return Formula.getTokens(field_18_formula2);
}
public void setParsedExpression2(Ptg[] ptgs) {
- field_18_formula2 = safeClone(ptgs);
+ field_18_formula2 = Formula.create(ptgs);
}
public short getSid()
}
/**
- * @param ptgs may be <code>null</code>
- * @return encoded size of the formula
+ * @param ptgs must not be <code>null</code>
+ * @return encoded size of the formula tokens (does not include 2 bytes for ushort length)
*/
- private static int getFormulaSize(Ptg[] ptgs) {
- if (ptgs == null) {
- return 0;
- }
- return Ptg.getEncodedSize(ptgs);
+ private static int getFormulaSize(Formula formula) {
+ return formula.getEncodedTokenSize();
}
/**
* @param data byte array containing instance data
* @return number of bytes written
*/
- public int serialize(int pOffset, byte [] data)
- {
+ public int serialize(int pOffset, byte [] data) {
int formula1Len=getFormulaSize(field_17_formula1);
int formula2Len=getFormulaSize(field_18_formula2);
- int offset = pOffset;
int recordsize = getRecordSize();
- LittleEndian.putShort(data, 0 + offset, sid);
- LittleEndian.putShort(data, 2 + offset, (short)(recordsize-4));
- data[4 + offset] = field_1_condition_type;
- data[5 + offset] = field_2_comparison_operator;
- LittleEndian.putUShort(data, 6 + offset, formula1Len);
- LittleEndian.putUShort(data, 8 + offset, formula2Len);
- LittleEndian.putInt(data, 10 + offset, field_5_options);
- LittleEndian.putShort(data,14 + offset, field_6_not_used);
- offset += 16;
+ LittleEndianByteArrayOutputStream out = new LittleEndianByteArrayOutputStream(data, pOffset, recordsize);
- if( containsFontFormattingBlock() )
- {
+ out.writeShort(sid);
+ out.writeShort(recordsize-4);
+ out.writeByte(field_1_condition_type);
+ out.writeByte(field_2_comparison_operator);
+ out.writeShort(formula1Len);
+ out.writeShort(formula2Len);
+ out.writeInt(field_5_options);
+ out.writeShort(field_6_not_used);
+
+ if (containsFontFormattingBlock()) {
byte[] fontFormattingRawRecord = fontFormatting.getRawRecord();
- System.arraycopy(fontFormattingRawRecord, 0, data, offset, fontFormattingRawRecord.length);
- offset += fontFormattingRawRecord.length;
+ out.write(fontFormattingRawRecord);
}
- if( containsBorderFormattingBlock())
- {
- offset += borderFormatting.serialize(offset, data);
+ if (containsBorderFormattingBlock()) {
+ borderFormatting.serialize(out);
}
- if( containsPatternFormattingBlock() )
- {
- offset += patternFormatting.serialize(offset, data);
+ if (containsPatternFormattingBlock()) {
+ patternFormatting.serialize(out);
}
- if (field_17_formula1 != null) {
- offset += Ptg.serializePtgs(field_17_formula1, data, offset);
- }
-
- if (field_18_formula2 != null) {
- offset += Ptg.serializePtgs(field_18_formula2, data, offset);
- }
- if(offset - pOffset != recordsize) {
- throw new IllegalStateException("write mismatch (" + (offset - pOffset) + "!=" + recordsize + ")");
+ field_17_formula1.serializeTokens(out);
+ field_18_formula2.serializeTokens(out);
+
+ if(out.getWriteIndex() - pOffset != recordsize) {
+ throw new IllegalStateException("write mismatch ("
+ + (out.getWriteIndex() - pOffset) + "!=" + recordsize + ")");
}
return recordsize;
}
}
- public String toString()
- {
+ public String toString() {
StringBuffer buffer = new StringBuffer();
buffer.append("[CFRULE]\n");
buffer.append(" OPTION FLAGS=0x"+Integer.toHexString(getOptions()));
- /*
- if( containsFontFormattingBlock())
- {
- buffer.append(fontFormatting.toString());
+ if (false) {
+ if (containsFontFormattingBlock()) {
+ buffer.append(fontFormatting.toString());
+ }
+ if (containsBorderFormattingBlock()) {
+ buffer.append(borderFormatting.toString());
+ }
+ if (containsPatternFormattingBlock()) {
+ buffer.append(patternFormatting.toString());
+ }
+ buffer.append("[/CFRULE]\n");
}
- if( containsBorderFormattingBlock())
- {
- buffer.append(borderFormatting.toString());
- }
- if( containsPatternFormattingBlock())
- {
- buffer.append(patternFormatting.toString());
- }
- buffer.append("[/CFRULE]\n");*/
return buffer.toString();
}
if (containsPatternFormattingBlock()) {
rec.patternFormatting = (PatternFormatting) patternFormatting.clone();
}
- if (field_17_formula1 != null) {
- rec.field_17_formula1 = (Ptg[]) field_17_formula1.clone();
- }
- if (field_18_formula2 != null) {
- rec.field_18_formula2 = (Ptg[]) field_18_formula2.clone();
- }
+ rec.field_17_formula1 = field_17_formula1.copy();
+ rec.field_18_formula2 = field_17_formula1.copy();
return rec;
}
package org.apache.poi.hssf.record;
-import org.apache.poi.hssf.record.UnicodeString.UnicodeRecordStats;
import org.apache.poi.hssf.record.formula.Ptg;
import org.apache.poi.hssf.usermodel.DVConstraint;
import org.apache.poi.hssf.usermodel.HSSFDataValidation;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.ss.util.CellRangeAddressList;
+import org.apache.poi.ss.formula.Formula;
import org.apache.poi.util.BitField;
-import org.apache.poi.util.LittleEndian;
+import org.apache.poi.util.LittleEndianByteArrayOutputStream;
+import org.apache.poi.util.LittleEndianOutput;
+import org.apache.poi.util.StringUtil;
/**
* Title: DATAVALIDATION Record (0x01BE)<p/>
/** Not used - Excel seems to always write 0x3FE0 */
private short _not_used_1 = 0x3FE0;
/** Formula data for first condition (RPN token array without size field) */
- private Ptg[] _formula1;
+ private Formula _formula1;
/** Not used - Excel seems to always write 0x0000 */
private short _not_used_2 = 0x0000;
/** Formula data for second condition (RPN token array without size field) */
- private Ptg[] _formula2;
+ private Formula _formula2;
/** Cell range address list with all affected ranges */
private CellRangeAddressList _regions;
_promptText = resolveTitleText(promptText);
_errorTitle = resolveTitleText(errorTitle);
_errorText = resolveTitleText(errorText);
- _formula1 = formula1;
- _formula2 = formula2;
+ _formula1 = Formula.create(formula1);
+ _formula2 = Formula.create(formula2);
_regions = regions;
}
public DVRecord(RecordInputStream in) {
-
- _option_flags = in.readInt();
-
- _promptTitle = readUnicodeString(in);
- _errorTitle = readUnicodeString(in);
- _promptText = readUnicodeString(in);
- _errorText = readUnicodeString(in);
- int field_size_first_formula = in.readUShort();
- _not_used_1 = in.readShort();
+ _option_flags = in.readInt();
+
+ _promptTitle = readUnicodeString(in);
+ _errorTitle = readUnicodeString(in);
+ _promptText = readUnicodeString(in);
+ _errorText = readUnicodeString(in);
+
+ int field_size_first_formula = in.readUShort();
+ _not_used_1 = in.readShort();
- //read first formula data condition
- _formula1 = Ptg.readTokens(field_size_first_formula, in);
+ // "You may not use unions, intersections or array constants in Data Validation criteria"
- int field_size_sec_formula = in.readUShort();
- _not_used_2 = in.readShort();
+ // read first formula data condition
+ _formula1 = Formula.read(field_size_first_formula, in);
- //read sec formula data condition
- _formula2 = Ptg.readTokens(field_size_sec_formula, in);
+ int field_size_sec_formula = in.readUShort();
+ _not_used_2 = in.readShort();
- //read cell range address list with all affected ranges
- _regions = new org.apache.poi.hssf.util.CellRangeAddressList(in);
+ // read sec formula data condition
+ _formula2 = Formula.read(field_size_sec_formula, in);
+
+ // read cell range address list with all affected ranges
+ _regions = new CellRangeAddressList(in);
}
// --> start option flags
return str;
}
- private void appendFormula(StringBuffer sb, String label, Ptg[] ptgs) {
+ private static void appendFormula(StringBuffer sb, String label, Formula f) {
sb.append(label);
- if (ptgs.length < 1) {
+
+ if (f == null) {
sb.append("<empty>\n");
return;
}
- sb.append("\n");
+ Ptg[] ptgs = f.getTokens();
+ sb.append('\n');
for (int i = 0; i < ptgs.length; i++) {
sb.append('\t').append(ptgs[i].toString()).append('\n');
}
}
public int serialize(int offset, byte [] data) {
- int size = this.getRecordSize();
- LittleEndian.putShort(data, 0 + offset, sid);
- LittleEndian.putShort(data, 2 + offset, ( short ) (size-4));
+ int recSize = getRecordSize();
+ LittleEndianOutput out = new LittleEndianByteArrayOutputStream(data, offset, recSize);
+
+ out.writeShort(sid);
+ out.writeShort(recSize-4);
- int pos = 4;
- LittleEndian.putInt(data, pos + offset, _option_flags);
- pos += 4;
+ out.writeInt(_option_flags);
- pos += serializeUnicodeString(_promptTitle, pos+offset, data);
- pos += serializeUnicodeString(_errorTitle, pos+offset, data);
- pos += serializeUnicodeString(_promptText, pos+offset, data);
- pos += serializeUnicodeString(_errorText, pos+offset, data);
- LittleEndian.putUShort(data, offset+pos, Ptg.getEncodedSize(_formula1));
- pos += 2;
- LittleEndian.putUShort(data, offset+pos, _not_used_1);
- pos += 2;
-
- pos += Ptg.serializePtgs(_formula1, data, pos+offset);
-
- LittleEndian.putUShort(data, offset+pos, Ptg.getEncodedSize(_formula2));
- pos += 2;
- LittleEndian.putShort(data, offset+pos, _not_used_2);
- pos += 2;
- pos += Ptg.serializePtgs(_formula2, data, pos+offset);
- _regions.serialize(pos+offset, data);
- return size;
+ serializeUnicodeString(_promptTitle, out);
+ serializeUnicodeString(_errorTitle, out);
+ serializeUnicodeString(_promptText, out);
+ serializeUnicodeString(_errorText, out);
+ out.writeShort(_formula1.getEncodedTokenSize());
+ out.writeShort(_not_used_1);
+ _formula1.serializeTokens(out);
+
+ out.writeShort(_formula2.getEncodedTokenSize());
+ out.writeShort(_not_used_2);
+ _formula2.serializeTokens(out);
+
+ _regions.serialize(out);
+ return recSize;
}
/**
return new UnicodeString(in);
}
- private static int serializeUnicodeString(UnicodeString us, int offset, byte[] data) {
- UnicodeRecordStats urs = new UnicodeRecordStats();
- us.serialize(urs, offset, data);
- return urs.recordSize;
+ private static void serializeUnicodeString(UnicodeString us, LittleEndianOutput out) {
+ StringUtil.writeUnicodeString(out, us.getString());
}
- private static int getUnicodeStringSize(UnicodeString str) {
- return 3 + str.getString().length();
+ private static int getUnicodeStringSize(UnicodeString us) {
+ String str = us.getString();
+ return 3 + str.length() * (StringUtil.hasMultibyte(str) ? 2 : 1);
}
public int getRecordSize() {
size += getUnicodeStringSize(_errorTitle);
size += getUnicodeStringSize(_promptText);
size += getUnicodeStringSize(_errorText);
- size += Ptg.getEncodedSize(_formula1);
- size += Ptg.getEncodedSize(_formula2);
+ size += _formula1.getEncodedTokenSize();
+ size += _formula2.getEncodedTokenSize();
size += _regions.getSize();
return size;
}
package org.apache.poi.hssf.record;
-import org.apache.poi.hssf.record.formula.Ptg;
-import org.apache.poi.util.LittleEndian;
+import org.apache.poi.ss.formula.Formula;
+import org.apache.poi.util.LittleEndianByteArrayOutputStream;
+import org.apache.poi.util.LittleEndianOutput;
import org.apache.poi.util.StringUtil;
/**
- * EXTERNALNAME<p/>
+ * EXTERNALNAME (0x0023)<p/>
*
* @author Josh Micich
*/
public final class ExternalNameRecord extends Record {
- private static final Ptg[] EMPTY_PTG_ARRAY = { };
-
- public final static short sid = 0x23; // as per BIFF8. (some old versions used 0x223)
+ public final static short sid = 0x0023; // as per BIFF8. (some old versions used 0x223)
private static final int OPT_BUILTIN_NAME = 0x0001;
private static final int OPT_AUTOMATIC_LINK = 0x0002; // m$ doc calls this fWantAdvise
private short field_2_index;
private short field_3_not_used;
private String field_4_name;
- private Ptg[] field_5_name_definition;
+ private Formula field_5_name_definition;
/**
* Convenience Function to determine if the name is a built-in name
int result = 3 * 2 // 3 short fields
+ 2 + field_4_name.length(); // nameLen and name
if(hasFormula()) {
- result += 2 + getNameDefinitionSize(); // nameDefLen and nameDef
+ result += field_5_name_definition.getEncodedSize();
}
return result;
}
*/
public int serialize( int offset, byte[] data ) {
int dataSize = getDataSize();
-
- LittleEndian.putShort( data, 0 + offset, sid );
- LittleEndian.putShort( data, 2 + offset, (short) dataSize );
- LittleEndian.putShort( data, 4 + offset, field_1_option_flag );
- LittleEndian.putShort( data, 6 + offset, field_2_index );
- LittleEndian.putShort( data, 8 + offset, field_3_not_used );
+ int recSize = dataSize + 4;
+ LittleEndianOutput out = new LittleEndianByteArrayOutputStream(data, offset, recSize);
+
+ out.writeShort(sid);
+ out.writeShort(dataSize);
+ out.writeShort(field_1_option_flag);
+ out.writeShort(field_2_index);
+ out.writeShort(field_3_not_used);
int nameLen = field_4_name.length();
- LittleEndian.putUShort( data, 10 + offset, nameLen );
- StringUtil.putCompressedUnicode( field_4_name, data, 12 + offset );
- if(hasFormula()) {
- int defLen = getNameDefinitionSize();
- LittleEndian.putUShort( data, 12 + nameLen + offset, defLen );
- Ptg.serializePtgs(field_5_name_definition, data, 14 + nameLen + offset );
+ out.writeShort(nameLen);
+ StringUtil.putCompressedUnicode(field_4_name, out);
+ if (hasFormula()) {
+ field_5_name_definition.serialize(out);
}
- return dataSize + 4;
+ return recSize;
}
- private int getNameDefinitionSize() {
- return Ptg.getEncodedSize(field_5_name_definition);
- }
-
-
public int getRecordSize(){
return 4 + getDataSize();
}
if(in.remaining() > 0) {
throw readFail("Some unread data (is formula present?)");
}
- field_5_name_definition = EMPTY_PTG_ARRAY;
+ field_5_name_definition = null;
return;
}
- if(in.remaining() <= 0) {
+ int nBytesRemaining = in.available();
+ if(nBytesRemaining <= 0) {
throw readFail("Ran out of record data trying to read formula.");
}
- short formulaLen = in.readShort();
- field_5_name_definition = Ptg.readTokens(formulaLen, in);
+ int formulaLen = in.readUShort();
+ nBytesRemaining -=2;
+ field_5_name_definition = Formula.read(formulaLen, in, nBytesRemaining);
}
/*
* Makes better error messages (while hasFormula() is not reliable)
private RuntimeException readFail(String msg) {
String fullMsg = msg + " fields: (option=" + field_1_option_flag + " index=" + field_2_index
+ " not_used=" + field_3_not_used + " name='" + field_4_name + "')";
- return new RuntimeException(fullMsg);
+ return new RecordFormatException(fullMsg);
}
private boolean hasFormula() {
import org.apache.poi.hssf.record.formula.Ptg;
import org.apache.poi.hssf.record.formula.eval.ErrorEval;
import org.apache.poi.hssf.usermodel.HSSFCell;
+import org.apache.poi.ss.formula.Formula;
import org.apache.poi.util.BitField;
import org.apache.poi.util.BitFieldFactory;
import org.apache.poi.util.HexDump;
-import org.apache.poi.util.LittleEndian;
+import org.apache.poi.util.LittleEndianByteArrayOutputStream;
+import org.apache.poi.util.LittleEndianInput;
+import org.apache.poi.util.LittleEndianOutput;
/**
* Formula Record (0x0006).
public final class FormulaRecord extends Record implements CellValueRecordInterface {
public static final short sid = 0x0006; // docs say 406...because of a bug Microsoft support site article #Q184647)
- private static int FIXED_SIZE = 22;
+ private static int FIXED_SIZE = 20;
private static final BitField alwaysCalc = BitFieldFactory.getInstance(0x0001);
private static final BitField calcOnLoad = BitFieldFactory.getInstance(0x0002);
}
return new SpecialCachedValue(result);
}
- public void serialize(byte[] data, int offset) {
- System.arraycopy(_variableData, 0, data, offset, VARIABLE_DATA_LENGTH);
- LittleEndian.putUShort(data, offset+VARIABLE_DATA_LENGTH, 0xFFFF);
+ public void serialize(LittleEndianOutput out) {
+ out.write(_variableData);
+ out.writeShort(0xFFFF);
}
public String formatDebugString() {
return formatValue() + ' ' + HexDump.toHex(_variableData);
private short field_3_xf;
private double field_4_value;
private short field_5_options;
- private int field_6_zero;
- private Ptg[] field_8_parsed_expr;
+ /**
+ * Unused field. As it turns out this field is often not zero..
+ * According to Microsoft Excel Developer's Kit Page 318:
+ * when writing the chn field (offset 20), it's supposed to be 0 but ignored on read
+ */
+ private int field_6_zero;
+ private Formula field_8_parsed_expr;
/**
* Since the NaN support seems sketchy (different constants) we'll store and spit it out directly
/** Creates new FormulaRecord */
public FormulaRecord() {
- field_8_parsed_expr = Ptg.EMPTY_PTG_ARRAY;
+ field_8_parsed_expr = Formula.create(Ptg.EMPTY_PTG_ARRAY);
}
- public FormulaRecord(RecordInputStream in) {
- field_1_row = in.readUShort();
- field_2_column = in.readShort();
- field_3_xf = in.readShort();
+ public FormulaRecord(RecordInputStream ris) {
+ LittleEndianInput in = ris;
+ field_1_row = in.readUShort();
+ field_2_column = in.readShort();
+ field_3_xf = in.readShort();
long valueLongBits = in.readLong();
field_5_options = in.readShort();
specialCachedValue = SpecialCachedValue.create(valueLongBits);
field_4_value = Double.longBitsToDouble(valueLongBits);
}
- field_6_zero = in.readInt();
+ field_6_zero = in.readInt();
+
int field_7_expression_len = in.readShort(); // this length does not include any extra array data
- field_8_parsed_expr = Ptg.readTokens(field_7_expression_len, in);
- if (in.remaining() == 10) {
- // TODO - this seems to occur when IntersectionPtg is present
- // 10 extra bytes are just 0x01 and 0x00
- // This causes POI stderr: "WARN. Unread 10 bytes of record 0x6"
- }
+ int nBytesAvailable = in.available();
+ field_8_parsed_expr = Formula.read(field_7_expression_len, in, nBytesAvailable);
}
* @return the formula tokens. never <code>null</code>
*/
public Ptg[] getParsedExpression() {
- return (Ptg[]) field_8_parsed_expr.clone();
+ return field_8_parsed_expr.getTokens();
}
public void setParsedExpression(Ptg[] ptgs) {
- field_8_parsed_expr = ptgs;
+ field_8_parsed_expr = Formula.create(ptgs);
}
public short getSid() {
}
private int getDataSize() {
- return FIXED_SIZE + Ptg.getEncodedSize(field_8_parsed_expr);
+ return FIXED_SIZE + field_8_parsed_expr.getEncodedSize();
}
public int serialize(int offset, byte [] data) {
int dataSize = getDataSize();
-
- LittleEndian.putShort(data, 0 + offset, sid);
- LittleEndian.putUShort(data, 2 + offset, dataSize);
- LittleEndian.putUShort(data, 4 + offset, getRow());
- LittleEndian.putShort(data, 6 + offset, getColumn());
- LittleEndian.putShort(data, 8 + offset, getXFIndex());
+ int recSize = 4 + dataSize;
+ LittleEndianOutput out = new LittleEndianByteArrayOutputStream(data, offset, recSize);
+ out.writeShort(sid);
+ out.writeShort(dataSize);
+ out.writeShort(getRow());
+ out.writeShort(getColumn());
+ out.writeShort(getXFIndex());
if (specialCachedValue == null) {
- LittleEndian.putDouble(data, 10 + offset, field_4_value);
+ out.writeDouble(field_4_value);
} else {
- specialCachedValue.serialize(data, 10+offset);
+ specialCachedValue.serialize(out);
}
- LittleEndian.putShort(data, 18 + offset, getOptions());
+ out.writeShort(getOptions());
- //when writing the chn field (offset 20), it's supposed to be 0 but ignored on read
- //Microsoft Excel Developer's Kit Page 318
- LittleEndian.putInt(data, 20 + offset, 0);
- int formulaTokensSize = Ptg.getEncodedSizeWithoutArrayData(field_8_parsed_expr);
- LittleEndian.putUShort(data, 24 + offset, formulaTokensSize);
- Ptg.serializePtgs(field_8_parsed_expr, data, 26+offset);
- return 4 + dataSize;
+ out.writeInt(field_6_zero); // may as well write original data back so as to minimise differences from original
+ field_8_parsed_expr.serialize(out);
+ return recSize;
}
public int getRecordSize() {
StringBuffer sb = new StringBuffer();
sb.append("[FORMULA]\n");
- sb.append(" .row = ").append(HexDump.shortToHex(getRow())).append("\n");
- sb.append(" .column = ").append(HexDump.shortToHex(getColumn())).append("\n");
- sb.append(" .xf = ").append(HexDump.shortToHex(getXFIndex())).append("\n");
- sb.append(" .value = ");
+ sb.append(" .row = ").append(HexDump.shortToHex(getRow())).append("\n");
+ sb.append(" .column = ").append(HexDump.shortToHex(getColumn())).append("\n");
+ sb.append(" .xf = ").append(HexDump.shortToHex(getXFIndex())).append("\n");
+ sb.append(" .value = ");
if (specialCachedValue == null) {
sb.append(field_4_value).append("\n");
} else {
sb.append(specialCachedValue.formatDebugString()).append("\n");
}
- sb.append(" .options = ").append(HexDump.shortToHex(getOptions())).append("\n");
- sb.append(" .alwaysCalc= ").append(alwaysCalc.isSet(getOptions())).append("\n");
- sb.append(" .calcOnLoad= ").append(calcOnLoad.isSet(getOptions())).append("\n");
- sb.append(" .shared = ").append(sharedFormula.isSet(getOptions())).append("\n");
- sb.append(" .zero = ").append(HexDump.intToHex(field_6_zero)).append("\n");
-
- for (int k = 0; k < field_8_parsed_expr.length; k++ ) {
- sb.append(" Ptg[").append(k).append("]=");
- Ptg ptg = field_8_parsed_expr[k];
+ sb.append(" .options = ").append(HexDump.shortToHex(getOptions())).append("\n");
+ sb.append(" .alwaysCalc= ").append(isAlwaysCalc()).append("\n");
+ sb.append(" .calcOnLoad= ").append(isCalcOnLoad()).append("\n");
+ sb.append(" .shared = ").append(isSharedFormula()).append("\n");
+ sb.append(" .zero = ").append(HexDump.intToHex(field_6_zero)).append("\n");
+
+ Ptg[] ptgs = field_8_parsed_expr.getTokens();
+ for (int k = 0; k < ptgs.length; k++ ) {
+ sb.append(" Ptg[").append(k).append("]=");
+ Ptg ptg = ptgs[k];
sb.append(ptg.toString()).append(ptg.getRVAType()).append("\n");
}
sb.append("[/FORMULA]\n");
rec.field_4_value = field_4_value;
rec.field_5_options = field_5_options;
rec.field_6_zero = field_6_zero;
- int nTokens = field_8_parsed_expr.length;
- Ptg[] ptgs = new Ptg[nTokens];
- for (int i = 0; i < nTokens; i++) {
- ptgs[i] = field_8_parsed_expr[i].copy();
- }
- rec.field_8_parsed_expr = ptgs;
+ rec.field_8_parsed_expr = field_8_parsed_expr;
rec.specialCachedValue = specialCachedValue;
return rec;
}
import org.apache.poi.hssf.record.formula.UnionPtg;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.hssf.util.RangeAddress;
+import org.apache.poi.ss.formula.Formula;
import org.apache.poi.ss.util.AreaReference;
import org.apache.poi.util.HexDump;
-import org.apache.poi.util.LittleEndian;
+import org.apache.poi.util.LittleEndianByteArrayOutputStream;
+import org.apache.poi.util.LittleEndianInput;
import org.apache.poi.util.StringUtil;
/**
private boolean field_11_nameIsMultibyte;
private byte field_12_built_in_code;
private String field_12_name_text;
- private Ptg[] field_13_name_definition;
+ private Formula field_13_name_definition;
private String field_14_custom_menu_text;
private String field_15_description_text;
private String field_16_help_topic_text;
/** Creates new NameRecord */
public NameRecord() {
- field_13_name_definition = Ptg.EMPTY_PTG_ARRAY;
+ field_13_name_definition = Formula.create(Ptg.EMPTY_PTG_ARRAY);
field_12_name_text = "";
field_14_custom_menu_text = "";
* @return <code>true</code> if name has a formula (named range or defined value)
*/
public boolean hasFormula() {
- return field_1_option_flag == 0 && field_13_name_definition.length > 0;
+ return field_1_option_flag == 0 && field_13_name_definition.getEncodedTokenSize() > 0;
}
/**
* @return the name formula. never <code>null</code>
*/
public Ptg[] getNameDefinition() {
- return (Ptg[]) field_13_name_definition.clone();
+ return field_13_name_definition.getTokens();
}
public void setNameDefinition(Ptg[] ptgs) {
- field_13_name_definition = (Ptg[]) ptgs.clone();
+ field_13_name_definition = Formula.create(ptgs);
}
/** get the custom menu text
int field_9_length_help_topic_text = field_16_help_topic_text.length();
int field_10_length_status_bar_text = field_17_status_bar_text.length();
int rawNameSize = getNameRawSize();
-
- int formulaTotalSize = Ptg.getEncodedSize(field_13_name_definition);
- int dataSize = 15 // 4 shorts + 7 bytes
+
+ int formulaTotalSize = field_13_name_definition.getEncodedSize();
+ int dataSize = 13 // 3 shorts + 7 bytes
+ rawNameSize
+ field_7_length_custom_menu
+ field_8_length_description_text
+ field_10_length_status_bar_text
+ formulaTotalSize;
- LittleEndian.putShort(data, 0 + offset, sid);
- LittleEndian.putUShort(data, 2 + offset, dataSize);
+ int recSize = 4 + dataSize;
+ LittleEndianByteArrayOutputStream out = new LittleEndianByteArrayOutputStream(data, offset, recSize);
+
+ out.writeShort(sid);
+ out.writeShort(dataSize);
// size defined below
- LittleEndian.putShort(data, 4 + offset, getOptionFlag());
- LittleEndian.putByte(data, 6 + offset, getKeyboardShortcut());
- LittleEndian.putByte(data, 7 + offset, getNameTextLength());
- // Note -
- LittleEndian.putUShort(data, 8 + offset, Ptg.getEncodedSizeWithoutArrayData(field_13_name_definition));
- LittleEndian.putUShort(data, 10 + offset, field_5_externSheetIndex_plus1);
- LittleEndian.putUShort(data, 12 + offset, field_6_sheetNumber);
- LittleEndian.putByte(data, 14 + offset, field_7_length_custom_menu);
- LittleEndian.putByte(data, 15 + offset, field_8_length_description_text);
- LittleEndian.putByte(data, 16 + offset, field_9_length_help_topic_text);
- LittleEndian.putByte(data, 17 + offset, field_10_length_status_bar_text);
- LittleEndian.putByte(data, 18 + offset, field_11_nameIsMultibyte ? 1 : 0);
- int pos = 19 + offset;
+ out.writeShort(getOptionFlag());
+ out.writeByte(getKeyboardShortcut());
+ out.writeByte(getNameTextLength());
+ // Note - formula size is not immediately before encoded formula, and does not include any array constant data
+ out.writeShort(field_13_name_definition.getEncodedTokenSize());
+ out.writeShort(field_5_externSheetIndex_plus1);
+ out.writeShort(field_6_sheetNumber);
+ out.writeByte(field_7_length_custom_menu);
+ out.writeByte(field_8_length_description_text);
+ out.writeByte(field_9_length_help_topic_text);
+ out.writeByte(field_10_length_status_bar_text);
+ out.writeByte(field_11_nameIsMultibyte ? 1 : 0);
if (isBuiltInName()) {
//can send the builtin name directly in
- LittleEndian.putByte(data, pos, field_12_built_in_code);
+ out.writeByte(field_12_built_in_code);
} else {
String nameText = field_12_name_text;
if (field_11_nameIsMultibyte) {
- StringUtil.putUnicodeLE(nameText, data, pos);
- } else {
- StringUtil.putCompressedUnicode(nameText, data, pos);
- }
+ StringUtil.putUnicodeLE(nameText, out);
+ } else {
+ StringUtil.putCompressedUnicode(nameText, out);
+ }
}
- pos += rawNameSize;
-
- Ptg.serializePtgs(field_13_name_definition, data, pos);
- pos += formulaTotalSize;
+ field_13_name_definition.serializeTokens(out);
+ field_13_name_definition.serializeArrayConstantData(out);
- StringUtil.putCompressedUnicode( getCustomMenuText(), data, pos);
- pos += field_7_length_custom_menu;
- StringUtil.putCompressedUnicode( getDescriptionText(), data, pos);
- pos += field_8_length_description_text;
- StringUtil.putCompressedUnicode( getHelpTopicText(), data, pos);
- pos += field_9_length_help_topic_text;
- StringUtil.putCompressedUnicode( getStatusBarText(), data, pos);
+ StringUtil.putCompressedUnicode( getCustomMenuText(), out);
+ StringUtil.putCompressedUnicode( getDescriptionText(), out);
+ StringUtil.putCompressedUnicode( getHelpTopicText(), out);
+ StringUtil.putCompressedUnicode( getStatusBarText(), out);
- return 4 + dataSize;
+ return recSize;
}
private int getNameRawSize() {
if (isBuiltInName()) {
public int getRecordSize(){
return 4 // sid + size
- + 15 // 4 shorts + 7 bytes
+ + 13 // 3 shorts + 7 bytes
+ getNameRawSize()
+ field_14_custom_menu_text.length()
+ field_15_description_text.length()
+ field_16_help_topic_text.length()
+ field_17_status_bar_text.length()
- + Ptg.getEncodedSize(field_13_name_definition);
+ + field_13_name_definition.getEncodedSize();
}
/** gets the extern sheet number
* @return extern sheet index
*/
public int getExternSheetNumber(){
- if (field_13_name_definition.length < 1) {
+ if (field_13_name_definition.getEncodedSize() < 1) {
return 0;
}
- Ptg ptg = field_13_name_definition[0];
+ Ptg ptg = field_13_name_definition.getTokens()[0];
if (ptg.getClass() == Area3DPtg.class){
return ((Area3DPtg) ptg).getExternSheetIndex();
* @param externSheetNumber extern sheet number
*/
public void setExternSheetNumber(short externSheetNumber){
+ Ptg[] ptgs = field_13_name_definition.getTokens();
Ptg ptg;
- if (field_13_name_definition.length < 1){
+ if (ptgs.length < 1){
ptg = createNewPtg();
- field_13_name_definition = new Ptg[] {
- ptg,
- };
+ ptgs = new Ptg[] { ptg, };
} else {
- ptg = field_13_name_definition[0];
+ ptg = ptgs[0];
}
if (ptg.getClass() == Area3DPtg.class){
} else if (ptg.getClass() == Ref3DPtg.class){
((Ref3DPtg) ptg).setExternSheetIndex(externSheetNumber);
}
-
+ field_13_name_definition = Formula.create(ptgs);
}
private static Ptg createNewPtg(){
* @return area reference
*/
public String getAreaReference(HSSFWorkbook book){
- return HSSFFormulaParser.toFormulaString(book, field_13_name_definition);
+ return HSSFFormulaParser.toFormulaString(book, field_13_name_definition.getTokens());
}
/** sets the reference , the area only (range)
RangeAddress ra = new RangeAddress(ref);
Ptg oldPtg;
- if (field_13_name_definition.length < 1){
+ if (field_13_name_definition.getEncodedTokenSize() < 1){
oldPtg = createNewPtg();
} else {
//Trying to find extern sheet index
- oldPtg = field_13_name_definition[0];
+ oldPtg = field_13_name_definition.getTokens()[0];
}
List temp = new ArrayList();
int externSheetIndex = 0;
}
Ptg[] ptgs = new Ptg[temp.size()];
temp.toArray(ptgs);
- field_13_name_definition = ptgs;
+ field_13_name_definition = Formula.create(ptgs);
}
/**
*
* @param in the RecordInputstream to read the record from
*/
- public NameRecord(RecordInputStream in) {
+ public NameRecord(RecordInputStream ris) {
+ LittleEndianInput in = ris;
field_1_option_flag = in.readShort();
field_2_keyboard_shortcut = in.readByte();
int field_3_length_name_text = in.readByte();
int field_4_length_name_definition = in.readShort();
field_5_externSheetIndex_plus1 = in.readShort();
field_6_sheetNumber = in.readUShort();
- int field_7_length_custom_menu = in.readUByte();
- int field_8_length_description_text = in.readUByte();
- int field_9_length_help_topic_text = in.readUByte();
- int field_10_length_status_bar_text = in.readUByte();
+ int f7_customMenuLen = in.readUByte();
+ int f8_descriptionTextLen = in.readUByte();
+ int f9_helpTopicTextLen = in.readUByte();
+ int f10_statusBarTextLen = in.readUByte();
//store the name in byte form if it's a built-in name
field_11_nameIsMultibyte = (in.readByte() != 0);
field_12_built_in_code = in.readByte();
} else {
if (field_11_nameIsMultibyte) {
- field_12_name_text = in.readUnicodeLEString(field_3_length_name_text);
+ field_12_name_text = StringUtil.readUnicodeLE(in, field_3_length_name_text);
} else {
- field_12_name_text = in.readCompressedUnicode(field_3_length_name_text);
+ field_12_name_text = StringUtil.readCompressedUnicode(in, field_3_length_name_text);
}
}
- field_13_name_definition = Ptg.readTokens(field_4_length_name_definition, in);
+ int nBytesAvailable = in.available() - (f7_customMenuLen
+ + f8_descriptionTextLen + f9_helpTopicTextLen + f10_statusBarTextLen);
+ field_13_name_definition = Formula.read(field_4_length_name_definition, in, nBytesAvailable);
//Who says that this can only ever be compressed unicode???
- field_14_custom_menu_text = in.readCompressedUnicode(field_7_length_custom_menu);
- field_15_description_text = in.readCompressedUnicode(field_8_length_description_text);
- field_16_help_topic_text = in.readCompressedUnicode(field_9_length_help_topic_text);
- field_17_status_bar_text = in.readCompressedUnicode(field_10_length_status_bar_text);
+ field_14_custom_menu_text = StringUtil.readCompressedUnicode(in, f7_customMenuLen);
+ field_15_description_text = StringUtil.readCompressedUnicode(in, f8_descriptionTextLen);
+ field_16_help_topic_text = StringUtil.readCompressedUnicode(in, f9_helpTopicTextLen);
+ field_17_status_bar_text = StringUtil.readCompressedUnicode(in, f10_statusBarTextLen);
}
/**
sb.append(" .Status bar text length = ").append(field_17_status_bar_text.length()).append("\n");
sb.append(" .NameIsMultibyte = ").append(field_11_nameIsMultibyte).append("\n");
sb.append(" .Name (Unicode text) = ").append( getNameText() ).append("\n");
- sb.append(" .Formula (nTokens=").append(field_13_name_definition.length).append("):") .append("\n");
- for (int i = 0; i < field_13_name_definition.length; i++) {
- Ptg ptg = field_13_name_definition[i];
+ Ptg[] ptgs = field_13_name_definition.getTokens();
+ sb.append(" .Formula (nTokens=").append(ptgs.length).append("):") .append("\n");
+ for (int i = 0; i < ptgs.length; i++) {
+ Ptg ptg = ptgs[i];
sb.append(" " + ptg.toString()).append(ptg.getRVAType()).append("\n");
}
package org.apache.poi.hssf.record;
import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
import java.util.ArrayList;
import java.util.List;
import org.apache.poi.util.HexDump;
import org.apache.poi.util.LittleEndian;
+import org.apache.poi.util.LittleEndianByteArrayOutputStream;
import org.apache.poi.util.LittleEndianInputStream;
-import org.apache.poi.util.LittleEndianOutput;
-import org.apache.poi.util.LittleEndianOutputStream;
/**
* OBJRECORD (0x005D)<p/>
*/
public final class ObjRecord extends Record {
public final static short sid = 0x005D;
+
+ private static final int NORMAL_PAD_ALIGNMENT = 2;
+ private static int MAX_PAD_ALIGNMENT = 4;
private List subrecords;
/** used when POI has no idea what is going on */
private byte[] _uninterpretedData;
+ /**
+ * Excel seems to tolerate padding to quad or double byte length
+ */
+ private boolean _isPaddedToQuadByteMultiple;
//00000000 15 00 12 00 01 00 01 00 11 60 00 00 00 00 00 0D .........`......
//00000010 26 01 00 00 00 00 00 00 00 00 &.........
_uninterpretedData = subRecordData;
return;
}
+ if (subRecordData.length % 2 != 0) {
+ String msg = "Unexpected length of subRecordData : " + HexDump.toHex(subRecordData);
+ throw new RecordFormatException(msg);
+ }
// System.out.println(HexDump.toHex(subRecordData));
break;
}
}
- if (bais.available() > 0) {
- // earlier versions of the code had allowances for padding
- // At present (Oct-2008), no unit test samples exhibit such padding
- String msg = "Leftover " + bais.available()
+ int nRemainingBytes = bais.available();
+ if (nRemainingBytes > 0) {
+ // At present (Oct-2008), most unit test samples have (subRecordData.length % 2 == 0)
+ _isPaddedToQuadByteMultiple = subRecordData.length % MAX_PAD_ALIGNMENT == 0;
+ if (nRemainingBytes >= (_isPaddedToQuadByteMultiple ? MAX_PAD_ALIGNMENT : NORMAL_PAD_ALIGNMENT)) {
+ String msg = "Leftover " + nRemainingBytes
+ " bytes in subrecord data " + HexDump.toHex(subRecordData);
- throw new RecordFormatException(msg);
+ throw new RecordFormatException(msg);
+ }
+ } else {
+ _isPaddedToQuadByteMultiple = false;
}
}
SubRecord record = (SubRecord) subrecords.get(i);
size += record.getDataSize()+4;
}
+ if (_isPaddedToQuadByteMultiple) {
+ while (size % MAX_PAD_ALIGNMENT != 0) {
+ size++;
+ }
+ } else {
+ while (size % NORMAL_PAD_ALIGNMENT != 0) {
+ size++;
+ }
+ }
return size;
}
public int serialize(int offset, byte[] data) {
int dataSize = getDataSize();
+ int recSize = 4 + dataSize;
+ LittleEndianByteArrayOutputStream out = new LittleEndianByteArrayOutputStream(data, offset, recSize);
- LittleEndian.putUShort(data, 0 + offset, sid);
- LittleEndian.putUShort(data, 2 + offset, dataSize);
+ out.writeShort(sid);
+ out.writeShort(dataSize);
- byte[] subRecordBytes;
if (_uninterpretedData == null) {
- ByteArrayOutputStream baos = new ByteArrayOutputStream(dataSize);
- LittleEndianOutput leo = new LittleEndianOutputStream(baos);
for (int i = 0; i < subrecords.size(); i++) {
SubRecord record = (SubRecord) subrecords.get(i);
- record.serialize(leo);
+ record.serialize(out);
}
+ int expectedEndIx = offset+dataSize;
// padding
- while (baos.size() < dataSize) {
- baos.write(0);
+ while (out.getWriteIndex() < expectedEndIx) {
+ out.writeByte(0);
}
- subRecordBytes = baos.toByteArray();
} else {
- subRecordBytes = _uninterpretedData;
+ out.write(_uninterpretedData);
}
- System.arraycopy(subRecordBytes, 0, data, offset + 4, dataSize);
- return 4 + dataSize;
+ return recSize;
}
public int getRecordSize() {
//growable array of the data.
ByteArrayOutputStream out = new ByteArrayOutputStream(2*MAX_RECORD_DATA_SIZE);
- while (isContinueNext()) {
+ while (true) {
byte[] b = readRemainder();
out.write(b, 0, b.length);
+ if (!isContinueNext()) {
+ break;
+ }
nextRecord();
}
- byte[] b = readRemainder();
- out.write(b, 0, b.length);
-
return out.toByteArray();
}
import org.apache.poi.hssf.record.formula.Ptg;
import org.apache.poi.hssf.record.formula.RefNPtg;
import org.apache.poi.hssf.record.formula.RefPtg;
+import org.apache.poi.hssf.util.CellRangeAddress8Bit;
+import org.apache.poi.ss.formula.Formula;
import org.apache.poi.util.HexDump;
+import org.apache.poi.util.LittleEndianOutput;
/**
* Title: SHAREDFMLA (0x04BC) SharedFormulaRecord
public final static short sid = 0x04BC;
private int field_5_reserved;
- private Ptg[] field_7_parsed_expr;
+ private Formula field_7_parsed_expr;
+ // for testing only
public SharedFormulaRecord() {
- field_7_parsed_expr = Ptg.EMPTY_PTG_ARRAY;
+ this(new CellRangeAddress8Bit(0,0,0,0));
+ }
+ private SharedFormulaRecord(CellRangeAddress8Bit range) {
+ super(range);
+ field_7_parsed_expr = Formula.create(Ptg.EMPTY_PTG_ARRAY);
}
/**
super(in);
field_5_reserved = in.readShort();
int field_6_expression_len = in.readShort();
- field_7_parsed_expr = Ptg.readTokens(field_6_expression_len, in);
+ int nAvailableBytes = in.available();
+ field_7_parsed_expr = Formula.read(field_6_expression_len, in, nAvailableBytes);
}
- protected void serializeExtraData(int offset, byte[] data) {
- //Because this record is converted to individual Formula records, this method is not required.
- throw new UnsupportedOperationException("Cannot serialize a SharedFormulaRecord");
+
+ protected void serializeExtraData(LittleEndianOutput out) {
+ out.writeShort(field_5_reserved);
+ field_7_parsed_expr.serialize(out);
}
protected int getExtraDataSize() {
- //Because this record is converted to individual Formula records, this method is not required.
- throw new UnsupportedOperationException("Cannot get the size for a SharedFormulaRecord");
-
+ return 2 + field_7_parsed_expr.getEncodedSize();
}
/**
buffer.append(" .range = ").append(getRange().toString()).append("\n");
buffer.append(" .reserved = ").append(HexDump.shortToHex(field_5_reserved)).append("\n");
- for (int k = 0; k < field_7_parsed_expr.length; k++ ) {
+ Ptg[] ptgs = field_7_parsed_expr.getTokens();
+ for (int k = 0; k < ptgs.length; k++ ) {
buffer.append("Formula[").append(k).append("]");
- Ptg ptg = field_7_parsed_expr[k];
+ Ptg ptg = ptgs[k];
buffer.append(ptg.toString()).append(ptg.getRVAType()).append("\n");
}
}
/**
- * Creates a non shared formula from the shared formula
- * counter part
+ * Creates a non shared formula from the shared formula counterpart<br/>
+ *
+ * Perhaps this functionality could be implemented in terms of the raw
+ * byte array inside {@link Formula}.
*/
- protected static Ptg[] convertSharedFormulas(Ptg[] ptgs, int formulaRow, int formulaColumn) {
- if(false) {
- /*
- * TODO - (May-2008) Stop converting relative ref Ptgs in shared formula records.
- * If/when POI writes out the workbook, this conversion makes an unnecessary diff in the BIFF records.
- * Disabling this code breaks one existing junit.
- * Some fix-up will be required to make Ptg.toFormulaString(HSSFWorkbook) work properly.
- * That method will need 2 extra params: rowIx and colIx.
- */
- return ptgs;
- }
+ static Ptg[] convertSharedFormulas(Ptg[] ptgs, int formulaRow, int formulaColumn) {
+
Ptg[] newPtgStack = new Ptg[ptgs.length];
for (int k = 0; k < ptgs.length; k++) {
}
/**
- * Creates a non shared formula from the shared formula
- * counter part
+ * @return the equivalent {@link Ptg} array that the formula would have, were it not shared.
*/
- public void convertSharedFormulaRecord(FormulaRecord formula) {
+ public Ptg[] getFormulaTokens(FormulaRecord formula) {
int formulaRow = formula.getRow();
int formulaColumn = formula.getColumn();
//Sanity checks
throw new RuntimeException("Shared Formula Conversion: Coding Error");
}
- Ptg[] ptgs = convertSharedFormulas(field_7_parsed_expr, formulaRow, formulaColumn);
- formula.setParsedExpression(ptgs);
- //Now its not shared!
- formula.setSharedFormula(false);
+ return convertSharedFormulas(field_7_parsed_expr.getTokens(), formulaRow, formulaColumn);
}
private static int fixupRelativeColumn(int currentcolumn, int column, boolean relative) {
}
public Object clone() {
- //Because this record is converted to individual Formula records, this method is not required.
- throw new UnsupportedOperationException("Cannot clone a SharedFormulaRecord");
+ SharedFormulaRecord result = new SharedFormulaRecord(getRange());
+ result.field_5_reserved = field_5_reserved;
+ result.field_7_parsed_expr = field_7_parsed_expr.copy();
+ return result;
}
}
package org.apache.poi.hssf.record;
import org.apache.poi.hssf.util.CellRangeAddress8Bit;
-import org.apache.poi.util.LittleEndian;
+import org.apache.poi.util.LittleEndianByteArrayOutputStream;
+import org.apache.poi.util.LittleEndianInput;
+import org.apache.poi.util.LittleEndianOutput;
/**
* Common base class for {@link SharedFormulaRecord}, {@link ArrayRecord} and
/**
* reads only the range (1 {@link CellRangeAddress8Bit}) from the stream
*/
- public SharedValueRecordBase(RecordInputStream in) {
+ public SharedValueRecordBase(LittleEndianInput in) {
_range = new CellRangeAddress8Bit(in);
}
protected abstract int getExtraDataSize();
- protected abstract void serializeExtraData(int offset, byte[] data);
+ protected abstract void serializeExtraData(LittleEndianOutput out);
public final int serialize(int offset, byte[] data) {
int dataSize = CellRangeAddress8Bit.ENCODED_SIZE + getExtraDataSize();
-
- LittleEndian.putShort(data, 0 + offset, getSid());
- LittleEndian.putUShort(data, 2 + offset, dataSize);
-
- int pos = offset + 4;
- _range.serialize(pos, data);
- pos += CellRangeAddress8Bit.ENCODED_SIZE;
- serializeExtraData(pos, data);
- return dataSize + 4;
+
+ int totalRecSize = dataSize + 4;
+ LittleEndianOutput out = new LittleEndianByteArrayOutputStream(data, offset, totalRecSize);
+ out.writeShort(getSid());
+ out.writeShort(dataSize);
+
+ _range.serialize(out);
+ serializeExtraData(out);
+ return totalRecSize;
}
/**
import org.apache.poi.util.BitField;
import org.apache.poi.util.BitFieldFactory;
import org.apache.poi.util.HexDump;
-import org.apache.poi.util.LittleEndian;
+import org.apache.poi.util.LittleEndianOutput;
/**
* DATATABLE (0x0236)<p/>
*
2 // 2 byte fields
+ 8; // 4 short fields
}
- protected void serializeExtraData(int offset, byte[] data) {
- LittleEndian.putByte(data, 0 + offset, field_5_flags);
- LittleEndian.putByte(data, 1 + offset, field_6_res);
- LittleEndian.putUShort(data, 2 + offset, field_7_rowInputRow);
- LittleEndian.putUShort(data, 4 + offset, field_8_colInputRow);
- LittleEndian.putUShort(data, 6 + offset, field_9_rowInputCol);
- LittleEndian.putUShort(data, 8 + offset, field_10_colInputCol);
+ protected void serializeExtraData(LittleEndianOutput out) {
+ out.writeByte(field_5_flags);
+ out.writeByte(field_6_res);
+ out.writeShort(field_7_rowInputRow);
+ out.writeShort(field_8_colInputRow);
+ out.writeShort(field_9_rowInputCol);
+ out.writeShort(field_10_colInputCol);
}
public String toString() {
import org.apache.poi.hssf.record.FormulaRecord;
import org.apache.poi.hssf.record.Record;
import org.apache.poi.hssf.record.RecordFormatException;
+import org.apache.poi.hssf.record.SharedFormulaRecord;
import org.apache.poi.hssf.record.StringRecord;
+import org.apache.poi.hssf.record.formula.ExpPtg;
+import org.apache.poi.hssf.record.formula.Ptg;
/**
* The formula record aggregate is used to join together the formula record and it's
*/
public final class FormulaRecordAggregate extends RecordAggregate implements CellValueRecordInterface {
- private final FormulaRecord _formulaRecord;
- private SharedValueManager _sharedValueManager;
- /** caches the calculated result of the formula */
- private StringRecord _stringRecord;
-
- /**
- * @param stringRec may be <code>null</code> if this formula does not have a cached text
- * value.
- * @param svm the {@link SharedValueManager} for the current sheet
- */
- public FormulaRecordAggregate(FormulaRecord formulaRec, StringRecord stringRec, SharedValueManager svm) {
- if (svm == null) {
- throw new IllegalArgumentException("sfm must not be null");
- }
- boolean hasStringRec = stringRec != null;
- boolean hasCachedStringFlag = formulaRec.hasCachedResultString();
- if (hasStringRec != hasCachedStringFlag) {
- throw new RecordFormatException("String record was "
- + (hasStringRec ? "": "not ") + " supplied but formula record flag is "
- + (hasCachedStringFlag ? "" : "not ") + " set");
- }
-
- if (formulaRec.isSharedFormula()) {
- svm.convertSharedFormulaRecord(formulaRec);
- }
- _formulaRecord = formulaRec;
- _sharedValueManager = svm;
- _stringRecord = stringRec;
- }
-
- public FormulaRecord getFormulaRecord() {
- return _formulaRecord;
- }
-
- /**
- * debug only
- * TODO - encapsulate
- */
- public StringRecord getStringRecord() {
- return _stringRecord;
- }
-
- public short getXFIndex() {
- return _formulaRecord.getXFIndex();
- }
-
- public void setXFIndex(short xf) {
- _formulaRecord.setXFIndex(xf);
- }
-
- public void setColumn(short col) {
- _formulaRecord.setColumn(col);
- }
-
- public void setRow(int row) {
- _formulaRecord.setRow(row);
- }
-
- public short getColumn() {
- return _formulaRecord.getColumn();
- }
-
- public int getRow() {
- return _formulaRecord.getRow();
- }
-
- public String toString() {
- return _formulaRecord.toString();
- }
-
- public void visitContainedRecords(RecordVisitor rv) {
- rv.visitRecord(_formulaRecord);
- Record sharedFormulaRecord = _sharedValueManager.getRecordForFirstCell(_formulaRecord);
- if (sharedFormulaRecord != null) {
- rv.visitRecord(sharedFormulaRecord);
- }
- if (_stringRecord != null) {
- rv.visitRecord(_stringRecord);
- }
- }
-
- public String getStringValue() {
- if(_stringRecord==null) {
- return null;
- }
- return _stringRecord.getString();
- }
-
- public void setCachedStringResult(String value) {
-
- // Save the string into a String Record, creating one if required
- if(_stringRecord == null) {
- _stringRecord = new StringRecord();
- }
- _stringRecord.setString(value);
- if (value.length() < 1) {
- _formulaRecord.setCachedResultTypeEmptyString();
- } else {
- _formulaRecord.setCachedResultTypeString();
- }
- }
- public void setCachedBooleanResult(boolean value) {
- _stringRecord = null;
- _formulaRecord.setCachedResultBoolean(value);
- }
- public void setCachedErrorResult(int errorCode) {
- _stringRecord = null;
- _formulaRecord.setCachedResultErrorCode(errorCode);
- }
+ private final FormulaRecord _formulaRecord;
+ private SharedValueManager _sharedValueManager;
+ /** caches the calculated result of the formula */
+ private StringRecord _stringRecord;
+ private SharedFormulaRecord _sharedFormulaRecord;
+
+ /**
+ * @param stringRec may be <code>null</code> if this formula does not have a cached text
+ * value.
+ * @param svm the {@link SharedValueManager} for the current sheet
+ */
+ public FormulaRecordAggregate(FormulaRecord formulaRec, StringRecord stringRec, SharedValueManager svm) {
+ if (svm == null) {
+ throw new IllegalArgumentException("sfm must not be null");
+ }
+ boolean hasStringRec = stringRec != null;
+ boolean hasCachedStringFlag = formulaRec.hasCachedResultString();
+ if (hasStringRec != hasCachedStringFlag) {
+ throw new RecordFormatException("String record was "
+ + (hasStringRec ? "": "not ") + " supplied but formula record flag is "
+ + (hasCachedStringFlag ? "" : "not ") + " set");
+ }
+
+ _formulaRecord = formulaRec;
+ _sharedValueManager = svm;
+ _stringRecord = stringRec;
+ if (formulaRec.isSharedFormula()) {
+ _sharedFormulaRecord = svm.linkSharedFormulaRecord(this);
+ if (_sharedFormulaRecord == null) {
+ handleMissingSharedFormulaRecord(formulaRec);
+ }
+ }
+ }
+ /**
+ * Sometimes the shared formula flag "seems" to be erroneously set (because the corresponding
+ * {@link SharedFormulaRecord} does not exist). Normally this would leave no way of determining
+ * the {@link Ptg} tokens for the formula. However as it turns out in these
+ * cases, Excel encodes the unshared {@link Ptg} tokens in the right place (inside the {@link
+ * FormulaRecord}). So the the only thing that needs to be done is to ignore the erroneous
+ * shared formula flag.<br/>
+ *
+ * This method may also be used for setting breakpoints to help diagnose issues regarding the
+ * abnormally-set 'shared formula' flags.
+ * (see TestValueRecordsAggregate.testSpuriousSharedFormulaFlag()).<p/>
+ */
+ private static void handleMissingSharedFormulaRecord(FormulaRecord formula) {
+ // make sure 'unshared' formula is actually available
+ Ptg firstToken = formula.getParsedExpression()[0];
+ if (firstToken instanceof ExpPtg) {
+ throw new RecordFormatException(
+ "SharedFormulaRecord not found for FormulaRecord with (isSharedFormula=true)");
+ }
+ // could log an info message here since this is a fairly unusual occurrence.
+ formula.setSharedFormula(false); // no point leaving the flag erroneously set
+ }
+
+ public FormulaRecord getFormulaRecord() {
+ return _formulaRecord;
+ }
+
+ /**
+ * debug only
+ * TODO - encapsulate
+ */
+ public StringRecord getStringRecord() {
+ return _stringRecord;
+ }
+
+ public short getXFIndex() {
+ return _formulaRecord.getXFIndex();
+ }
+
+ public void setXFIndex(short xf) {
+ _formulaRecord.setXFIndex(xf);
+ }
+
+ public void setColumn(short col) {
+ _formulaRecord.setColumn(col);
+ }
+
+ public void setRow(int row) {
+ _formulaRecord.setRow(row);
+ }
+
+ public short getColumn() {
+ return _formulaRecord.getColumn();
+ }
+
+ public int getRow() {
+ return _formulaRecord.getRow();
+ }
+
+ public String toString() {
+ return _formulaRecord.toString();
+ }
+
+ public void visitContainedRecords(RecordVisitor rv) {
+ rv.visitRecord(_formulaRecord);
+ // TODO - only bother with this if array or table formula
+ Record sharedFormulaRecord = _sharedValueManager.getRecordForFirstCell(_formulaRecord);
+ if (sharedFormulaRecord != null) {
+ rv.visitRecord(sharedFormulaRecord);
+ }
+ if (_stringRecord != null) {
+ rv.visitRecord(_stringRecord);
+ }
+ }
+
+ public String getStringValue() {
+ if(_stringRecord==null) {
+ return null;
+ }
+ return _stringRecord.getString();
+ }
+
+ public void setCachedStringResult(String value) {
+
+ // Save the string into a String Record, creating one if required
+ if(_stringRecord == null) {
+ _stringRecord = new StringRecord();
+ }
+ _stringRecord.setString(value);
+ if (value.length() < 1) {
+ _formulaRecord.setCachedResultTypeEmptyString();
+ } else {
+ _formulaRecord.setCachedResultTypeString();
+ }
+ }
+ public void setCachedBooleanResult(boolean value) {
+ _stringRecord = null;
+ _formulaRecord.setCachedResultBoolean(value);
+ }
+ public void setCachedErrorResult(int errorCode) {
+ _stringRecord = null;
+ _formulaRecord.setCachedResultErrorCode(errorCode);
+ }
+
+ public Ptg[] getFormulaTokens() {
+ if (_sharedFormulaRecord == null) {
+ return _formulaRecord.getParsedExpression();
+ }
+ return _sharedFormulaRecord.getFormulaTokens(_formulaRecord);
+ }
+
+ /**
+ * Also checks for a related shared formula and unlinks it if found
+ */
+ public void setParsedExpression(Ptg[] ptgs) {
+ notifyFormulaChanging();
+ _formulaRecord.setParsedExpression(ptgs);
+ }
+
+ public void unlinkSharedFormula() {
+ SharedFormulaRecord sfr = _sharedFormulaRecord;
+ if (sfr == null) {
+ throw new IllegalStateException("Formula not linked to shared formula");
+ }
+ Ptg[] ptgs = sfr.getFormulaTokens(_formulaRecord);
+ _formulaRecord.setParsedExpression(ptgs);
+ //Now its not shared!
+ _formulaRecord.setSharedFormula(false);
+ _sharedFormulaRecord = null;
+ }
+ /**
+ * Should be called by any code which is either deleting this formula cell, or changing
+ * its type. This method gives the aggregate a chance to unlink any shared formula
+ * that may be involved with this cell formula.
+ */
+ public void notifyFormulaChanging() {
+ if (_sharedFormulaRecord != null) {
+ _sharedValueManager.unlink(_sharedFormulaRecord);
+ }
+ }
}
\r
import org.apache.poi.hssf.record.Record;\r
import org.apache.poi.hssf.record.RecordBase;\r
-import org.apache.poi.hssf.record.RecordInputStream;\r
\r
/**\r
* <tt>RecordAggregate</tt>s are groups of of BIFF <tt>Record</tt>s that are typically stored \r
* @author Josh Micich\r
*/\r
public abstract class RecordAggregate extends RecordBase {\r
- // TODO - delete these methods when all subclasses have been converted\r
- protected final void validateSid(short id) {\r
- throw new RuntimeException("Should not be called");\r
- }\r
- protected final void fillFields(RecordInputStream in) {\r
- throw new RuntimeException("Should not be called");\r
- }\r
- public final short getSid() {\r
- throw new RuntimeException("Should not be called");\r
- }\r
\r
/**\r
* Visit each of the atomic BIFF records contained in this {@link RecordAggregate} in the order\r
return currentRow-1;
}
- public int writeHidden( RowRecord rowRecord, int row, boolean hidden )
- {
+ /**
+ * Hide all rows at or below the current outline level
+ * @return index of the <em>next<em> row after the last row that gets hidden
+ */
+ private int writeHidden(RowRecord pRowRecord, int row) {
+ int rowIx = row;
+ RowRecord rowRecord = pRowRecord;
int level = rowRecord.getOutlineLevel();
- while (rowRecord != null && this.getRow(row).getOutlineLevel() >= level)
- {
- rowRecord.setZeroHeight( hidden );
- row++;
- rowRecord = this.getRow( row );
+ while (rowRecord != null && getRow(rowIx).getOutlineLevel() >= level) {
+ rowRecord.setZeroHeight(true);
+ rowIx++;
+ rowRecord = getRow(rowIx);
}
- return row - 1;
+ return rowIx;
}
- public void collapseRow( int rowNumber )
- {
+ public void collapseRow(int rowNumber) {
// Find the start of the group.
- int startRow = findStartOfRowOutlineGroup( rowNumber );
- RowRecord rowRecord = getRow( startRow );
+ int startRow = findStartOfRowOutlineGroup(rowNumber);
+ RowRecord rowRecord = getRow(startRow);
// Hide all the columns until the end of the group
- int lastRow = writeHidden( rowRecord, startRow, true );
+ int nextRowIx = writeHidden(rowRecord, startRow);
- // Write collapse field
- if (getRow(lastRow + 1) != null)
- {
- getRow(lastRow + 1).setColapsed( true );
- }
- else
- {
- RowRecord row = createRow( lastRow + 1);
- row.setColapsed( true );
- insertRow( row );
+ RowRecord row = getRow(nextRowIx);
+ if (row == null) {
+ row = createRow(nextRowIx);
+ insertRow(row);
}
+ // Write collapse field
+ row.setColapsed(true);
}
/**
_valuesAgg.insertCell(cvRec);
}
public void removeCell(CellValueRecordInterface cvRec) {
+ if (cvRec instanceof FormulaRecordAggregate) {
+ ((FormulaRecordAggregate)cvRec).notifyFormulaChanging();
+ }
_valuesAgg.removeCell(cvRec);
}
public FormulaRecordAggregate createFormula(int row, int col) {
package org.apache.poi.hssf.record.aggregates;
+import java.util.HashMap;
+import java.util.Map;
+
import org.apache.poi.hssf.record.ArrayRecord;
import org.apache.poi.hssf.record.FormulaRecord;
import org.apache.poi.hssf.record.SharedFormulaRecord;
* @author Josh Micich
*/
public final class SharedValueManager {
+
+ // This class should probably be generalised to handle array and table groups too
+ private static final class SharedValueGroup {
+ private final SharedValueRecordBase _svr;
+ private final FormulaRecordAggregate[] _frAggs;
+ private int _numberOfFormulas;
+
+ public SharedValueGroup(SharedValueRecordBase svr) {
+ _svr = svr;
+ int width = svr.getLastColumn() - svr.getFirstColumn() + 1;
+ int height = svr.getLastRow() - svr.getFirstRow() + 1;
+ _frAggs = new FormulaRecordAggregate[width * height];
+ _numberOfFormulas = 0;
+ }
+
+ public void add(FormulaRecordAggregate agg) {
+ _frAggs[_numberOfFormulas++] = agg;
+ }
+
+ public void unlinkSharedFormulas() {
+ for (int i = 0; i < _numberOfFormulas; i++) {
+ _frAggs[i].unlinkSharedFormula();
+ }
+ }
+
+ public boolean isInRange(int rowIx, int columnIx) {
+ return _svr.isInRange(rowIx, columnIx);
+ }
+
+ public SharedValueRecordBase getSVR() {
+ return _svr;
+ }
+
+ /**
+ * Note - Sometimes the first formula in a group is not present (because the range
+ * is sparsely populated), so this method can return <code>true</code> for a cell
+ * that is not the top-left corner of the range.
+ * @return <code>true</code> if this is the first formula cell in the group
+ */
+ public boolean isFirstCell(int row, int column) {
+ // hack for the moment, just check against the first formula that
+ // came in through the add() method.
+ FormulaRecordAggregate fra = _frAggs[0];
+ return fra.getRow() == row && fra.getColumn() == column;
+ }
+
+ }
public static final SharedValueManager EMPTY = new SharedValueManager(
new SharedFormulaRecord[0], new ArrayRecord[0], new TableRecord[0]);
- private final SharedFormulaRecord[] _sfrs;
private final ArrayRecord[] _arrayRecords;
private final TableRecord[] _tableRecords;
+ private final Map _groupsBySharedFormulaRecord;
+ /** cached for optimization purposes */
+ private SharedValueGroup[] _groups;
private SharedValueManager(SharedFormulaRecord[] sharedFormulaRecords,
ArrayRecord[] arrayRecords, TableRecord[] tableRecords) {
- _sfrs = sharedFormulaRecords;
_arrayRecords = arrayRecords;
_tableRecords = tableRecords;
+ Map m = new HashMap(sharedFormulaRecords.length * 3 / 2);
+ for (int i = 0; i < sharedFormulaRecords.length; i++) {
+ SharedFormulaRecord sfr = sharedFormulaRecords[i];
+ m.put(sfr, new SharedValueGroup(sfr));
+ }
+ _groupsBySharedFormulaRecord = m;
}
/**
return new SharedValueManager(sharedFormulaRecords, arrayRecords, tableRecords);
}
- public void convertSharedFormulaRecord(FormulaRecord formula) {
+
+ /**
+ * @return <code>null</code> if the specified formula does not have any corresponding
+ * {@link SharedFormulaRecord}
+ */
+ public SharedFormulaRecord linkSharedFormulaRecord(FormulaRecordAggregate agg) {
+ FormulaRecord formula = agg.getFormulaRecord();
int row = formula.getRow();
int column = formula.getColumn();
// Traverse the list of shared formulas in
// reverse order, and try to find the correct one
// for us
- for (int i = 0; i < _sfrs.length; i++) {
- SharedFormulaRecord shrd = _sfrs[i];
- if (shrd.isInRange(row, column)) {
- shrd.convertSharedFormulaRecord(formula);
- return;
+
+ SharedValueGroup[] groups = getGroups();
+ for (int i = 0; i < groups.length; i++) {
+ SharedValueGroup svr = groups[i];
+ if (svr.isInRange(row, column)) {
+ svr.add(agg);
+ return (SharedFormulaRecord) svr.getSVR();
}
}
- // not found
- handleMissingSharedFormulaRecord(formula);
+ return null;
}
- /**
- * Sometimes the shared formula flag "seems" to be erroneously set, in which case there is no
- * call to <tt>SharedFormulaRecord.convertSharedFormulaRecord</tt> and hence the
- * <tt>parsedExpression</tt> field of this <tt>FormulaRecord</tt> will not get updated.<br/>
- * As it turns out, this is not a problem, because in these circumstances, the existing value
- * for <tt>parsedExpression</tt> is perfectly OK.<p/>
- *
- * This method may also be used for setting breakpoints to help diagnose issues regarding the
- * abnormally-set 'shared formula' flags.
- * (see TestValueRecordsAggregate.testSpuriousSharedFormulaFlag()).<p/>
- *
- * The method currently does nothing but do not delete it without finding a nice home for this
- * comment.
- */
- private static void handleMissingSharedFormulaRecord(FormulaRecord formula) {
- // could log an info message here since this is a fairly unusual occurrence.
- formula.setSharedFormula(false); // no point leaving the flag erroneously set
+ private SharedValueGroup[] getGroups() {
+ if (_groups == null) {
+ SharedValueGroup[] groups = new SharedValueGroup[_groupsBySharedFormulaRecord.size()];
+ _groupsBySharedFormulaRecord.values().toArray(groups);
+ _groups = groups;
+
+ }
+ return _groups;
}
+
+
/**
* Note - does not return SharedFormulaRecords currently, because the corresponding formula
* records have been converted to 'unshared'. POI does not attempt to re-share formulas. On
return ar;
}
}
+ SharedValueGroup[] groups = getGroups();
+ for (int i = 0; i < groups.length; i++) {
+ SharedValueGroup svg = groups[i];
+ if (svg.isFirstCell(row, column)) {
+ return svg.getSVR();
+ }
+ }
return null;
}
+
+ /**
+ * Converts all {@link FormulaRecord}s handled by <tt>sharedFormulaRecord</tt>
+ * to plain unshared formulas
+ */
+ public void unlink(SharedFormulaRecord sharedFormulaRecord) {
+ SharedValueGroup svg = (SharedValueGroup) _groupsBySharedFormulaRecord.remove(sharedFormulaRecord);
+ _groups = null; // be sure to reset cached value
+ if (svg == null) {
+ throw new IllegalStateException("Failed to find formulas for shared formula");
+ }
+ svg.unlinkSharedFormulas();
+ }
}
public void construct(CellValueRecordInterface rec, RecordStream rs, SharedValueManager sfh) {
if (rec instanceof FormulaRecord) {
FormulaRecord formulaRec = (FormulaRecord)rec;
- if (formulaRec.isSharedFormula()) {
- sfh.convertSharedFormulaRecord(formulaRec);
- }
// read optional cached text value
StringRecord cachedText;
Class nextClass = rs.peekNextClass();
-
/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
See the License for the specific language governing permissions and
limitations under the License.
==================================================================== */
-
-/*
- * FontFormatting.java
- *
- * Created on January 22, 2008, 10:05 PM
- */
package org.apache.poi.hssf.record.cf;
-import org.apache.poi.hssf.record.RecordInputStream;
import org.apache.poi.util.BitField;
import org.apache.poi.util.BitFieldFactory;
import org.apache.poi.util.LittleEndian;
+import org.apache.poi.util.LittleEndianInput;
+import org.apache.poi.util.LittleEndianOutput;
/**
* Border Formatting Block of the Conditional Formatting Rule Record.
- *
+ *
* @author Dmitriy Kumshayev
*/
+public final class BorderFormatting {
-public class BorderFormatting
-{
-
- /**
- * No border
- */
-
+ /** No border */
public final static short BORDER_NONE = 0x0;
-
- /**
- * Thin border
- */
-
+ /** Thin border */
public final static short BORDER_THIN = 0x1;
-
- /**
- * Medium border
- */
-
+ /** Medium border */
public final static short BORDER_MEDIUM = 0x2;
-
- /**
- * dash border
- */
-
+ /** dash border */
public final static short BORDER_DASHED = 0x3;
-
- /**
- * dot border
- */
-
- public final static short BORDER_HAIR = 0x4;
-
- /**
- * Thick border
- */
-
+ /** dot border */
+ public final static short BORDER_HAIR = 0x4;
+ /** Thick border */
public final static short BORDER_THICK = 0x5;
-
- /**
- * double-line border
- */
-
+ /** double-line border */
public final static short BORDER_DOUBLE = 0x6;
-
- /**
- * hair-line border
- */
-
- public final static short BORDER_DOTTED = 0x7;
-
- /**
- * Medium dashed border
- */
-
+ /** hair-line border */
+ public final static short BORDER_DOTTED = 0x7;
+ /** Medium dashed border */
public final static short BORDER_MEDIUM_DASHED = 0x8;
-
- /**
- * dash-dot border
- */
-
+ /** dash-dot border */
public final static short BORDER_DASH_DOT = 0x9;
-
- /**
- * medium dash-dot border
- */
-
+ /** medium dash-dot border */
public final static short BORDER_MEDIUM_DASH_DOT = 0xA;
-
- /**
- * dash-dot-dot border
- */
-
+ /** dash-dot-dot border */
public final static short BORDER_DASH_DOT_DOT = 0xB;
-
- /**
- * medium dash-dot-dot border
- */
-
+ /** medium dash-dot-dot border */
public final static short BORDER_MEDIUM_DASH_DOT_DOT = 0xC;
-
- /**
- * slanted dash-dot border
- */
-
+ /** slanted dash-dot border */
public final static short BORDER_SLANTED_DASH_DOT = 0xD;
-
- public BorderFormatting()
- {
- field_13_border_styles1 = (short)0;
- field_14_border_styles2 = (short)0;
- }
-
- /** Creates new FontFormatting */
- public BorderFormatting(RecordInputStream in)
- {
- field_13_border_styles1 = in.readInt();
- field_14_border_styles2 = in.readInt();
- }
-
+
// BORDER FORMATTING BLOCK
// For Border Line Style codes see HSSFCellStyle.BORDER_XXXXXX
- private int field_13_border_styles1;
+ private int field_13_border_styles1;
private static final BitField bordLeftLineStyle = BitFieldFactory.getInstance(0x0000000F);
private static final BitField bordRightLineStyle = BitFieldFactory.getInstance(0x000000F0);
private static final BitField bordTopLineStyle = BitFieldFactory.getInstance(0x00000F00);
private static final BitField bordTlBrLineOnOff = BitFieldFactory.getInstance(0x40000000);
private static final BitField bordBlTrtLineOnOff = BitFieldFactory.getInstance(0x80000000);
- private int field_14_border_styles2;
+ private int field_14_border_styles2;
private static final BitField bordTopLineColor = BitFieldFactory.getInstance(0x0000007F);
private static final BitField bordBottomLineColor= BitFieldFactory.getInstance(0x00003f80);
private static final BitField bordDiagLineColor = BitFieldFactory.getInstance(0x001FC000);
private static final BitField bordDiagLineStyle = BitFieldFactory.getInstance(0x01E00000);
+
+ public BorderFormatting() {
+ field_13_border_styles1 = 0;
+ field_14_border_styles2 = 0;
+ }
+
+ /** Creates new FontFormatting */
+ public BorderFormatting(LittleEndianInput in) {
+ field_13_border_styles1 = in.readInt();
+ field_14_border_styles2 = in.readInt();
+ }
+
+
/**
* set the type of border to use for the left border of the cell
* @param border type
* @see #BORDER_MEDIUM_DASH_DOT_DOT
* @see #BORDER_SLANTED_DASH_DOT
*/
-
- public void setBorderLeft(short border)
- {
- field_13_border_styles1 = bordLeftLineStyle.setValue(field_13_border_styles1, border);
+ public void setBorderLeft(int border) {
+ field_13_border_styles1 = bordLeftLineStyle.setValue(field_13_border_styles1, border);
}
/**
* @see #BORDER_MEDIUM_DASH_DOT_DOT
* @see #BORDER_SLANTED_DASH_DOT
*/
-
- public short getBorderLeft()
- {
- return (short)bordLeftLineStyle.getValue(field_13_border_styles1);
+ public int getBorderLeft() {
+ return bordLeftLineStyle.getValue(field_13_border_styles1);
}
/**
* @see #BORDER_MEDIUM_DASH_DOT_DOT
* @see #BORDER_SLANTED_DASH_DOT
*/
-
- public void setBorderRight(short border)
- {
- field_13_border_styles1 = bordRightLineStyle.setValue(field_13_border_styles1, border);
+ public void setBorderRight(int border) {
+ field_13_border_styles1 = bordRightLineStyle.setValue(field_13_border_styles1, border);
}
/**
* @see #BORDER_MEDIUM_DASH_DOT_DOT
* @see #BORDER_SLANTED_DASH_DOT
*/
-
- public short getBorderRight()
- {
- return (short)bordRightLineStyle.getValue(field_13_border_styles1);
+ public int getBorderRight() {
+ return bordRightLineStyle.getValue(field_13_border_styles1);
}
/**
* @see #BORDER_MEDIUM_DASH_DOT_DOT
* @see #BORDER_SLANTED_DASH_DOT
*/
-
- public void setBorderTop(short border)
- {
- field_13_border_styles1 = bordTopLineStyle.setValue(field_13_border_styles1, border);
+ public void setBorderTop(int border) {
+ field_13_border_styles1 = bordTopLineStyle.setValue(field_13_border_styles1, border);
}
/**
* @see #BORDER_MEDIUM_DASH_DOT_DOT
* @see #BORDER_SLANTED_DASH_DOT
*/
-
- public short getBorderTop()
- {
- return (short)bordTopLineStyle.getValue(field_13_border_styles1);
+ public int getBorderTop() {
+ return bordTopLineStyle.getValue(field_13_border_styles1);
}
/**
* @see #BORDER_MEDIUM_DASH_DOT_DOT
* @see #BORDER_SLANTED_DASH_DOT
*/
-
- public void setBorderBottom(short border)
- {
- field_13_border_styles1 = bordBottomLineStyle.setValue(field_13_border_styles1, border);
+ public void setBorderBottom(int border) {
+ field_13_border_styles1 = bordBottomLineStyle.setValue(field_13_border_styles1, border);
}
/**
* @see #BORDER_MEDIUM_DASH_DOT_DOT
* @see #BORDER_SLANTED_DASH_DOT
*/
- public short getBorderBottom()
- {
- return (short)bordBottomLineStyle.getValue(field_13_border_styles1);
+ public int getBorderBottom() {
+ return bordBottomLineStyle.getValue(field_13_border_styles1);
}
-
+
/**
* set the type of border to use for the diagonal border of the cell
* @param border type
* @see #BORDER_MEDIUM_DASH_DOT_DOT
* @see #BORDER_SLANTED_DASH_DOT
*/
-
- public void setBorderDiagonal(short border)
- {
- field_14_border_styles2 = bordDiagLineStyle.setValue(field_14_border_styles2, border);
+ public void setBorderDiagonal(int border) {
+ field_14_border_styles2 = bordDiagLineStyle.setValue(field_14_border_styles2, border);
}
/**
* @see #BORDER_MEDIUM_DASH_DOT_DOT
* @see #BORDER_SLANTED_DASH_DOT
*/
- public short getBorderDiagonal()
- {
- return (short)bordDiagLineStyle.getValue(field_14_border_styles2);
+ public int getBorderDiagonal() {
+ return bordDiagLineStyle.getValue(field_14_border_styles2);
}
/**
* set the color to use for the left border
* @param color The index of the color definition
*/
- public void setLeftBorderColor(short color)
- {
- field_13_border_styles1 = bordLeftLineColor.setValue(field_13_border_styles1, color);
+ public void setLeftBorderColor(int color) {
+ field_13_border_styles1 = bordLeftLineColor.setValue(field_13_border_styles1, color);
}
/**
* @see org.apache.poi.hssf.usermodel.HSSFPalette#getColor(short)
* @param color The index of the color definition
*/
- public short getLeftBorderColor()
- {
- return (short)bordLeftLineColor.getValue(field_13_border_styles1);
+ public int getLeftBorderColor() {
+ return bordLeftLineColor.getValue(field_13_border_styles1);
}
/**
* set the color to use for the right border
* @param color The index of the color definition
*/
- public void setRightBorderColor(short color)
- {
- field_13_border_styles1 = bordRightLineColor.setValue(field_13_border_styles1, color);
+ public void setRightBorderColor(int color) {
+ field_13_border_styles1 = bordRightLineColor.setValue(field_13_border_styles1, color);
}
/**
* @see org.apache.poi.hssf.usermodel.HSSFPalette#getColor(short)
* @param color The index of the color definition
*/
- public short getRightBorderColor()
- {
- return (short)bordRightLineColor.getValue(field_13_border_styles1);
+ public int getRightBorderColor() {
+ return bordRightLineColor.getValue(field_13_border_styles1);
}
/**
* set the color to use for the top border
* @param color The index of the color definition
*/
- public void setTopBorderColor(short color)
- {
- field_14_border_styles2 = bordTopLineColor.setValue(field_14_border_styles2, color);
+ public void setTopBorderColor(int color) {
+ field_14_border_styles2 = bordTopLineColor.setValue(field_14_border_styles2, color);
}
/**
* @see org.apache.poi.hssf.usermodel.HSSFPalette#getColor(short)
* @param color The index of the color definition
*/
- public short getTopBorderColor()
- {
- return (short)bordTopLineColor.getValue(field_14_border_styles2);
+ public int getTopBorderColor() {
+ return bordTopLineColor.getValue(field_14_border_styles2);
}
/**
* set the color to use for the bottom border
* @param color The index of the color definition
*/
- public void setBottomBorderColor(short color)
+ public void setBottomBorderColor(int color)
{
- field_14_border_styles2 = bordBottomLineColor.setValue(field_14_border_styles2, color);
+ field_14_border_styles2 = bordBottomLineColor.setValue(field_14_border_styles2, color);
}
/**
* @see org.apache.poi.hssf.usermodel.HSSFPalette#getColor(short)
* @param color The index of the color definition
*/
- public short getBottomBorderColor()
- {
- return (short)bordBottomLineColor.getValue(field_14_border_styles2);
+ public int getBottomBorderColor() {
+ return bordBottomLineColor.getValue(field_14_border_styles2);
}
-
+
/**
* set the color to use for the diagonal borders
* @param color The index of the color definition
*/
- public void setDiagonalBorderColor(short color)
- {
- field_14_border_styles2 = bordDiagLineColor.setValue(field_14_border_styles2, color);
+ public void setDiagonalBorderColor(int color) {
+ field_14_border_styles2 = bordDiagLineColor.setValue(field_14_border_styles2, color);
}
/**
* @see org.apache.poi.hssf.usermodel.HSSFPalette#getColor(short)
* @param color The index of the color definition
*/
- public short getDiagonalBorderColor()
- {
- return (short)bordDiagLineColor.getValue(field_14_border_styles2);
+ public int getDiagonalBorderColor() {
+ return bordDiagLineColor.getValue(field_14_border_styles2);
}
/**
* Of/off bottom left to top right line
- *
- * @param on - if true - on, otherwise off
+ *
+ * @param on - if <code>true</code> - on, otherwise off
*/
- public void setForwardDiagonalOn(boolean on)
- {
- field_13_border_styles1 = bordBlTrtLineOnOff.setBoolean(field_13_border_styles1, on);
+ public void setForwardDiagonalOn(boolean on) {
+ field_13_border_styles1 = bordBlTrtLineOnOff.setBoolean(field_13_border_styles1, on);
}
/**
* Of/off top left to bottom right line
- *
- * @param on - if true - on, otherwise off
+ *
+ * @param on - if <code>true</code> - on, otherwise off
*/
- public void setBackwardDiagonalOn(boolean on)
- {
- field_13_border_styles1 = bordTlBrLineOnOff.setBoolean(field_13_border_styles1, on);
+ public void setBackwardDiagonalOn(boolean on) {
+ field_13_border_styles1 = bordTlBrLineOnOff.setBoolean(field_13_border_styles1, on);
}
-
+
/**
- * @return true if forward diagonal is on
+ * @return <code>true</code> if forward diagonal is on
*/
- public boolean isForwardDiagonalOn()
- {
- return bordBlTrtLineOnOff.isSet(field_13_border_styles1);
+ public boolean isForwardDiagonalOn() {
+ return bordBlTrtLineOnOff.isSet(field_13_border_styles1);
}
/**
- * @return true if backward diagonal is on
+ * @return <code>true</code> if backward diagonal is on
*/
- public boolean isBackwardDiagonalOn()
- {
- return bordTlBrLineOnOff.isSet(field_13_border_styles1);
+ public boolean isBackwardDiagonalOn() {
+ return bordTlBrLineOnOff.isSet(field_13_border_styles1);
}
-
-
- public String toString()
- {
+
+
+ public String toString() {
StringBuffer buffer = new StringBuffer();
buffer.append(" [Border Formatting]\n");
buffer.append(" .lftln = ").append(Integer.toHexString(getBorderLeft())).append("\n");
buffer.append(" [/Border Formatting]\n");
return buffer.toString();
}
-
- public Object clone()
- {
+
+ public Object clone() {
BorderFormatting rec = new BorderFormatting();
- rec.field_13_border_styles1 = field_13_border_styles1;
- rec.field_14_border_styles2 = field_14_border_styles2;
+ rec.field_13_border_styles1 = field_13_border_styles1;
+ rec.field_14_border_styles2 = field_14_border_styles2;
return rec;
}
-
- public int serialize(int offset, byte [] data)
- {
- LittleEndian.putInt(data, offset, field_13_border_styles1);
- offset += 4;
- LittleEndian.putInt(data, offset, field_14_border_styles2);
- offset += 4;
- return 8;
+
+ public int serialize(int offset, byte [] data) {
+ LittleEndian.putInt(data, offset+0, field_13_border_styles1);
+ LittleEndian.putInt(data, offset+4, field_14_border_styles2);
+ return 8;
+ }
+ public void serialize(LittleEndianOutput out) {
+ out.writeInt(field_13_border_styles1);
+ out.writeInt(field_14_border_styles2);
}
}
-
/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
See the License for the specific language governing permissions and
limitations under the License.
==================================================================== */
-
-/*
- * FontFormatting.java
- *
- * Created on January 22, 2008, 10:05 PM
- */
package org.apache.poi.hssf.record.cf;
-import org.apache.poi.hssf.record.RecordInputStream;
import org.apache.poi.util.BitField;
import org.apache.poi.util.BitFieldFactory;
-import org.apache.poi.util.LittleEndian;
+import org.apache.poi.util.LittleEndianInput;
+import org.apache.poi.util.LittleEndianOutput;
/**
* Pattern Formatting Block of the Conditional Formatting Rule Record.
*
* @author Dmitriy Kumshayev
*/
-
-public class PatternFormatting implements Cloneable
-{
+public final class PatternFormatting implements Cloneable {
/** No background */
public final static short NO_FILL = 0 ;
/** Solidly filled */
public final static short LESS_DOTS = 17 ;
/** Least Dots */
public final static short LEAST_DOTS = 18 ;
-
- public PatternFormatting()
- {
- field_15_pattern_style = (short)0;
- field_16_pattern_color_indexes = (short)0;
- }
- /** Creates new FontFormatting */
- public PatternFormatting(RecordInputStream in)
- {
- field_15_pattern_style = in.readShort();
- field_16_pattern_color_indexes = in.readShort();
- }
// PATTERN FORMATING BLOCK
// For Pattern Styles see constants at HSSFCellStyle (from NO_FILL to LEAST_DOTS)
- private short field_15_pattern_style;
+ private int field_15_pattern_style;
private static final BitField fillPatternStyle = BitFieldFactory.getInstance(0xFC00);
- private short field_16_pattern_color_indexes;
- private static final BitField patternColorIndex = BitFieldFactory.getInstance(0x007F);
- private static final BitField patternBackgroundColorIndex = BitFieldFactory.getInstance(0x3F80);
+ private int field_16_pattern_color_indexes;
+ private static final BitField patternColorIndex = BitFieldFactory.getInstance(0x007F);
+ private static final BitField patternBackgroundColorIndex = BitFieldFactory.getInstance(0x3F80);
+
+ public PatternFormatting() {
+ field_15_pattern_style = 0;
+ field_16_pattern_color_indexes = 0;
+ }
+
+ /** Creates new FontFormatting */
+ public PatternFormatting(LittleEndianInput in) {
+ field_15_pattern_style = in.readUShort();
+ field_16_pattern_color_indexes = in.readUShort();
+ }
+
/**
* setting fill pattern
*
*
* @param fp fill pattern
*/
- public void setFillPattern(short fp)
- {
- field_15_pattern_style = fillPatternStyle.setShortValue(field_15_pattern_style, fp);
+ public void setFillPattern(int fp) {
+ field_15_pattern_style = fillPatternStyle.setValue(field_15_pattern_style, fp);
}
/**
- * get the fill pattern
* @return fill pattern
*/
-
- public short getFillPattern()
- {
- return fillPatternStyle.getShortValue(field_15_pattern_style);
+ public int getFillPattern() {
+ return fillPatternStyle.getValue(field_15_pattern_style);
}
/**
* set the background fill color.
- *
- * @param bg color
*/
-
- public void setFillBackgroundColor(short bg)
- {
- field_16_pattern_color_indexes = patternBackgroundColorIndex.setShortValue(field_16_pattern_color_indexes,bg);
+ public void setFillBackgroundColor(int bg) {
+ field_16_pattern_color_indexes = patternBackgroundColorIndex.setValue(field_16_pattern_color_indexes,bg);
}
/**
- * get the background fill color
* @see org.apache.poi.hssf.usermodel.HSSFPalette#getColor(short)
- * @return fill color
+ * @return get the background fill color
*/
- public short getFillBackgroundColor()
- {
- return patternBackgroundColorIndex.getShortValue(field_16_pattern_color_indexes);
+ public int getFillBackgroundColor() {
+ return patternBackgroundColorIndex.getValue(field_16_pattern_color_indexes);
}
/**
* set the foreground fill color
- * @param bg color
*/
- public void setFillForegroundColor(short fg)
- {
- field_16_pattern_color_indexes = patternColorIndex.setShortValue(field_16_pattern_color_indexes,fg);
+ public void setFillForegroundColor(int fg) {
+ field_16_pattern_color_indexes = patternColorIndex.setValue(field_16_pattern_color_indexes,fg);
}
/**
- * get the foreground fill color
* @see org.apache.poi.hssf.usermodel.HSSFPalette#getColor(short)
- * @return fill color
+ * @return get the foreground fill color
*/
- public short getFillForegroundColor()
- {
- return patternColorIndex.getShortValue(field_16_pattern_color_indexes);
+ public int getFillForegroundColor() {
+ return patternColorIndex.getValue(field_16_pattern_color_indexes);
}
- public String toString()
- {
+ public String toString() {
StringBuffer buffer = new StringBuffer();
buffer.append(" [Pattern Formatting]\n");
buffer.append(" .fillpattern= ").append(Integer.toHexString(getFillPattern())).append("\n");
return buffer.toString();
}
- public Object clone()
- {
+ public Object clone() {
PatternFormatting rec = new PatternFormatting();
rec.field_15_pattern_style = field_15_pattern_style;
rec.field_16_pattern_color_indexes = field_16_pattern_color_indexes;
return rec;
}
-
- public int serialize(int offset, byte [] data)
- {
- LittleEndian.putShort(data, offset, field_15_pattern_style);
- offset += 2;
- LittleEndian.putShort(data, offset, field_16_pattern_color_indexes);
- offset += 2;
- return 4;
+
+ public void serialize(LittleEndianOutput out) {
+ out.writeShort(field_15_pattern_style);
+ out.writeShort(field_16_pattern_color_indexes);
}
}
public short getBorderBottom()
{
- return borderFormatting.getBorderBottom();
+ return (short)borderFormatting.getBorderBottom();
}
public short getBorderDiagonal()
{
- return borderFormatting.getBorderDiagonal();
+ return (short)borderFormatting.getBorderDiagonal();
}
public short getBorderLeft()
{
- return borderFormatting.getBorderLeft();
+ return (short)borderFormatting.getBorderLeft();
}
public short getBorderRight()
{
- return borderFormatting.getBorderRight();
+ return (short)borderFormatting.getBorderRight();
}
public short getBorderTop()
{
- return borderFormatting.getBorderTop();
+ return (short)borderFormatting.getBorderTop();
}
public short getBottomBorderColor()
{
- return borderFormatting.getBottomBorderColor();
+ return (short)borderFormatting.getBottomBorderColor();
}
public short getDiagonalBorderColor()
{
- return borderFormatting.getDiagonalBorderColor();
+ return (short)borderFormatting.getDiagonalBorderColor();
}
public short getLeftBorderColor()
{
- return borderFormatting.getLeftBorderColor();
+ return (short)borderFormatting.getLeftBorderColor();
}
public short getRightBorderColor()
{
- return borderFormatting.getRightBorderColor();
+ return (short)borderFormatting.getRightBorderColor();
}
public short getTopBorderColor()
{
- return borderFormatting.getTopBorderColor();
+ return (short)borderFormatting.getTopBorderColor();
}
public boolean isBackwardDiagonalOn()
* @see #CELL_TYPE_BOOLEAN
* @see #CELL_TYPE_ERROR
*/
-
- public void setCellType(int cellType)
- {
+ public void setCellType(int cellType) {
+ notifyFormulaChanging();
int row=record.getRow();
short col=record.getColumn();
short styleIndex=record.getXFIndex();
* @param value value to set the cell to. For formulas we'll set the formula
* string, for String cells we'll set its value. For other types we will
* change the cell to a string cell and set its value.
- * If value is null then we will change the cell to a Blank cell.
+ * If value is <code>null</code> then we will change the cell to a Blank cell.
*/
public void setCellValue(RichTextString value)
short styleIndex=record.getXFIndex();
if (hvalue == null)
{
+ notifyFormulaChanging();
setCellType(CELL_TYPE_BLANK, false, row, col, styleIndex);
return;
}
short styleIndex=record.getXFIndex();
if (formula==null) {
+ notifyFormulaChanging();
setCellType(CELL_TYPE_BLANK, false, row, col, styleIndex);
return;
}
setCellType(CELL_TYPE_FORMULA, false, row, col, styleIndex);
- FormulaRecordAggregate rec = (FormulaRecordAggregate) record;
- FormulaRecord frec = rec.getFormulaRecord();
+ FormulaRecordAggregate agg = (FormulaRecordAggregate) record;
+ FormulaRecord frec = agg.getFormulaRecord();
frec.setOptions((short) 2);
frec.setValue(0);
//only set to default if there is no extended format index already set
- if (rec.getXFIndex() == (short)0) {
- rec.setXFIndex((short) 0x0f);
+ if (agg.getXFIndex() == (short)0) {
+ agg.setXFIndex((short) 0x0f);
}
Ptg[] ptgs = HSSFFormulaParser.parse(formula, book);
- frec.setParsedExpression(ptgs);
+ agg.setParsedExpression(ptgs);
+ }
+ /**
+ * Should be called any time that a formula could potentially be deleted.
+ * Does nothing if this cell currently does not hold a formula
+ */
+ private void notifyFormulaChanging() {
+ if (record instanceof FormulaRecordAggregate) {
+ ((FormulaRecordAggregate)record).notifyFormulaChanging();
+ }
}
public String getCellFormula() {
- return HSSFFormulaParser.toFormulaString(book, ((FormulaRecordAggregate)record).getFormulaRecord().getParsedExpression());
+ return HSSFFormulaParser.toFormulaString(book, ((FormulaRecordAggregate)record).getFormulaTokens());
}
/**
\r
import org.apache.poi.hssf.model.HSSFFormulaParser;\r
import org.apache.poi.hssf.model.Workbook;\r
-import org.apache.poi.hssf.record.FormulaRecord;\r
import org.apache.poi.hssf.record.NameRecord;\r
import org.apache.poi.hssf.record.aggregates.FormulaRecordAggregate;\r
import org.apache.poi.hssf.record.formula.NamePtg;\r
// to make sure that all formulas POI can evaluate can also be parsed.\r
return HSSFFormulaParser.parse(cell.getCellFormula(), _uBook);\r
}\r
- FormulaRecord fr = ((FormulaRecordAggregate) cell.getCellValueRecord()).getFormulaRecord();\r
- return fr.getParsedExpression();\r
+ FormulaRecordAggregate fra = (FormulaRecordAggregate) cell.getCellValueRecord();\r
+ return fra.getFormulaTokens();\r
}\r
\r
private static final class Name implements EvaluationName {\r
*/
public short getFillBackgroundColor()
{
- return patternFormatting.getFillBackgroundColor();
+ return (short)patternFormatting.getFillBackgroundColor();
}
/**
*/
public short getFillForegroundColor()
{
- return patternFormatting.getFillForegroundColor();
+ return (short)patternFormatting.getFillForegroundColor();
}
/**
*/
public short getFillPattern()
{
- return patternFormatting.getFillPattern();
+ return (short)patternFormatting.getFillPattern();
}
/**
\r
package org.apache.poi.hssf.util;\r
\r
-import org.apache.poi.hssf.record.RecordInputStream;\r
import org.apache.poi.ss.util.CellRangeAddressBase;\r
-import org.apache.poi.util.LittleEndian;\r
+import org.apache.poi.util.LittleEndianByteArrayOutputStream;\r
+import org.apache.poi.util.LittleEndianInput;\r
+import org.apache.poi.util.LittleEndianOutput;\r
\r
/**\r
* See OOO documentation: excelfileformat.pdf sec 2.5.14 - 'Cell Range Address'<p/>\r
super(firstRow, lastRow, firstCol, lastCol);\r
}\r
\r
- public CellRangeAddress8Bit(RecordInputStream in) {\r
+ public CellRangeAddress8Bit(LittleEndianInput in) {\r
super(readUShortAndCheck(in), in.readUShort(), in.readUByte(), in.readUByte());\r
}\r
\r
- private static int readUShortAndCheck(RecordInputStream in) {\r
- if (in.remaining() < ENCODED_SIZE) {\r
+ private static int readUShortAndCheck(LittleEndianInput in) {\r
+ if (in.available() < ENCODED_SIZE) {\r
// Ran out of data\r
throw new RuntimeException("Ran out of data reading CellRangeAddress");\r
}\r
return in.readUShort();\r
}\r
\r
+ /**\r
+ * @deprecated use {@link #serialize(LittleEndianOutput)}\r
+ */\r
public int serialize(int offset, byte[] data) {\r
- LittleEndian.putUShort(data, offset + 0, getFirstRow());\r
- LittleEndian.putUShort(data, offset + 2, getLastRow());\r
- LittleEndian.putByte(data, offset + 4, getFirstColumn());\r
- LittleEndian.putByte(data, offset + 5, getLastColumn());\r
+ serialize(new LittleEndianByteArrayOutputStream(data, offset, ENCODED_SIZE));\r
return ENCODED_SIZE;\r
}\r
+ public void serialize(LittleEndianOutput out) {\r
+ out.writeShort(getFirstRow());\r
+ out.writeShort(getLastRow());\r
+ out.writeByte(getFirstColumn());\r
+ out.writeByte(getLastColumn());\r
+ }\r
\r
public CellRangeAddress8Bit copy() {\r
return new CellRangeAddress8Bit(getFirstRow(), getLastRow(), getFirstColumn(), getLastColumn());\r
package org.apache.poi.hssf.util;
-import java.util.ArrayList;
-import java.util.List;
-
import org.apache.poi.hssf.record.RecordInputStream;
-import org.apache.poi.util.LittleEndian;
/**
* Implementation of the cell range address lists,like is described
--- /dev/null
+package org.apache.poi.ss.formula;\r
+\r
+import org.apache.poi.hssf.record.formula.Ptg;\r
+import org.apache.poi.util.LittleEndianByteArrayInputStream;\r
+import org.apache.poi.util.LittleEndianInput;\r
+import org.apache.poi.util.LittleEndianOutput;\r
+\r
+public class Formula {\r
+\r
+ private static final byte[] EMPTY_BYTE_ARRAY = { };\r
+ private final byte[] _byteEncoding;\r
+ private final int _encodedTokenLen;\r
+ \r
+ private Formula(byte[] byteEncoding, int encodedTokenLen) {\r
+ _byteEncoding = byteEncoding;\r
+ _encodedTokenLen = encodedTokenLen;\r
+ if (false) { // set to true to eagerly check Ptg decoding \r
+ LittleEndianByteArrayInputStream in = new LittleEndianByteArrayInputStream(byteEncoding);\r
+ Ptg.readTokens(encodedTokenLen, in);\r
+ int nUnusedBytes = _byteEncoding.length - in.getReadIndex();\r
+ if (nUnusedBytes > 0) {\r
+ // TODO - this seems to occur when IntersectionPtg is present\r
+ // This example file "IntersectionPtg.xls"\r
+ // used by test: TestIntersectionPtg.testReading()\r
+ // has 10 bytes unused at the end of the formula\r
+ // 10 extra bytes are just 0x01 and 0x00\r
+ System.out.println(nUnusedBytes + " unused bytes at end of formula");\r
+ }\r
+ }\r
+ }\r
+ /**\r
+ * Convenience method for {@link #read(int, LittleEndianInput, int)}\r
+ */\r
+ public static Formula read(int encodedTokenLen, LittleEndianInput in) {\r
+ return read(encodedTokenLen, in, encodedTokenLen);\r
+ }\r
+ /**\r
+ * When there are no array constants present, <tt>encodedTokenLen</tt>==<tt>totalEncodedLen</tt>\r
+ * @param encodedTokenLen number of bytes in the stream taken by the plain formula tokens\r
+ * @param totalEncodedLen the total number of bytes in the formula (includes trailing encoding\r
+ * for array constants, but does not include 2 bytes for initial <tt>ushort encodedTokenLen</tt> field.\r
+ * @return A new formula object as read from the stream. Possibly empty, never <code>null</code>.\r
+ */\r
+ public static Formula read(int encodedTokenLen, LittleEndianInput in, int totalEncodedLen) {\r
+ byte[] byteEncoding = new byte[totalEncodedLen];\r
+ in.readFully(byteEncoding);\r
+ return new Formula(byteEncoding, encodedTokenLen);\r
+ }\r
+ \r
+ public Ptg[] getTokens() {\r
+ LittleEndianInput in = new LittleEndianByteArrayInputStream(_byteEncoding);\r
+ return Ptg.readTokens(_encodedTokenLen, in);\r
+ }\r
+ /**\r
+ * Writes The formula encoding is includes:\r
+ * <ul>\r
+ * <li>ushort tokenDataLen</li>\r
+ * <li>tokenData</li>\r
+ * <li>arrayConstantData (if present)</li>\r
+ * </ul>\r
+ */\r
+ public void serialize(LittleEndianOutput out) {\r
+ out.writeShort(_encodedTokenLen);\r
+ out.write(_byteEncoding);\r
+ }\r
+\r
+ public void serializeTokens(LittleEndianOutput out) {\r
+ out.write(_byteEncoding, 0, _encodedTokenLen);\r
+ }\r
+ public void serializeArrayConstantData(LittleEndianOutput out) {\r
+ int len = _byteEncoding.length-_encodedTokenLen;\r
+ out.write(_byteEncoding, _encodedTokenLen, len);\r
+ }\r
+ \r
+ \r
+ /**\r
+ * @return total formula encoding length. The formula encoding includes:\r
+ * <ul>\r
+ * <li>ushort tokenDataLen</li>\r
+ * <li>tokenData</li>\r
+ * <li>arrayConstantData (optional)</li>\r
+ * </ul>\r
+ * Note - this value is different to <tt>tokenDataLength</tt>\r
+ */\r
+ public int getEncodedSize() {\r
+ return 2 + _byteEncoding.length;\r
+ }\r
+ /**\r
+ * This method is often used when the formula length does not appear immediately before\r
+ * the encoded token data.\r
+ * \r
+ * @return the encoded length of the plain formula tokens. This does <em>not</em> include\r
+ * the leading ushort field, nor any trailing array constant data.\r
+ */\r
+ public int getEncodedTokenSize() {\r
+ return _encodedTokenLen;\r
+ }\r
+ \r
+ /**\r
+ * Creates a {@link Formula} object from a supplied {@link Ptg} array. \r
+ * Handles <code>null</code>s OK.\r
+ * @param ptgs may be <code>null</code>\r
+ * @return Never <code>null</code> (Possibly empty if the supplied <tt>ptgs</tt> is <code>null</code>)\r
+ */\r
+ public static Formula create(Ptg[] ptgs) {\r
+ if (ptgs == null) {\r
+ return new Formula(EMPTY_BYTE_ARRAY, 0);\r
+ }\r
+ int totalSize = Ptg.getEncodedSize(ptgs);\r
+ byte[] encodedData = new byte[totalSize];\r
+ Ptg.serializePtgs(ptgs, encodedData, 0);\r
+ int encodedTokenLen = Ptg.getEncodedSizeWithoutArrayData(ptgs);\r
+ return new Formula(encodedData, encodedTokenLen);\r
+ }\r
+ /**\r
+ * Gets the {@link Ptg} array from the supplied {@link Formula}. \r
+ * Handles <code>null</code>s OK.\r
+ * \r
+ * @param formula may be <code>null</code>\r
+ * @return possibly <code>null</code> (if the supplied <tt>formula</tt> is <code>null</code>)\r
+ */\r
+ public static Ptg[] getTokens(Formula formula) {\r
+ if (formula == null) {\r
+ return null;\r
+ }\r
+ return formula.getTokens();\r
+ }\r
+ \r
+ public Formula copy() {\r
+ // OK to return this for the moment because currently immutable\r
+ return this;\r
+ }\r
+}\r
import org.apache.poi.hssf.record.RecordInputStream;
import org.apache.poi.hssf.record.SelectionRecord;
-import org.apache.poi.util.LittleEndian;
+import org.apache.poi.util.LittleEndianByteArrayOutputStream;
+import org.apache.poi.util.LittleEndianOutput;
/**
* See OOO documentation: excelfileformat.pdf sec 2.5.14 - 'Cell Range Address'<p/>
super(firstRow, lastRow, firstCol, lastCol);
}
+ /**
+ * @deprecated use {@link #serialize(LittleEndianOutput)}
+ */
public int serialize(int offset, byte[] data) {
- LittleEndian.putUShort(data, offset + 0, getFirstRow());
- LittleEndian.putUShort(data, offset + 2, getLastRow());
- LittleEndian.putUShort(data, offset + 4, getFirstColumn());
- LittleEndian.putUShort(data, offset + 6, getLastColumn());
+ serialize(new LittleEndianByteArrayOutputStream(data, offset, ENCODED_SIZE));
return ENCODED_SIZE;
}
+ public void serialize(LittleEndianOutput out) {
+ out.writeShort(getFirstRow());
+ out.writeShort(getLastRow());
+ out.writeShort(getFirstColumn());
+ out.writeShort(getLastColumn());
+ }
+
public CellRangeAddress(RecordInputStream in) {
super(readUShortAndCheck(in), in.readUShort(), in.readUShort(), in.readUShort());
}
import java.util.List;
import org.apache.poi.hssf.record.RecordInputStream;
-import org.apache.poi.util.LittleEndian;
+import org.apache.poi.util.LittleEndianByteArrayOutputStream;
+import org.apache.poi.util.LittleEndianOutput;
/**
* Implementation of the cell range address lists,like is described
}
public int serialize(int offset, byte[] data) {
- int pos = 2;
-
+ int totalSize = getSize();
+ serialize(new LittleEndianByteArrayOutputStream(data, offset, totalSize));
+ return totalSize;
+ }
+ public void serialize(LittleEndianOutput out) {
int nItems = _list.size();
- LittleEndian.putUShort(data, offset, nItems);
+ out.writeShort(nItems);
for (int k = 0; k < nItems; k++) {
CellRangeAddress region = (CellRangeAddress) _list.get(k);
- pos += region.serialize(offset + pos, data);
+ region.serialize(out);
}
- return getSize();
}
+
public CellRangeAddressList copy() {
CellRangeAddressList result = new CellRangeAddressList();
System.arraycopy(b, 0, _buf, _writeIndex, len);
_writeIndex += len;
}
+ public void write(byte[] b, int offset, int len) {
+ checkPosition(len);
+ System.arraycopy(b, offset, _buf, _writeIndex, len);
+ _writeIndex += len;
+ }
public int getWriteIndex() {
return _writeIndex;
}
void writeInt(int v);\r
void writeLong(long v);\r
void writeDouble(double v);\r
- void write(byte[] data);\r
+ void write(byte[] b);\r
+ void write(byte[] b, int offset, int len);\r
}\r
throw new RuntimeException(e);\r
}\r
}\r
+ public void write(byte[] b, int off, int len) {\r
+ // suppress IOException for interface method\r
+ try {\r
+ super.write(b, off, len);\r
+ } catch (IOException e) {\r
+ throw new RuntimeException(e);\r
+ }\r
+ }\r
}\r
package org.apache.poi.hssf.record;
-import java.io.ByteArrayInputStream;
-
import junit.framework.TestCase;
import org.apache.poi.hssf.record.formula.AttrPtg;
FuncVarPtg choose = (FuncVarPtg)ptgs[8];
assertEquals("CHOOSE", choose.getName());
}
+
+ public void testReserialize() {
+ FormulaRecord formulaRecord = new FormulaRecord();
+ formulaRecord.setRow(1);
+ formulaRecord.setColumn((short) 1);
+ formulaRecord.setParsedExpression(new Ptg[] { new RefPtg("B$5"), });
+ formulaRecord.setValue(3.3);
+ byte[] ser = formulaRecord.serialize();
+ assertEquals(31, ser.length);
+
+ RecordInputStream in = TestcaseRecordInputStream.create(ser);
+ FormulaRecord fr2 = new FormulaRecord(in);
+ assertEquals(3.3, fr2.getValue(), 0.0);
+ Ptg[] ptgs = fr2.getParsedExpression();
+ assertEquals(1, ptgs.length);
+ RefPtg rp = (RefPtg) ptgs[0];
+ assertEquals("B$5", rp.toFormulaString());
+ }
}
package org.apache.poi.hssf.record;
+import org.apache.poi.util.HexRead;
+
import junit.framework.TestCase;
/**
* Tests the NameRecord serializes/deserializes correctly
- *
+ *
* @author Danny Mui (dmui at apache dot org)
*/
public final class TestNameRecord extends TestCase {
- /**
- * Makes sure that additional name information is parsed properly such as menu/description
- */
- public void testFillExtras()
- {
-
- byte[] examples = {
- (byte) 0x88, (byte) 0x03, (byte) 0x67, (byte) 0x06,
- (byte) 0x07, (byte) 0x00, (byte) 0x00, (byte) 0x00,
- (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x23,
- (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x4D,
- (byte) 0x61, (byte) 0x63, (byte) 0x72, (byte) 0x6F,
- (byte) 0x31, (byte) 0x3A, (byte) 0x01, (byte) 0x00,
- (byte) 0x00, (byte) 0x00, (byte) 0x11, (byte) 0x00,
- (byte) 0x00, (byte) 0x4D, (byte) 0x61, (byte) 0x63,
- (byte) 0x72, (byte) 0x6F, (byte) 0x20, (byte) 0x72,
- (byte) 0x65, (byte) 0x63, (byte) 0x6F, (byte) 0x72,
- (byte) 0x64, (byte) 0x65, (byte) 0x64, (byte) 0x20,
- (byte) 0x32, (byte) 0x37, (byte) 0x2D, (byte) 0x53,
- (byte) 0x65, (byte) 0x70, (byte) 0x2D, (byte) 0x39,
- (byte) 0x33, (byte) 0x20, (byte) 0x62, (byte) 0x79,
- (byte) 0x20, (byte) 0x41, (byte) 0x4C, (byte) 0x4C,
- (byte) 0x57, (byte) 0x4F, (byte) 0x52
- };
-
-
- NameRecord name = new NameRecord(TestcaseRecordInputStream.create(NameRecord.sid, examples));
- String description = name.getDescriptionText();
- assertNotNull( description );
- assertTrue( "text contains ALLWOR", description.indexOf( "ALLWOR" ) > 0 );
- }
+ /**
+ * Makes sure that additional name information is parsed properly such as menu/description
+ */
+ public void testFillExtras() {
+
+ byte[] examples = HexRead.readFromString(""
+ + "88 03 67 06 07 00 00 00 00 00 00 23 00 00 00 4D "
+ + "61 63 72 6F 31 3A 01 00 00 00 11 00 00 4D 61 63 "
+ + "72 6F 20 72 65 63 6F 72 64 65 64 20 32 37 2D 53 "
+ + "65 70 2D 39 33 20 62 79 20 41 4C 4C 57 4F 52");
+
+ NameRecord name = new NameRecord(TestcaseRecordInputStream.create(NameRecord.sid, examples));
+ String description = name.getDescriptionText();
+ assertNotNull(description);
+ assertTrue(description.endsWith("Macro recorded 27-Sep-93 by ALLWOR"));
+ }
+
+ public void testReserialize() {
+ byte[] data = HexRead
+ .readFromString(""
+ + "20 00 00 01 0B 00 00 00 01 00 00 00 00 00 00 06 3B 00 00 00 00 02 00 00 00 09 00]");
+ RecordInputStream in = TestcaseRecordInputStream.create(NameRecord.sid, data);
+ NameRecord nr = new NameRecord(in);
+ assertEquals(0x0020, nr.getOptionFlag());
+ byte[] data2 = nr.serialize();
+ TestcaseRecordInputStream.confirmRecordEncoding(NameRecord.sid, data, data2);
+ }
}
-
-
import java.util.Arrays;
import java.util.List;
+import org.apache.poi.util.HexRead;
+
import junit.framework.AssertionFailedError;
import junit.framework.TestCase;
* [ftCmo]
* [ftEnd]
*/
- private static final byte[] recdata = {
- 0x15, 0x00, 0x12, 0x00, 0x06, 0x00, 0x01, 0x00, 0x11, 0x60,
- (byte)0xF4, 0x02, 0x41, 0x01, 0x14, 0x10, 0x1F, 0x02, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- // TODO - this data seems to require two extra bytes padding. not sure where original file is.
- // it's not bug 38607 attachment 17639
- };
-
- private static final byte[] recdataNeedingPadding = {
- 21, 0, 18, 0, 0, 0, 1, 0, 17, 96, 0, 0, 0, 0, 56, 111, -52, 3, 0, 0, 0, 0, 6, 0, 2, 0, 0, 0, 0, 0, 0, 0
- };
+ private static final byte[] recdata = HexRead.readFromString(""
+ + "15 00 12 00 06 00 01 00 11 60 "
+ + "F4 02 41 01 14 10 1F 02 00 00 "
+ +"00 00 00 00 00 00"
+ // TODO - this data seems to require two extra bytes padding. not sure where original file is.
+ // it's not bug 38607 attachment 17639
+ );
+
+ private static final byte[] recdataNeedingPadding = HexRead.readFromString(""
+ + "15 00 12 00 00 00 01 00 11 60 00 00 00 00 38 6F CC 03 00 00 00 00 06 00 02 00 00 00 00 00 00 00"
+ );
public void testLoad() {
ObjRecord record = new ObjRecord(TestcaseRecordInputStream.create(ObjRecord.sid, recdata));
assertEquals(GroupMarkerSubRecord.class, subrecords.get(1).getClass());
assertEquals(EndSubRecord.class, subrecords.get(2).getClass());
}
+
+ /**
+ * Check that ObjRecord tolerates and preserves padding to a 4-byte boundary
+ * (normally padding is to a 2-byte boundary).
+ */
+ public void test4BytePadding() {
+ // actual data from file saved by Excel 2007
+ byte[] data = HexRead.readFromString(""
+ + "15 00 12 00 1E 00 01 00 11 60 B4 6D 3C 01 C4 06 "
+ + "49 06 00 00 00 00 00 00 00 00 00 00");
+ // this data seems to have 2 extra bytes of padding more than usual
+ // the total may have been padded to the nearest quad-byte length
+ RecordInputStream in = TestcaseRecordInputStream.create(ObjRecord.sid, data);
+ // check read OK
+ ObjRecord record = new ObjRecord(in);
+ // check that it re-serializes to the same data
+ byte[] ser = record.serialize();
+ TestcaseRecordInputStream.confirmRecordEncoding(ObjRecord.sid, data, ser);
+ }
}
import junit.framework.ComparisonFailure;
import junit.framework.TestCase;
+import org.apache.poi.hssf.HSSFTestDataSamples;
import org.apache.poi.hssf.record.formula.Ptg;
import org.apache.poi.hssf.record.formula.RefPtg;
+import org.apache.poi.hssf.usermodel.HSSFCell;
+import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator;
+import org.apache.poi.hssf.usermodel.HSSFSheet;
+import org.apache.poi.hssf.usermodel.HSSFWorkbook;
+import org.apache.poi.hssf.usermodel.RecordInspector;
+import org.apache.poi.ss.usermodel.CellValue;
import org.apache.poi.util.LittleEndianInput;
/**
*/
public final class TestSharedFormulaRecord extends TestCase {
+ /**
+ * A sample spreadsheet known to have one sheet with 4 shared formula ranges
+ */
+ private static final String SHARED_FORMULA_TEST_XLS = "SharedFormulaTest.xls";
/**
* Binary data for an encoded formula. Taken from attachment 22062 (bugzilla 45123/45421).
* The shared formula is in Sheet1!C6:C21, with text "SUMPRODUCT(--(End_Acct=$C6),--(End_Bal))"
}
}
}
+
+ /**
+ * Make sure that POI preserves {@link SharedFormulaRecord}s
+ */
+ public void testPreserveOnReserialize() {
+ HSSFWorkbook wb;
+ HSSFSheet sheet;
+ HSSFCell cellB32769;
+ HSSFCell cellC32769;
+
+ // Reading directly from XLS file
+ wb = HSSFTestDataSamples.openSampleWorkbook(SHARED_FORMULA_TEST_XLS);
+ sheet = wb.getSheetAt(0);
+ cellB32769 = sheet.getRow(32768).getCell(1);
+ cellC32769 = sheet.getRow(32768).getCell(2);
+ // check reading of formulas which are shared (two cells from a 1R x 8C range)
+ assertEquals("B32770*2", cellB32769.getCellFormula());
+ assertEquals("C32770*2", cellC32769.getCellFormula());
+ confirmCellEvaluation(wb, cellB32769, 4);
+ confirmCellEvaluation(wb, cellC32769, 6);
+ // Confirm this example really does have SharedFormulas.
+ // there are 3 others besides the one at A32769:H32769
+ assertEquals(4, countSharedFormulas(sheet));
+
+
+ // Re-serialize and check again
+ wb = HSSFTestDataSamples.writeOutAndReadBack(wb);
+ sheet = wb.getSheetAt(0);
+ cellB32769 = sheet.getRow(32768).getCell(1);
+ cellC32769 = sheet.getRow(32768).getCell(2);
+ assertEquals("B32770*2", cellB32769.getCellFormula());
+ confirmCellEvaluation(wb, cellB32769, 4);
+ assertEquals(4, countSharedFormulas(sheet));
+ }
+
+ public void testUnshareFormulaDueToChangeFormula() {
+ HSSFWorkbook wb;
+ HSSFSheet sheet;
+ HSSFCell cellB32769;
+ HSSFCell cellC32769;
+
+ wb = HSSFTestDataSamples.openSampleWorkbook(SHARED_FORMULA_TEST_XLS);
+ sheet = wb.getSheetAt(0);
+ cellB32769 = sheet.getRow(32768).getCell(1);
+ cellC32769 = sheet.getRow(32768).getCell(2);
+
+ // Updating cell formula, causing it to become unshared
+ cellB32769.setCellFormula("1+1");
+ confirmCellEvaluation(wb, cellB32769, 2);
+ // currently (Oct 2008) POI handles this by exploding the whole shared formula group
+ assertEquals(3, countSharedFormulas(sheet)); // one less now
+ // check that nearby cell of the same group still has the same formula
+ assertEquals("C32770*2", cellC32769.getCellFormula());
+ confirmCellEvaluation(wb, cellC32769, 6);
+ }
+ public void testUnshareFormulaDueToDelete() {
+ HSSFWorkbook wb;
+ HSSFSheet sheet;
+ HSSFCell cell;
+ final int ROW_IX = 2;
+
+ // changing shared formula cell to blank
+ wb = HSSFTestDataSamples.openSampleWorkbook(SHARED_FORMULA_TEST_XLS);
+ sheet = wb.getSheetAt(0);
+
+ assertEquals("A$1*2", sheet.getRow(ROW_IX).getCell(1).getCellFormula());
+ cell = sheet.getRow(ROW_IX).getCell(1);
+ cell.setCellType(HSSFCell.CELL_TYPE_BLANK);
+ assertEquals(3, countSharedFormulas(sheet));
+
+ wb = HSSFTestDataSamples.writeOutAndReadBack(wb);
+ sheet = wb.getSheetAt(0);
+ assertEquals("A$1*2", sheet.getRow(ROW_IX+1).getCell(1).getCellFormula());
+
+ // deleting shared formula cell
+ wb = HSSFTestDataSamples.openSampleWorkbook(SHARED_FORMULA_TEST_XLS);
+ sheet = wb.getSheetAt(0);
+
+ assertEquals("A$1*2", sheet.getRow(ROW_IX).getCell(1).getCellFormula());
+ cell = sheet.getRow(ROW_IX).getCell(1);
+ sheet.getRow(ROW_IX).removeCell(cell);
+ assertEquals(3, countSharedFormulas(sheet));
+
+ wb = HSSFTestDataSamples.writeOutAndReadBack(wb);
+ sheet = wb.getSheetAt(0);
+ assertEquals("A$1*2", sheet.getRow(ROW_IX+1).getCell(1).getCellFormula());
+ }
+
+ private static void confirmCellEvaluation(HSSFWorkbook wb, HSSFCell cell, double expectedValue) {
+ HSSFFormulaEvaluator fe = new HSSFFormulaEvaluator(wb);
+ CellValue cv = fe.evaluate(cell);
+ assertEquals(HSSFCell.CELL_TYPE_NUMERIC, cv.getCellType());
+ assertEquals(expectedValue, cv.getNumberValue(), 0.0);
+ }
+
+ /**
+ * @return the number of {@link SharedFormulaRecord}s encoded for the specified sheet
+ */
+ private static int countSharedFormulas(HSSFSheet sheet) {
+ Record[] records = RecordInspector.getRecords(sheet, 0);
+ int count = 0;
+ for (int i = 0; i < records.length; i++) {
+ Record rec = records[i];
+ if(rec instanceof SharedFormulaRecord) {
+ count++;
+ }
+ }
+ return count;
+ }
}
}
- public void testManyRows() {
+ /**
+ * Test for row indexes beyond {@link Short#MAX_VALUE}.
+ * This bug was first fixed in svn r352609.
+ */
+ public void testRowIndexesBeyond32768() {
HSSFWorkbook workbook = new HSSFWorkbook();
HSSFSheet sheet = workbook.createSheet();
HSSFRow row;
HSSFCell cell;
- int i, j;
- for ( i = 0, j = 32771; j > 0; i++, j-- )
- {
+ for (int i = 32700; i < 32771; i++) {
row = sheet.createRow(i);
cell = row.createCell(0);
cell.setCellValue(i);