import org.apache.poi.ss.usermodel.IconMultiStateFormatting.IconSet;
import org.apache.poi.util.BitField;
import org.apache.poi.util.BitFieldFactory;
-import org.apache.poi.util.HexDump;
import org.apache.poi.util.LittleEndianInput;
import org.apache.poi.util.LittleEndianOutput;
import org.apache.poi.util.POILogFactory;
private IconSet iconSet;
private byte options;
- private byte[] states; // TODO Decode
+ private Threshold[] thresholds;
private static BitField iconOnly = BitFieldFactory.getInstance(0x01);
private static BitField reversed = BitFieldFactory.getInstance(0x04);
public IconMultiStateFormatting() {
iconSet = IconSet.GYR_3_TRAFFIC_LIGHTS;
options = 0;
- states = new byte[0];
+ thresholds = new Threshold[iconSet.num];
}
public IconMultiStateFormatting(LittleEndianInput in) {
in.readShort(); // Ignored
log.log(POILogger.WARN, "Inconsistent Icon Set defintion, found " + iconSet + " but defined as " + num + " entries");
}
options = in.readByte();
- // TODO Decode
- states = new byte[in.available()];
- in.readFully(states);
+
+ thresholds = new Threshold[iconSet.num];
+ for (int i=0; i<thresholds.length; i++) {
+ thresholds[i] = new Threshold(in);
+ }
}
public IconSet getIconSet() {
this.iconSet = set;
}
+ public Threshold[] getThresholds() {
+ return thresholds;
+ }
+ public void setThresholds(Threshold[] thresholds) {
+ this.thresholds = thresholds;
+ }
+
public boolean isIconOnly() {
return getOptionFlag(iconOnly);
}
buffer.append(" .icon_set = ").append(iconSet).append("\n");
buffer.append(" .icon_only= ").append(isIconOnly()).append("\n");
buffer.append(" .reversed = ").append(isReversed()).append("\n");
- buffer.append(" .states = ").append(HexDump.toHex(states)).append("\n");
+ for (Threshold t : thresholds) {
+ buffer.append(t.toString());
+ }
buffer.append(" [/Icon Formatting]\n");
return buffer.toString();
}
IconMultiStateFormatting rec = new IconMultiStateFormatting();
rec.iconSet = iconSet;
rec.options = options;
- rec.states = new byte[states.length];
- System.arraycopy(states, 0, rec.states, 0, states.length);
+ rec.thresholds = new Threshold[thresholds.length];
+ System.arraycopy(thresholds, 0, rec.thresholds, 0, thresholds.length);
return rec;
}
public int getDataLength() {
- return 6 + states.length;
+ int len = 6;
+ for (Threshold t : thresholds) {
+ len += t.getDataLength();
+ }
+ return len;
}
public void serialize(LittleEndianOutput out) {
out.writeByte(iconSet.num);
out.writeByte(iconSet.id);
out.writeByte(options);
- out.write(states);
+ for (Threshold t : thresholds) {
+ t.serialize(out);
+ }
}
}
--- /dev/null
+/* ====================================================================
+ 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.cf;
+
+import org.apache.poi.ss.formula.Formula;
+import org.apache.poi.ss.usermodel.ConditionalFormattingThreshold.RangeType;
+import org.apache.poi.util.LittleEndianInput;
+import org.apache.poi.util.LittleEndianOutput;
+
+/**
+ * Threshold / value for changes in Conditional Formatting
+ */
+public final class Threshold {
+ /**
+ * Cell values that are equal to the threshold value do not pass the threshold
+ */
+ public static final byte EQUALS_EXCLUDE = 0;
+ /**
+ * Cell values that are equal to the threshold value pass the threshold.
+ */
+ public static final byte EQUALS_INCLUDE = 1;
+
+ private byte type;
+ private Formula formula;
+ private Double value;
+ private byte equals;
+
+ public Threshold() {
+ type = (byte)RangeType.NUMBER.id;
+ formula = null; // TODO SHould this be empty instead?
+ value = 0d;
+ }
+
+ /** Creates new Threshold */
+ public Threshold(LittleEndianInput in) {
+ type = in.readByte();
+ short formuaLen = in.readShort();
+ if (formuaLen > 0) {
+ formula = Formula.read(formuaLen, in);
+ }
+ // Value is only there for non-formula, non min/max thresholds
+ if (formula == null && type != RangeType.MIN.id &&
+ type != RangeType.MAX.id) {
+ value = in.readDouble();
+ }
+ equals = in.readByte();
+ // Reserved, 4 bytes, all 0
+ in.readInt();
+ }
+
+ public byte getType() {
+ return type;
+ }
+ public void setType(byte type) {
+ this.type = type;
+ }
+
+ public Formula getFormula() {
+ return formula;
+ }
+ public void setFormula(Formula formula) {
+ this.formula = formula;
+ }
+
+ public Double getValue() {
+ return value;
+ }
+ public void setValue(Double value) {
+ this.value = value;
+ }
+
+ public byte getEquals() {
+ return equals;
+ }
+ public void setEquals(byte equals) {
+ this.equals = equals;
+ }
+
+ public int getDataLength() {
+ int len = 1;
+ if (formula != null) {
+ len += formula.getEncodedSize();
+ } else {
+ len += 2;
+ }
+ if (value != null) {
+ len += 8;
+ }
+ len += 5;
+ return len;
+ }
+
+
+ public String toString() {
+ StringBuffer buffer = new StringBuffer();
+ buffer.append(" [CF Threshold]\n");
+ buffer.append(" .type = ").append(Integer.toHexString(type)).append("\n");
+ // TODO Output the formula better
+ buffer.append(" .formula = ").append(formula).append("\n");
+ buffer.append(" .value = ").append(value).append("\n");
+ buffer.append(" [/CF Threshold]\n");
+ return buffer.toString();
+ }
+
+ public Object clone() {
+ Threshold rec = new Threshold();
+ rec.type = type;
+ rec.formula = formula;
+ rec.value = value;
+ rec.equals = equals;
+ return rec;
+ }
+
+ public void serialize(LittleEndianOutput out) {
+ out.writeByte(type);
+ if (formula == null) {
+ out.writeShort(0);
+ } else {
+ formula.serialize(out);
+ }
+ if (value != null) {
+ out.writeDouble(value);
+ }
+ out.writeByte(equals);
+ out.writeInt(0); // Reserved
+ }
+}
// TODO Should this be assigning unique IDs to the rules
// as they get added to the file?
- // TODO Support types beyond CELL_VALUE_IS and FORMULA
-
HSSFConditionalFormatting(HSSFWorkbook workbook, CFRecordsAggregate cfAggregate) {
if(workbook == null) {
throw new IllegalArgumentException("workbook must not be null");
/**
* Replaces an existing Conditional Formatting rule at position idx.
- * Excel allows to create up to 3 Conditional Formatting rules.
+ * Older versions of Excel only allow up to 3 Conditional Formatting rules,
+ * and will ignore rules beyond that, while newer versions are fine.
* This method can be useful to modify existing Conditional Formatting rules.
*
- * @param idx position of the rule. Should be between 0 and 2.
+ * @param idx position of the rule. Should be between 0 and 2 for older Excel versions
* @param cfRule - Conditional Formatting rule
*/
public void setRule(int idx, HSSFConditionalFormattingRule cfRule) {
--- /dev/null
+/* ====================================================================
+ 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.usermodel;
+
+import org.apache.poi.hssf.record.cf.Threshold;
+import org.apache.poi.ss.formula.Formula;
+
+/**
+ * High level representation for Icon / Multi-State / Databar /
+ * Colour Scale change thresholds
+ */
+public final class HSSFConditionalFormattingThreshold implements org.apache.poi.ss.usermodel.ConditionalFormattingThreshold {
+ private final Threshold threshold;
+
+ protected HSSFConditionalFormattingThreshold(Threshold threshold) {
+ this.threshold = threshold;
+ }
+ protected Threshold getThreshold() {
+ return threshold;
+ }
+
+ public RangeType getRangeType() {
+ return RangeType.byId(threshold.getType());
+ }
+ public void setRangeType(RangeType type) {
+ threshold.setType((byte)type.id);
+ }
+
+ public Formula getFormula() {
+ return threshold.getFormula();
+ }
+ public void setFormula(Formula formula) {
+ threshold.setFormula(formula);
+ }
+
+ public Double getValue() {
+ return threshold.getValue();
+ }
+ public void setValue(Double value) {
+ threshold.setValue(value);
+ }
+}
import org.apache.poi.hssf.record.CFRule12Record;
import org.apache.poi.hssf.record.cf.IconMultiStateFormatting;
+import org.apache.poi.hssf.record.cf.Threshold;
+import org.apache.poi.ss.usermodel.ConditionalFormattingThreshold;
/**
* High level representation for Icon / Multi-State Formatting
public void setReversed(boolean reversed) {
iconFormatting.setReversed(reversed);
}
+
+ public ConditionalFormattingThreshold[] getThresholds() {
+ Threshold[] t = iconFormatting.getThresholds();
+ HSSFConditionalFormattingThreshold[] ht = new HSSFConditionalFormattingThreshold[t.length];
+ for (int i=0; i<t.length; i++) {
+ ht[i] = new HSSFConditionalFormattingThreshold(t[i]);
+ }
+ return ht;
+ }
+
+ public void setThresholds(ConditionalFormattingThreshold[] thresholds) {
+ Threshold[] t = new Threshold[thresholds.length];
+ for (int i=0; i<t.length; i++) {
+ t[i] = ((HSSFConditionalFormattingThreshold)thresholds[i]).getThreshold();
+ }
+ iconFormatting.setThresholds(t);
+ }
}
return new HSSFConditionalFormattingRule(wb, rr);
}
+ // TODO Support types beyond CELL_VALUE_IS and FORMULA
+
/**
* A factory method allowing the creation of conditional formatting
* rules using an Icon Set / Multi-State formatting/
--- /dev/null
+/*\r
+ * ====================================================================\r
+ * Licensed to the Apache Software Foundation (ASF) under one or more\r
+ * contributor license agreements. See the NOTICE file distributed with\r
+ * this work for additional information regarding copyright ownership.\r
+ * The ASF licenses this file to You under the Apache License, Version 2.0\r
+ * (the "License"); you may not use this file except in compliance with\r
+ * the License. You may obtain a copy of the License at\r
+ *\r
+ * http://www.apache.org/licenses/LICENSE-2.0\r
+ *\r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ * ====================================================================\r
+ */\r
+\r
+package org.apache.poi.ss.usermodel;\r
+\r
+import org.apache.poi.ss.formula.Formula;\r
+\r
+/**\r
+ * The Threshold / CFVO / Conditional Formatting Value Object.\r
+ * <p>This defines how to calculate the ranges for a conditional\r
+ * formatting rule, eg which values get a Green Traffic Light\r
+ * icon and which Yellow or Red.</p>\r
+ */\r
+public interface ConditionalFormattingThreshold {\r
+ public enum RangeType {\r
+ /** Number / Parameter */\r
+ NUMBER(1, "num"),\r
+ /** The minimum value from the range */\r
+ MIN(2, "min"),\r
+ /** The maximum value from the range */\r
+ MAX(3, "max"),\r
+ /** Percent of the way from the mi to the max value in the range */\r
+ PERCENT(4, "percent"),\r
+ /** The minimum value of the cell that is in X percentile of the range */\r
+ PERCENTILE(5, "percentile"),\r
+ UNALLOCATED(6, null),\r
+ /** Formula result */\r
+ FORMULA(7, "formula");\r
+ \r
+ /** Numeric ID of the type */\r
+ public int id;\r
+ /** Name (system) of the type */\r
+ public final String name;\r
+ \r
+ public String toString() {\r
+ return id + " - " + name;\r
+ }\r
+ \r
+ public static RangeType byId(int id) {\r
+ return values()[id-1]; // 1-based IDs\r
+ }\r
+ \r
+ private RangeType(int id, String name) {\r
+ this.id = id; this.name = name;\r
+ }\r
+ }\r
+ \r
+ /**\r
+ * Get the Range Type used\r
+ */\r
+ RangeType getRangeType();\r
+ \r
+ /**\r
+ * Changes the Range Type used\r
+ * \r
+ * <p>If you change the range type, you need to\r
+ * ensure that the Formula and Value parameters\r
+ * are compatible with it before saving</p>\r
+ */\r
+ void setRangeType(RangeType type);\r
+ \r
+ /**\r
+ * Formula to use to calculate the threshold,\r
+ * or <code>null</code> if no formula \r
+ */\r
+ Formula getFormula();\r
+\r
+ /**\r
+ * Sets the formula used to calculate the threshold,\r
+ * or unsets it if <code>null</code> is given.\r
+ */\r
+ void setFormula(Formula formula);\r
+ \r
+ /**\r
+ * Gets the value used for the threshold, or \r
+ * <code>null</code> if there isn't one.\r
+ */\r
+ Double getValue();\r
+ \r
+ /**\r
+ * Sets the value used for the threshold. \r
+ * <p>If the type is {@link RangeType#PERCENT} or \r
+ * {@link RangeType#PERCENTILE} it must be between 0 and 100.\r
+ * <p>If the type is {@link RangeType#MIN} or {@link RangeType#MAX}\r
+ * or {@link RangeType#FORMULA} it shouldn't be set.\r
+ * <p>Use <code>null</code> to unset\r
+ */\r
+ void setValue(Double value);\r
+}\r
boolean isReversed();\r
void setReversed(boolean reversed);\r
\r
- // TODO States\r
+ /**\r
+ * Gets the list of thresholds\r
+ */\r
+ ConditionalFormattingThreshold[] getThresholds();\r
+ /**\r
+ * Sets the of thresholds. The number must match\r
+ * {@link IconSet#num} for the current {@link #getIconSet()}\r
+ */\r
+ void setThresholds(ConditionalFormattingThreshold[] thresholds);\r
}\r