diff --git a/src/java/org/apache/poi/hssf/record/CFRule12Record.java b/src/java/org/apache/poi/hssf/record/CFRule12Record.java index 0b896fce8..c5afb3e4e 100644 --- a/src/java/org/apache/poi/hssf/record/CFRule12Record.java +++ b/src/java/org/apache/poi/hssf/record/CFRule12Record.java @@ -24,6 +24,7 @@ import org.apache.poi.hssf.usermodel.HSSFSheet; import org.apache.poi.ss.formula.Formula; import org.apache.poi.ss.formula.ptg.Ptg; import org.apache.poi.util.LittleEndianOutput; +import org.apache.poi.util.POILogger; /** * Conditional Formatting v12 Rule Record (0x087A). @@ -39,20 +40,46 @@ public final class CFRule12Record extends CFRuleBase { public static final short sid = 0x087A; private FtrHeader futureHeader; - private Formula formulaScale; + private int ext_formatting_length; + private byte[] ext_formatting_data; + private Formula formula_scale; + private byte ext_opts; + private int priority; + private int template_type; + private byte template_param_length; + private byte[] template_params; + + // TODO Parse these + private byte[] gradient_data; + private byte[] databar_data; + private byte[] filter_data; + private byte[] multistate_data; /** Creates new CFRuleRecord */ private CFRule12Record(byte conditionType, byte comparisonOperation) { super(conditionType, comparisonOperation); - futureHeader = new FtrHeader(); - futureHeader.setRecordType(sid); - // TODO Remaining fields + setDefaults(); } private CFRule12Record(byte conditionType, byte comparisonOperation, Ptg[] formula1, Ptg[] formula2, Ptg[] formulaScale) { super(conditionType, comparisonOperation, formula1, formula2); - this.formulaScale = Formula.create(formulaScale); - // TODO Remaining fields + setDefaults(); + this.formula_scale = Formula.create(formulaScale); + } + private void setDefaults() { + futureHeader = new FtrHeader(); + futureHeader.setRecordType(sid); + + ext_formatting_length = 0; + ext_formatting_data = new byte[4]; + + formula_scale = Formula.create(Ptg.EMPTY_PTG_ARRAY); + + ext_opts = 0; + priority = 0; + template_type = getConditionType(); + template_param_length = 16; + template_params = new byte[template_param_length]; } /** @@ -92,7 +119,47 @@ public final class CFRule12Record extends CFRuleBase { int field_3_formula1_len = in.readUShort(); int field_4_formula2_len = in.readUShort(); - // TODO Handle the remainder + ext_formatting_length = in.readInt(); + ext_formatting_data = new byte[0]; + if (ext_formatting_length == 0) { + // 2 bytes reserved + in.readUShort(); + } else { + int len = readFormatOptions(in); + if (len < ext_formatting_length) { + ext_formatting_data = new byte[ext_formatting_length-len]; + in.readFully(ext_formatting_data); + } + } + + setFormula1(Formula.read(field_3_formula1_len, in)); + setFormula2(Formula.read(field_4_formula2_len, in)); + + int formula_scale_len = in.readUShort(); + formula_scale = Formula.read(formula_scale_len, in); + + ext_opts = in.readByte(); + priority = in.readUShort(); + template_type = in.readUShort(); + template_param_length = in.readByte(); + if (template_param_length == 0 || template_param_length == 16) { + template_params = new byte[template_param_length]; + in.readFully(template_params); + } else { + logger.log(POILogger.WARN, "CF Rule v12 template params length should be 0 or 16, found " + template_param_length); + in.readRemainder(); + } + + byte type = getConditionType(); + if (type == CONDITION_TYPE_COLOR_SCALE) { + gradient_data = in.readRemainder(); + } else if (type == CONDITION_TYPE_DATA_BAR) { + databar_data = in.readRemainder(); + } else if (type == CONDITION_TYPE_FILTER) { + filter_data = in.readRemainder(); + } else if (type == CONDITION_TYPE_ICON_SET) { + multistate_data = in.readRemainder(); + } } /** @@ -104,10 +171,10 @@ public final class CFRule12Record extends CFRuleBase { * callers should check for null! */ public Ptg[] getParsedExpressionScale() { - return formulaScale.getTokens(); + return formula_scale.getTokens(); } public void setParsedExpressionScale(Ptg[] ptgs) { - formulaScale = Formula.create(ptgs); + formula_scale = Formula.create(ptgs); } public short getSid() { @@ -132,22 +199,71 @@ public final class CFRule12Record extends CFRuleBase { out.writeShort(formula1Len); out.writeShort(formula2Len); - // TODO Output the rest + // TODO Update ext_formatting_length + if (ext_formatting_length == 0) { + out.writeInt(0); + out.writeShort(0); + } else { + out.writeInt(ext_formatting_length); + serializeFormattingBlock(out); + out.write(ext_formatting_data); + } + + getFormula1().serializeTokens(out); + getFormula2().serializeTokens(out); + formula_scale.serializeTokens(out); + + out.writeByte(ext_opts); + out.writeShort(priority); + out.writeShort(template_type); + out.writeByte(template_param_length); + out.write(template_params); + + byte type = getConditionType(); + if (type == CONDITION_TYPE_COLOR_SCALE) { + out.write(gradient_data); + } else if (type == CONDITION_TYPE_DATA_BAR) { + out.write(databar_data); + } else if (type == CONDITION_TYPE_FILTER) { + out.write(filter_data); + } else if (type == CONDITION_TYPE_ICON_SET) { + out.write(multistate_data); + } } protected int getDataSize() { - // TODO Calculate - return 0; + int len = FtrHeader.getDataSize() + 6; + if (ext_formatting_length == 0) { + len += 6; + } else { + len += 4 + getFormattingBlockSize() + ext_formatting_data.length; + } + len += getFormulaSize(getFormula1()); + len += getFormulaSize(getFormula2()); + len += 4 + getFormulaSize(formula_scale); + len += 6 + template_params.length; + + byte type = getConditionType(); + if (type == CONDITION_TYPE_COLOR_SCALE) { + len += gradient_data.length; + } else if (type == CONDITION_TYPE_DATA_BAR) { + len += databar_data.length; + } else if (type == CONDITION_TYPE_FILTER) { + len += filter_data.length; + } else if (type == CONDITION_TYPE_ICON_SET) { + len += multistate_data.length; + } + return len; } public String toString() { StringBuffer buffer = new StringBuffer(); buffer.append("[CFRULE12]\n"); buffer.append(" .condition_type =").append(getConditionType()).append("\n"); - buffer.append(" TODO The rest!\n"); + buffer.append(" TODO The rest!\n"); // TODO The Rest buffer.append(" Formula 1 =").append(Arrays.toString(getFormula1().getTokens())).append("\n"); buffer.append(" Formula 2 =").append(Arrays.toString(getFormula2().getTokens())).append("\n"); - buffer.append(" Formula S =").append(Arrays.toString(formulaScale.getTokens())).append("\n"); + buffer.append(" Formula S =").append(Arrays.toString(formula_scale.getTokens())).append("\n"); buffer.append("[/CFRULE12]\n"); return buffer.toString(); } @@ -158,7 +274,7 @@ public final class CFRule12Record extends CFRuleBase { // TODO The other fields - rec.formulaScale = formulaScale.copy(); + rec.formula_scale = formula_scale.copy(); return rec; } diff --git a/src/java/org/apache/poi/hssf/record/CFRuleBase.java b/src/java/org/apache/poi/hssf/record/CFRuleBase.java index f43b0e36a..b7db3a7a0 100644 --- a/src/java/org/apache/poi/hssf/record/CFRuleBase.java +++ b/src/java/org/apache/poi/hssf/record/CFRuleBase.java @@ -27,6 +27,9 @@ import org.apache.poi.ss.formula.FormulaType; import org.apache.poi.ss.formula.ptg.Ptg; import org.apache.poi.util.BitField; import org.apache.poi.util.BitFieldFactory; +import org.apache.poi.util.LittleEndianOutput; +import org.apache.poi.util.POILogFactory; +import org.apache.poi.util.POILogger; /** * Conditional Formatting Rules. This can hold old-style rules @@ -49,6 +52,7 @@ public abstract class CFRuleBase extends StandardRecord { public static final byte LE = 8; private static final byte max_operator = 8; } + protected static final POILogger logger = POILogFactory.getLogger(CFRuleBase.class); private byte condition_type; // The only kinds that CFRuleRecord handles @@ -91,41 +95,41 @@ public abstract class CFRuleBase extends StandardRecord { public static final int TEMPLATE_BELOW_OR_EQUAL_TO_AVERAGE = 0x001E; static final BitField modificationBits = bf(0x003FFFFF); // Bits: font,align,bord,patt,prot - static final BitField alignHor = bf(0x00000001); // 0 = Horizontal alignment modified - static final BitField alignVer = bf(0x00000002); // 0 = Vertical alignment modified - static final BitField alignWrap = bf(0x00000004); // 0 = Text wrapped flag modified - static final BitField alignRot = bf(0x00000008); // 0 = Text rotation modified - static final BitField alignJustLast = bf(0x00000010); // 0 = Justify last line flag modified - static final BitField alignIndent = bf(0x00000020); // 0 = Indentation modified - static final BitField alignShrin = bf(0x00000040); // 0 = Shrink to fit flag modified - static final BitField notUsed1 = bf(0x00000080); // Always 1 - static final BitField protLocked = bf(0x00000100); // 0 = Cell locked flag modified - static final BitField protHidden = bf(0x00000200); // 0 = Cell hidden flag modified - static final BitField bordLeft = bf(0x00000400); // 0 = Left border style and colour modified - static final BitField bordRight = bf(0x00000800); // 0 = Right border style and colour modified - static final BitField bordTop = bf(0x00001000); // 0 = Top border style and colour modified - static final BitField bordBot = bf(0x00002000); // 0 = Bottom border style and colour modified - static final BitField bordTlBr = bf(0x00004000); // 0 = Top-left to bottom-right border flag modified - static final BitField bordBlTr = bf(0x00008000); // 0 = Bottom-left to top-right border flag modified - static final BitField pattStyle = bf(0x00010000); // 0 = Pattern style modified - static final BitField pattCol = bf(0x00020000); // 0 = Pattern colour modified - static final BitField pattBgCol = bf(0x00040000); // 0 = Pattern background colour modified - static final BitField notUsed2 = bf(0x00380000); // Always 111 - static final BitField undocumented = bf(0x03C00000); // Undocumented bits - static final BitField fmtBlockBits = bf(0x7C000000); // Bits: font,align,bord,patt,prot - static final BitField font = bf(0x04000000); // 1 = Record contains font formatting block - static final BitField align = bf(0x08000000); // 1 = Record contains alignment formatting block - static final BitField bord = bf(0x10000000); // 1 = Record contains border formatting block - static final BitField patt = bf(0x20000000); // 1 = Record contains pattern formatting block - static final BitField prot = bf(0x40000000); // 1 = Record contains protection formatting block - static final BitField alignTextDir = bf(0x80000000); // 0 = Text direction modified + static final BitField alignHor = bf(0x00000001); // 0 = Horizontal alignment modified + static final BitField alignVer = bf(0x00000002); // 0 = Vertical alignment modified + static final BitField alignWrap = bf(0x00000004); // 0 = Text wrapped flag modified + static final BitField alignRot = bf(0x00000008); // 0 = Text rotation modified + static final BitField alignJustLast = bf(0x00000010); // 0 = Justify last line flag modified + static final BitField alignIndent = bf(0x00000020); // 0 = Indentation modified + static final BitField alignShrin = bf(0x00000040); // 0 = Shrink to fit flag modified + static final BitField mergeCell = bf(0x00000080); // Normally 1, 0 = Merge Cell flag modified + static final BitField protLocked = bf(0x00000100); // 0 = Cell locked flag modified + static final BitField protHidden = bf(0x00000200); // 0 = Cell hidden flag modified + static final BitField bordLeft = bf(0x00000400); // 0 = Left border style and colour modified + static final BitField bordRight = bf(0x00000800); // 0 = Right border style and colour modified + static final BitField bordTop = bf(0x00001000); // 0 = Top border style and colour modified + static final BitField bordBot = bf(0x00002000); // 0 = Bottom border style and colour modified + static final BitField bordTlBr = bf(0x00004000); // 0 = Top-left to bottom-right border flag modified + static final BitField bordBlTr = bf(0x00008000); // 0 = Bottom-left to top-right border flag modified + static final BitField pattStyle = bf(0x00010000); // 0 = Pattern style modified + static final BitField pattCol = bf(0x00020000); // 0 = Pattern colour modified + static final BitField pattBgCol = bf(0x00040000); // 0 = Pattern background colour modified + static final BitField notUsed2 = bf(0x00380000); // Always 111 (ifmt / ifnt / 1) + static final BitField undocumented = bf(0x03C00000); // Undocumented bits + static final BitField fmtBlockBits = bf(0x7C000000); // Bits: font,align,bord,patt,prot + static final BitField font = bf(0x04000000); // 1 = Record contains font formatting block + static final BitField align = bf(0x08000000); // 1 = Record contains alignment formatting block + static final BitField bord = bf(0x10000000); // 1 = Record contains border formatting block + static final BitField patt = bf(0x20000000); // 1 = Record contains pattern formatting block + static final BitField prot = bf(0x40000000); // 1 = Record contains protection formatting block + static final BitField alignTextDir = bf(0x80000000); // 0 = Text direction modified private static BitField bf(int i) { return BitFieldFactory.getInstance(i); } - protected int field_5_options; // TODO Rename me - protected short field_6_not_used; // TODO Rename me + protected int formatting_options; + protected short formatting_not_used; // TODO Decode this properly protected FontFormatting _fontFormatting; protected BorderFormatting _borderFormatting; @@ -149,8 +153,8 @@ public abstract class CFRuleBase extends StandardRecord { protected CFRuleBase() {} protected int readFormatOptions(RecordInputStream in) { - field_5_options = in.readInt(); - field_6_not_used = in.readShort(); + formatting_options = in.readInt(); + formatting_not_used = in.readShort(); int len = 6; @@ -261,14 +265,14 @@ public abstract class CFRuleBase extends StandardRecord { * @return bit mask */ public int getOptions() { - return field_5_options; + return formatting_options; } private boolean isModified(BitField field) { - return !field.isSet(field_5_options); + return !field.isSet(formatting_options); } private void setModified(boolean modified, BitField field) { - field_5_options = field.setBoolean(field_5_options, !modified); + formatting_options = field.setBoolean(formatting_options, !modified); } public boolean isLeftBorderModified() { @@ -336,10 +340,34 @@ public abstract class CFRuleBase extends StandardRecord { } private boolean getOptionFlag(BitField field) { - return field.isSet(field_5_options); + return field.isSet(formatting_options); } private void setOptionFlag(boolean flag, BitField field) { - field_5_options = field.setBoolean(field_5_options, flag); + formatting_options = field.setBoolean(formatting_options, flag); + } + + protected int getFormattingBlockSize() { + return + (containsFontFormattingBlock()?_fontFormatting.getRawRecord().length:0)+ + (containsBorderFormattingBlock()?8:0)+ + (containsPatternFormattingBlock()?4:0); + } + protected void serializeFormattingBlock(LittleEndianOutput out) { + out.writeInt(formatting_options); + out.writeShort(formatting_not_used); + + if (containsFontFormattingBlock()) { + byte[] fontFormattingRawRecord = _fontFormatting.getRawRecord(); + out.write(fontFormattingRawRecord); + } + + if (containsBorderFormattingBlock()) { + _borderFormatting.serialize(out); + } + + if (containsPatternFormattingBlock()) { + _patternFormatting.serialize(out); + } } /** @@ -409,8 +437,8 @@ public abstract class CFRuleBase extends StandardRecord { rec.condition_type = condition_type; rec.comparison_operator = comparison_operator; - rec.field_5_options = field_5_options; - rec.field_6_not_used = field_6_not_used; + rec.formatting_options = formatting_options; + rec.formatting_not_used = formatting_not_used; if (containsFontFormattingBlock()) { rec._fontFormatting = (FontFormatting) _fontFormatting.clone(); } diff --git a/src/java/org/apache/poi/hssf/record/CFRuleRecord.java b/src/java/org/apache/poi/hssf/record/CFRuleRecord.java index f13685abb..a00fde731 100644 --- a/src/java/org/apache/poi/hssf/record/CFRuleRecord.java +++ b/src/java/org/apache/poi/hssf/record/CFRuleRecord.java @@ -46,12 +46,12 @@ public final class CFRuleRecord extends CFRuleBase { } private void setDefaults() { // Set modification flags to 1: by default options are not modified - field_5_options = modificationBits.setValue(field_5_options, -1); + formatting_options = modificationBits.setValue(formatting_options, -1); // Set formatting block flags to 0 (no formatting blocks) - field_5_options = fmtBlockBits.setValue(field_5_options, 0); - field_5_options = undocumented.clear(field_5_options); + formatting_options = fmtBlockBits.setValue(formatting_options, 0); + formatting_options = undocumented.clear(formatting_options); - field_6_not_used = (short)0x8002; // Excel seems to write this value, but it doesn't seem to care what it reads + formatting_not_used = (short)0x8002; // Excel seems to write this value, but it doesn't seem to care what it reads _fontFormatting = null; _borderFormatting = null; _patternFormatting = null; @@ -106,34 +106,17 @@ public final class CFRuleRecord extends CFRuleBase { out.writeByte(getComparisonOperation()); out.writeShort(formula1Len); out.writeShort(formula2Len); - out.writeInt(field_5_options); - out.writeShort(field_6_not_used); - - if (containsFontFormattingBlock()) { - byte[] fontFormattingRawRecord = _fontFormatting.getRawRecord(); - out.write(fontFormattingRawRecord); - } - - if (containsBorderFormattingBlock()) { - _borderFormatting.serialize(out); - } - - if (containsPatternFormattingBlock()) { - _patternFormatting.serialize(out); - } + + serializeFormattingBlock(out); getFormula1().serializeTokens(out); getFormula2().serializeTokens(out); } protected int getDataSize() { - int i = 12 + - (containsFontFormattingBlock()?_fontFormatting.getRawRecord().length:0)+ - (containsBorderFormattingBlock()?8:0)+ - (containsPatternFormattingBlock()?4:0)+ - getFormulaSize(getFormula1())+ - getFormulaSize(getFormula2()); - return i; + return 12 + getFormattingBlockSize() + + getFormulaSize(getFormula1())+ + getFormulaSize(getFormula2()); } public String toString() {