| /* ==================================================================== |
| Licensed to the Apache Software Foundation (ASF) under one or more |
| contributor license agreements. See the NOTICE file distributed with |
| this work for additional information regarding copyright ownership. |
| The ASF licenses this file to You under the Apache License, Version 2.0 |
| (the "License"); you may not use this file except in compliance with |
| the License. You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| ==================================================================== */ |
| |
| package org.apache.poi.hssf.record; |
| |
| import java.util.Arrays; |
| |
| import org.apache.poi.hssf.record.cf.ColorGradientFormatting; |
| import org.apache.poi.hssf.record.cf.ColorGradientThreshold; |
| import org.apache.poi.hssf.record.cf.DataBarFormatting; |
| import org.apache.poi.hssf.record.cf.DataBarThreshold; |
| import org.apache.poi.hssf.record.cf.IconMultiStateFormatting; |
| import org.apache.poi.hssf.record.cf.IconMultiStateThreshold; |
| import org.apache.poi.hssf.record.cf.Threshold; |
| import org.apache.poi.hssf.record.common.ExtendedColor; |
| import org.apache.poi.hssf.record.common.FtrHeader; |
| import org.apache.poi.hssf.record.common.FutureRecord; |
| 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.ss.usermodel.ConditionalFormattingThreshold.RangeType; |
| import org.apache.poi.ss.usermodel.IconMultiStateFormatting.IconSet; |
| import org.apache.poi.ss.util.CellRangeAddress; |
| import org.apache.poi.util.HexDump; |
| import org.apache.poi.util.LittleEndianOutput; |
| import org.apache.poi.util.POILogger; |
| |
| /** |
| * Conditional Formatting v12 Rule Record (0x087A). |
| * |
| * <p>This is for newer-style Excel conditional formattings, |
| * from Excel 2007 onwards. |
| * |
| * <p>{@link CFRuleRecord} is used where the condition type is |
| * {@link #CONDITION_TYPE_CELL_VALUE_IS} or {@link #CONDITION_TYPE_FORMULA}, |
| * this is only used for the other types |
| */ |
| public final class CFRule12Record extends CFRuleBase implements FutureRecord, Cloneable { |
| public static final short sid = 0x087A; |
| |
| private FtrHeader futureHeader; |
| 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; |
| |
| private DataBarFormatting data_bar; |
| private IconMultiStateFormatting multistate; |
| private ColorGradientFormatting color_gradient; |
| // TODO Parse this, see #58150 |
| private byte[] filter_data; |
| |
| /** Creates new CFRuleRecord */ |
| private CFRule12Record(byte conditionType, byte comparisonOperation) { |
| super(conditionType, comparisonOperation); |
| setDefaults(); |
| } |
| |
| private CFRule12Record(byte conditionType, byte comparisonOperation, Ptg[] formula1, Ptg[] formula2, Ptg[] formulaScale) { |
| super(conditionType, comparisonOperation, formula1, formula2); |
| 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]; |
| } |
| |
| /** |
| * Creates a new comparison operation rule |
| * |
| * @param sheet the sheet |
| * @param formulaText the first formula text |
| * |
| * @return a new comparison operation rule |
| */ |
| public static CFRule12Record create(HSSFSheet sheet, String formulaText) { |
| Ptg[] formula1 = parseFormula(formulaText, sheet); |
| return new CFRule12Record(CONDITION_TYPE_FORMULA, ComparisonOperator.NO_COMPARISON, |
| formula1, null, null); |
| } |
| |
| /** |
| * Creates a new comparison operation rule |
| * |
| * @param sheet the sheet |
| * @param comparisonOperation the comparison operation |
| * @param formulaText1 the first formula text |
| * @param formulaText2 the second formula text |
| * |
| * @return a new comparison operation rule |
| */ |
| public static CFRule12Record create(HSSFSheet sheet, byte comparisonOperation, |
| String formulaText1, String formulaText2) { |
| Ptg[] formula1 = parseFormula(formulaText1, sheet); |
| Ptg[] formula2 = parseFormula(formulaText2, sheet); |
| return new CFRule12Record(CONDITION_TYPE_CELL_VALUE_IS, comparisonOperation, |
| formula1, formula2, null); |
| } |
| |
| /** |
| * Creates a new comparison operation rule |
| * |
| * @param sheet the sheet |
| * @param comparisonOperation the comparison operation |
| * @param formulaText1 the first formula text |
| * @param formulaText2 the second formula text |
| * @param formulaTextScale the scale to apply for the comparison |
| * |
| * @return a new comparison operation rule |
| */ |
| public static CFRule12Record create(HSSFSheet sheet, byte comparisonOperation, |
| String formulaText1, String formulaText2, String formulaTextScale) { |
| Ptg[] formula1 = parseFormula(formulaText1, sheet); |
| Ptg[] formula2 = parseFormula(formulaText2, sheet); |
| Ptg[] formula3 = parseFormula(formulaTextScale, sheet); |
| return new CFRule12Record(CONDITION_TYPE_CELL_VALUE_IS, comparisonOperation, |
| formula1, formula2, formula3); |
| } |
| |
| /** |
| * Creates a new Data Bar formatting |
| * |
| * @param sheet the sheet |
| * @param color the data bar color |
| * |
| * @return a new Data Bar formatting |
| */ |
| public static CFRule12Record create(HSSFSheet sheet, ExtendedColor color) { |
| CFRule12Record r = new CFRule12Record(CONDITION_TYPE_DATA_BAR, |
| ComparisonOperator.NO_COMPARISON); |
| DataBarFormatting dbf = r.createDataBarFormatting(); |
| dbf.setColor(color); |
| dbf.setPercentMin((byte)0); |
| dbf.setPercentMax((byte)100); |
| |
| DataBarThreshold min = new DataBarThreshold(); |
| min.setType(RangeType.MIN.id); |
| dbf.setThresholdMin(min); |
| |
| DataBarThreshold max = new DataBarThreshold(); |
| max.setType(RangeType.MAX.id); |
| dbf.setThresholdMax(max); |
| |
| return r; |
| } |
| |
| /** |
| * Creates a new Icon Set / Multi-State formatting |
| * |
| * @param sheet the sheet |
| * @param iconSet the icon set |
| * |
| * @return a new Icon Set / Multi-State formatting |
| */ |
| public static CFRule12Record create(HSSFSheet sheet, IconSet iconSet) { |
| Threshold[] ts = new Threshold[iconSet.num]; |
| for (int i=0; i<ts.length; i++) { |
| ts[i] = new IconMultiStateThreshold(); |
| } |
| |
| CFRule12Record r = new CFRule12Record(CONDITION_TYPE_ICON_SET, |
| ComparisonOperator.NO_COMPARISON); |
| IconMultiStateFormatting imf = r.createMultiStateFormatting(); |
| imf.setIconSet(iconSet); |
| imf.setThresholds(ts); |
| return r; |
| } |
| |
| /** |
| * Creates a new Color Scale / Color Gradient formatting |
| * |
| * @param sheet the sheet |
| * |
| * @return a new Color Scale / Color Gradient formatting |
| */ |
| public static CFRule12Record createColorScale(HSSFSheet sheet) { |
| int numPoints = 3; |
| ExtendedColor[] colors = new ExtendedColor[numPoints]; |
| ColorGradientThreshold[] ts = new ColorGradientThreshold[numPoints]; |
| for (int i=0; i<ts.length; i++) { |
| ts[i] = new ColorGradientThreshold(); |
| colors[i] = new ExtendedColor(); |
| } |
| |
| CFRule12Record r = new CFRule12Record(CONDITION_TYPE_COLOR_SCALE, |
| ComparisonOperator.NO_COMPARISON); |
| ColorGradientFormatting cgf = r.createColorGradientFormatting(); |
| cgf.setNumControlPoints(numPoints); |
| cgf.setThresholds(ts); |
| cgf.setColors(colors); |
| return r; |
| } |
| |
| public CFRule12Record(RecordInputStream in) { |
| futureHeader = new FtrHeader(in); |
| setConditionType(in.readByte()); |
| setComparisonOperation(in.readByte()); |
| int field_3_formula1_len = in.readUShort(); |
| int field_4_formula2_len = in.readUShort(); |
| |
| 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) { |
| color_gradient = new ColorGradientFormatting(in); |
| } else if (type == CONDITION_TYPE_DATA_BAR) { |
| data_bar = new DataBarFormatting(in); |
| } else if (type == CONDITION_TYPE_FILTER) { |
| filter_data = in.readRemainder(); |
| } else if (type == CONDITION_TYPE_ICON_SET) { |
| multistate = new IconMultiStateFormatting(in); |
| } |
| } |
| |
| public boolean containsDataBarBlock() { |
| return (data_bar != null); |
| } |
| public DataBarFormatting getDataBarFormatting() { |
| return data_bar; |
| } |
| public DataBarFormatting createDataBarFormatting() { |
| if (data_bar != null) return data_bar; |
| |
| // Convert, setup and return |
| setConditionType(CONDITION_TYPE_DATA_BAR); |
| data_bar = new DataBarFormatting(); |
| return data_bar; |
| } |
| |
| public boolean containsMultiStateBlock() { |
| return (multistate != null); |
| } |
| public IconMultiStateFormatting getMultiStateFormatting() { |
| return multistate; |
| } |
| public IconMultiStateFormatting createMultiStateFormatting() { |
| if (multistate != null) return multistate; |
| |
| // Convert, setup and return |
| setConditionType(CONDITION_TYPE_ICON_SET); |
| multistate = new IconMultiStateFormatting(); |
| return multistate; |
| } |
| |
| public boolean containsColorGradientBlock() { |
| return (color_gradient != null); |
| } |
| public ColorGradientFormatting getColorGradientFormatting() { |
| return color_gradient; |
| } |
| public ColorGradientFormatting createColorGradientFormatting() { |
| if (color_gradient != null) return color_gradient; |
| |
| // Convert, setup and return |
| setConditionType(CONDITION_TYPE_COLOR_SCALE); |
| color_gradient = new ColorGradientFormatting(); |
| return color_gradient; |
| } |
| |
| /** |
| * get the stack of the scale 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! |
| */ |
| public Ptg[] getParsedExpressionScale() { |
| return formula_scale.getTokens(); |
| } |
| public void setParsedExpressionScale(Ptg[] ptgs) { |
| formula_scale = Formula.create(ptgs); |
| } |
| |
| public short getSid() { |
| return sid; |
| } |
| |
| /** |
| * called by the class that is responsible for writing this sucker. |
| * Subclasses should implement this so that their data is passed back in a |
| * byte array. |
| * |
| * @param out the stream to write to |
| */ |
| public void serialize(LittleEndianOutput out) { |
| futureHeader.serialize(out); |
| |
| int formula1Len=getFormulaSize(getFormula1()); |
| int formula2Len=getFormulaSize(getFormula2()); |
| |
| out.writeByte(getConditionType()); |
| out.writeByte(getComparisonOperation()); |
| out.writeShort(formula1Len); |
| out.writeShort(formula2Len); |
| |
| // 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); |
| out.writeShort(getFormulaSize(formula_scale)); |
| 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) { |
| color_gradient.serialize(out); |
| } else if (type == CONDITION_TYPE_DATA_BAR) { |
| data_bar.serialize(out); |
| } else if (type == CONDITION_TYPE_FILTER) { |
| out.write(filter_data); |
| } else if (type == CONDITION_TYPE_ICON_SET) { |
| multistate.serialize(out); |
| } |
| } |
| |
| protected int getDataSize() { |
| 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 += 2 + getFormulaSize(formula_scale); |
| len += 6 + template_params.length; |
| |
| byte type = getConditionType(); |
| if (type == CONDITION_TYPE_COLOR_SCALE) { |
| len += color_gradient.getDataLength(); |
| } else if (type == CONDITION_TYPE_DATA_BAR) { |
| len += data_bar.getDataLength(); |
| } else if (type == CONDITION_TYPE_FILTER) { |
| len += filter_data.length; |
| } else if (type == CONDITION_TYPE_ICON_SET) { |
| len += multistate.getDataLength(); |
| } |
| return len; |
| } |
| |
| public String toString() { |
| StringBuffer buffer = new StringBuffer(); |
| buffer.append("[CFRULE12]\n"); |
| buffer.append(" .condition_type=").append(getConditionType()).append("\n"); |
| buffer.append(" .dxfn12_length =0x").append(Integer.toHexString(ext_formatting_length)).append("\n"); |
| buffer.append(" .option_flags =0x").append(Integer.toHexString(getOptions())).append("\n"); |
| if (containsFontFormattingBlock()) { |
| buffer.append(_fontFormatting.toString()).append("\n"); |
| } |
| if (containsBorderFormattingBlock()) { |
| buffer.append(_borderFormatting.toString()).append("\n"); |
| } |
| if (containsPatternFormattingBlock()) { |
| buffer.append(_patternFormatting.toString()).append("\n"); |
| } |
| buffer.append(" .dxfn12_ext=").append(HexDump.toHex(ext_formatting_data)).append("\n"); |
| 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(formula_scale.getTokens())).append("\n"); |
| buffer.append(" .ext_opts =").append(ext_opts).append("\n"); |
| buffer.append(" .priority =").append(priority).append("\n"); |
| buffer.append(" .template_type =").append(template_type).append("\n"); |
| buffer.append(" .template_params=").append(HexDump.toHex(template_params)).append("\n"); |
| buffer.append(" .filter_data =").append(HexDump.toHex(filter_data)).append("\n"); |
| if (color_gradient != null) { |
| buffer.append(color_gradient); |
| } |
| if (multistate != null) { |
| buffer.append(multistate); |
| } |
| if (data_bar != null) { |
| buffer.append(data_bar); |
| } |
| buffer.append("[/CFRULE12]\n"); |
| return buffer.toString(); |
| } |
| |
| @Override |
| public CFRule12Record clone() { |
| CFRule12Record rec = new CFRule12Record(getConditionType(), getComparisonOperation()); |
| rec.futureHeader.setAssociatedRange(futureHeader.getAssociatedRange().copy()); |
| |
| super.copyTo(rec); |
| |
| rec.ext_formatting_length = ext_formatting_length; |
| rec.ext_formatting_data = new byte[ext_formatting_length]; |
| System.arraycopy(ext_formatting_data, 0, rec.ext_formatting_data, 0, ext_formatting_length); |
| |
| rec.formula_scale = formula_scale.copy(); |
| |
| rec.ext_opts = ext_opts; |
| rec.priority = priority; |
| rec.template_type = template_type; |
| rec.template_param_length = template_param_length; |
| rec.template_params = new byte[template_param_length]; |
| System.arraycopy(template_params, 0, rec.template_params, 0, template_param_length); |
| |
| if (color_gradient != null) { |
| rec.color_gradient = (ColorGradientFormatting)color_gradient.clone(); |
| } |
| if (multistate != null) { |
| rec.multistate = (IconMultiStateFormatting)multistate.clone(); |
| } |
| if (data_bar != null) { |
| rec.data_bar = (DataBarFormatting)data_bar.clone(); |
| } |
| if (filter_data != null) { |
| rec.filter_data = new byte[filter_data.length]; |
| System.arraycopy(filter_data, 0, rec.filter_data, 0, filter_data.length); |
| } |
| |
| return rec; |
| } |
| |
| public short getFutureRecordType() { |
| return futureHeader.getRecordType(); |
| } |
| public FtrHeader getFutureHeader() { |
| return futureHeader; |
| } |
| public CellRangeAddress getAssociatedRange() { |
| return futureHeader.getAssociatedRange(); |
| } |
| } |