]> source.dussan.org Git - poi.git/commitdiff
Start on conditional formatting thresholds
authorNick Burch <nick@apache.org>
Thu, 16 Jul 2015 19:34:06 +0000 (19:34 +0000)
committerNick Burch <nick@apache.org>
Thu, 16 Jul 2015 19:34:06 +0000 (19:34 +0000)
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1691434 13f79535-47bb-0310-9956-ffa450edef68

src/java/org/apache/poi/hssf/record/cf/IconMultiStateFormatting.java
src/java/org/apache/poi/hssf/record/cf/Threshold.java [new file with mode: 0644]
src/java/org/apache/poi/hssf/usermodel/HSSFConditionalFormatting.java
src/java/org/apache/poi/hssf/usermodel/HSSFConditionalFormattingThreshold.java [new file with mode: 0644]
src/java/org/apache/poi/hssf/usermodel/HSSFIconMultiStateFormatting.java
src/java/org/apache/poi/hssf/usermodel/HSSFSheetConditionalFormatting.java
src/java/org/apache/poi/ss/usermodel/ConditionalFormattingThreshold.java [new file with mode: 0644]
src/java/org/apache/poi/ss/usermodel/IconMultiStateFormatting.java

index 33390a6f08489047df872b4f34e6100ce5357ac9..f405c031e0150c5b41000e83793bb3130bd928cd 100644 (file)
@@ -20,7 +20,6 @@ package org.apache.poi.hssf.record.cf;
 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;
@@ -34,7 +33,7 @@ public final class IconMultiStateFormatting implements Cloneable {
             
     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);
@@ -42,7 +41,7 @@ public final class IconMultiStateFormatting implements Cloneable {
     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
@@ -54,9 +53,11 @@ public final class IconMultiStateFormatting implements Cloneable {
             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() {
@@ -66,6 +67,13 @@ public final class IconMultiStateFormatting implements Cloneable {
         this.iconSet = set;
     }
 
+    public Threshold[] getThresholds() {
+        return thresholds;
+    }
+    public void setThresholds(Threshold[] thresholds) {
+        this.thresholds = thresholds;
+    }
+    
     public boolean isIconOnly() {
         return getOptionFlag(iconOnly);
     }
@@ -94,7 +102,9 @@ public final class IconMultiStateFormatting implements Cloneable {
         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();
     }
@@ -103,13 +113,17 @@ public final class IconMultiStateFormatting implements Cloneable {
       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) {
@@ -118,6 +132,8 @@ public final class IconMultiStateFormatting implements Cloneable {
         out.writeByte(iconSet.num);
         out.writeByte(iconSet.id);
         out.writeByte(options);
-        out.write(states);
+        for (Threshold t : thresholds) {
+            t.serialize(out);
+        }
     }
 }
diff --git a/src/java/org/apache/poi/hssf/record/cf/Threshold.java b/src/java/org/apache/poi/hssf/record/cf/Threshold.java
new file mode 100644 (file)
index 0000000..208b21f
--- /dev/null
@@ -0,0 +1,142 @@
+/* ====================================================================
+   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
+    }
+}
index 17bed7f6bf0fb4f1c2c57b785c04272e5a27faf7..67658bae4fd1b98476421378cf7d08a37fcb5490 100644 (file)
@@ -80,8 +80,6 @@ public final class HSSFConditionalFormatting  implements ConditionalFormatting {
     // 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");
@@ -112,10 +110,11 @@ public final class HSSFConditionalFormatting  implements ConditionalFormatting {
 
     /**
      * 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) {
diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFConditionalFormattingThreshold.java b/src/java/org/apache/poi/hssf/usermodel/HSSFConditionalFormattingThreshold.java
new file mode 100644 (file)
index 0000000..7b21d93
--- /dev/null
@@ -0,0 +1,57 @@
+/* ====================================================================
+   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);
+    }
+}
index 18733e17ada04e9aa345f1734d3b91ec58b66d07..6e2ea49ad01f5a085be861b0cfafa9dd65599402 100644 (file)
@@ -19,6 +19,8 @@ package org.apache.poi.hssf.usermodel;
 
 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 
@@ -53,4 +55,21 @@ public final class HSSFIconMultiStateFormatting implements org.apache.poi.ss.use
     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);
+    }
 }
index f0df8e52233ca3f19d516ab777ab8b59970d23d8..f6e71e363aec59cd5c506f7ef61f7c7e4d4a875b 100644 (file)
@@ -97,6 +97,8 @@ public final class HSSFSheetConditionalFormatting implements SheetConditionalFor
                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/
diff --git a/src/java/org/apache/poi/ss/usermodel/ConditionalFormattingThreshold.java b/src/java/org/apache/poi/ss/usermodel/ConditionalFormattingThreshold.java
new file mode 100644 (file)
index 0000000..a614e42
--- /dev/null
@@ -0,0 +1,105 @@
+/*\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
index d4762c790cd75748cf95fdbeaef616aefd3d1b61..cdd46d0573e9bd4cd3395e15ca6d80f9f8a05120 100644 (file)
@@ -104,5 +104,13 @@ public interface IconMultiStateFormatting {
     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