diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFConditionalFormattingRule.java b/src/java/org/apache/poi/hssf/usermodel/HSSFConditionalFormattingRule.java index b52ea9102..83a5b2569 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFConditionalFormattingRule.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFConditionalFormattingRule.java @@ -29,6 +29,8 @@ import org.apache.poi.hssf.record.cf.FontFormatting; import org.apache.poi.hssf.record.cf.IconMultiStateFormatting; import org.apache.poi.hssf.record.cf.PatternFormatting; import org.apache.poi.ss.formula.ptg.Ptg; +import org.apache.poi.ss.usermodel.ConditionFilterData; +import org.apache.poi.ss.usermodel.ConditionFilterType; import org.apache.poi.ss.usermodel.ConditionType; import org.apache.poi.ss.usermodel.ConditionalFormattingRule; @@ -57,6 +59,22 @@ public final class HSSFConditionalFormattingRule implements ConditionalFormattin cfRuleRecord = pRuleRecord; } + /** + * we don't know priority for these, other than definition/model order, which appears to be what Excel uses. + * @see org.apache.poi.ss.usermodel.ConditionalFormattingRule#getPriority() + */ + public int getPriority() { + return 0; + } + + /** + * Always true for HSSF files, per Microsoft Excel documentation + * @see org.apache.poi.ss.usermodel.ConditionalFormattingRule#getStopIfTrue() + */ + public boolean getStopIfTrue() { + return true; + } + CFRuleBase getCfRuleRecord() { return cfRuleRecord; } @@ -236,6 +254,18 @@ public final class HSSFConditionalFormattingRule implements ConditionalFormattin return ConditionType.forId(code); } + /** + * always null (not a filter condition) or {@link ConditionFilterType#FILTER} if it is. + * @see org.apache.poi.ss.usermodel.ConditionalFormattingRule#getConditionFilterType() + */ + public ConditionFilterType getConditionFilterType() { + return getConditionType() == ConditionType.FILTER ? ConditionFilterType.FILTER : null; + } + + public ConditionFilterData getFilterConfiguration() { + return null; + } + /** * @return - the comparisionoperatation for the cfrule */ diff --git a/src/java/org/apache/poi/ss/formula/ConditionalFormattingEvaluator.java b/src/java/org/apache/poi/ss/formula/ConditionalFormattingEvaluator.java new file mode 100644 index 000000000..81af9b084 --- /dev/null +++ b/src/java/org/apache/poi/ss/formula/ConditionalFormattingEvaluator.java @@ -0,0 +1,282 @@ +/* ==================================================================== + 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.ss.formula; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.ConditionalFormatting; +import org.apache.poi.ss.usermodel.ConditionalFormattingRule; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.SheetConditionalFormatting; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.ss.util.CellRangeAddress; +import org.apache.poi.ss.util.CellRangeAddressBase; +import org.apache.poi.ss.util.CellReference; +import org.apache.poi.ss.util.SheetUtil; + +/** + * Evaluates Conditional Formatting constraints.
+ * + * For performance reasons, this class keeps a cache of all previously evaluated rules and cells. + * Be sure to call {@link #clearAllCachedFormats()} if any conditional formats are modified, added, or deleted, + * and {@link #clearAllCachedValues()} whenever cell values change. + * + * + */ +public class ConditionalFormattingEvaluator { + + private final WorkbookEvaluator workbookEvaluator; + private final Workbook workbook; + + /** + * All the underlying structures, for both HSSF and XSSF, repeatedly go to the raw bytes/XML for the + * different pieces used in the ConditionalFormatting* structures. That's highly inefficient, + * and can cause significant lag when checking formats for large workbooks. + * + * Instead we need a cached version that is discarded when definitions change. + * + * Sheets don't implement equals, and since its an interface, + * there's no guarantee instances won't be recreated on the fly by some implementation. + * So we use sheet name. + */ + private final MapCTCfRule
in XSSFConditionalFormattingRule
.
+ *
+ * Most cells will have zero or one applied rule, but it is possible to define multiple rules
+ * that apply at the same time to the same cell, thus the List result.
+ *
+ * Note that to properly apply conditional rules, care must be taken to offset the base
+ * formula by the relative position of the current cell, or the wrong value is checked.
+ * This is handled by {@link WorkbookEvaluator#evaluate(String, CellReference, CellRangeAddressBase)}.
+ *
+ * @param cell NOTE: if no sheet name is specified, this uses the workbook active sheet
+ * @return Unmodifiable List of {@link EvaluationConditionalFormattingRule}s that apply to the current cell value,
+ * in priority order, as evaluated by Excel (smallest priority # for XSSF, definition order for HSSF),
+ * or null if none apply
+ */
+ public ListCTCfRule
in XSSFConditionalFormattingRule
.
+ *
+ * Most cells will have zero or one applied rule, but it is possible to define multiple rules
+ * that apply at the same time to the same cell, thus the List result.
+ *
+ * Note that to properly apply conditional rules, care must be taken to offset the base
+ * formula by the relative position of the current cell, or the wrong value is checked.
+ * This is handled by {@link WorkbookEvaluator#evaluate(String, CellReference, CellRangeAddressBase)}.
+ *
+ * @param cell
+ * @return Unmodifiable List of {@link EvaluationConditionalFormattingRule}s that apply to the current cell value,
+ * in priority order, as evaluated by Excel (smallest priority # for XSSF, definition order for HSSF),
+ * or null if none apply
+ */
+ public ListSTCfType
for convenience.
+ */
+public enum ConditionFilterType {
+ /** This is the only value valid for HSSF rules */
+ FILTER,
+ TOP_10,
+ UNIQUE_VALUES,
+ DUPLICATE_VALUES,
+ CONTAINS_TEXT,
+ NOT_CONTAINS_TEXT,
+ BEGINS_WITH,
+ ENDS_WITH,
+ CONTAINS_BLANKS,
+ NOT_CONTAINS_BLANKS,
+ CONTAINS_ERRORS,
+ NOT_CONTAINS_ERRORS,
+ TIME_PERIOD,
+ ABOVE_AVERAGE,
+ ;
+}
diff --git a/src/java/org/apache/poi/ss/usermodel/ConditionalFormattingRule.java b/src/java/org/apache/poi/ss/usermodel/ConditionalFormattingRule.java
index 635aaa21f..fa705e61d 100644
--- a/src/java/org/apache/poi/ss/usermodel/ConditionalFormattingRule.java
+++ b/src/java/org/apache/poi/ss/usermodel/ConditionalFormattingRule.java
@@ -83,6 +83,32 @@ public interface ConditionalFormattingRule {
* @return the type of condition
*/
ConditionType getConditionType();
+
+ /**
+ * This is null if
+ *
+ * {@link #getConditionType()} != {@link ConditionType#FILTER}
+ *
+ * This is always {@link ConditionFilterType#FILTER} for HSSF rules of type {@link ConditionType#FILTER}.
+ *
+ * For XSSF filter rules, this will indicate the specific type of filter.
+ *
+ * @return filter type for filter rules, or null if not a filter rule.
+ */
+ ConditionFilterType getConditionFilterType();
+
+ /**
+ * This is null if
+ *
+ * {@link #getConditionFilterType()} == null
+ *
+ * This means it is always null for HSSF, which does not define the extended condition types.
+ *
+ * This object contains the additional configuration information for XSSF filter conditions.
+ *
+ * @return
+ */
+ public ConditionFilterData getFilterConfiguration();
/**
* The comparison function used when the type of conditional formatting is set to
@@ -119,4 +145,25 @@ public interface ConditionalFormattingRule {
* @return the second formula
*/
String getFormula2();
+
+ /**
+ * HSSF just returns 0, XSSF uses the value stored in the model if present,
+ * otherwise uses 0.
+ *
+ * If priority is 0, just use definition order, as that's how HSSF rules are evaluated.
+ *
+ * If a rule is created but not yet added to a sheet, this value may not be valid.
+
+ * @return rule priority
+ */
+ int getPriority();
+
+ /**
+ * Always true for HSSF rules, optional flag for XSSF rules.
+ * See Excel help for more.
+ *
+ * @return true if conditional formatting rule processing stops when this one is true, false if not
+ * @see Microsoft Excel help
+ */
+ boolean getStopIfTrue();
}
diff --git a/src/java/org/apache/poi/ss/usermodel/ConditionalFormattingRule.java.svntmp b/src/java/org/apache/poi/ss/usermodel/ConditionalFormattingRule.java.svntmp
new file mode 100644
index 000000000..aa16c7761
--- /dev/null
+++ b/src/java/org/apache/poi/ss/usermodel/ConditionalFormattingRule.java.svntmp
@@ -0,0 +1,169 @@
+/*
+ * ====================================================================
+ * 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.ss.usermodel;
+
+/**
+ * Represents a description of a conditional formatting rule
+ */
+public interface ConditionalFormattingRule {
+ /**
+ * Create a new border formatting structure if it does not exist,
+ * otherwise just return existing object.
+ *
+ * @return - border formatting object, never returns null
.
+ */
+ BorderFormatting createBorderFormatting();
+
+ /**
+ * @return - border formatting object if defined, null
otherwise
+ */
+ BorderFormatting getBorderFormatting();
+
+ /**
+ * Create a new font formatting structure if it does not exist,
+ * otherwise just return existing object.
+ *
+ * @return - font formatting object, never returns null
.
+ */
+ FontFormatting createFontFormatting();
+
+ /**
+ * @return - font formatting object if defined, null
otherwise
+ */
+ FontFormatting getFontFormatting();
+
+ /**
+ * Create a new pattern formatting structure if it does not exist,
+ * otherwise just return existing object.
+ *
+ * @return - pattern formatting object, never returns null
.
+ */
+ PatternFormatting createPatternFormatting();
+
+ /**
+ * @return - pattern formatting object if defined, null
otherwise
+ */
+ PatternFormatting getPatternFormatting();
+
+ /**
+ * @return - databar / data-bar formatting object if defined, null
otherwise
+ */
+ DataBarFormatting getDataBarFormatting();
+
+ /**
+ * @return - icon / multi-state formatting object if defined, null
otherwise
+ */
+ IconMultiStateFormatting getMultiStateFormatting();
+
+ /**
+ * @return color scale / color grate formatting object if defined, null
otherwise
+ */
+ ColorScaleFormatting getColorScaleFormatting();
+
+ /**
+ * Type of conditional formatting rule.
+ *
+ * @return the type of condition
+ */
+ ConditionType getConditionType();
+
+ /**
+ * This is null if
+ *
+ * {@link #getConditionType()} != {@link ConditionType#FILTER}
+ *
+ * This is always {@link ConditionFilterType#FILTER} for HSSF rules of type {@link ConditionType#FILTER}.
+ *
+ * For XSSF filter rules, this will indicate the specific type of filter.
+ *
+ * @return filter type for filter rules, or null if not a filter rule.
+ */
+ ConditionFilterType getConditionFilterType();
+
+ /**
+ * This is null if
+ *
+ * {@link #getConditionFilterType()} == null
+ *
+ * This means it is always null for HSSF, which does not define the extended condition types.
+ *
+ * This object contains the additional configuration information for XSSF filter conditions.
+ *
+ * @return
+ */
+ public ConditionFilterData getFilterConfiguration();
+
+ /**
+ * The comparison function used when the type of conditional formatting is set to
+ * {@link ConditionType#CELL_VALUE_IS}
+ * + * MUST be a constant from {@link ComparisonOperator} + *
+ * + * @return the conditional format operator + */ + byte getComparisonOperation(); + + /** + * The formula used to evaluate the first operand for the conditional formatting rule. + *+ * If the condition type is {@link ConditionType#CELL_VALUE_IS}, + * this field is the first operand of the comparison. + * If type is {@link ConditionType#FORMULA}, this formula is used + * to determine if the conditional formatting is applied. + *
+ *+ * If comparison type is {@link ConditionType#FORMULA} the formula MUST be a Boolean function + *
+ * + * @return the first formula + */ + String getFormula1(); + + /** + * The formula used to evaluate the second operand of the comparison when + * comparison type is {@link ConditionType#CELL_VALUE_IS} and operator + * is either {@link ComparisonOperator#BETWEEN} or {@link ComparisonOperator#NOT_BETWEEN} + * + * @return the second formula + */ + String getFormula2(); + + /** + * HSSF just returns 0, XSSF uses the value stored in the model if present, + * otherwise uses 0. + * + * If priority is 0, just use definition order, as that's how HSSF rules are evaluated. + * + * If a rule is created but not yet added to a sheet, this value may not be valid. + + * @return rule priority + */ + int getPriority(); + + /** + * Always true for HSSF rules, optional flag for XSSF rules. + * See Excel help for more. + * + * @return true if conditional formatting rule processing stops when this one is true, false if not + * @see Microsoft Excel help + */ + boolean getStopIfTrue(); +} diff --git a/src/java/org/apache/poi/ss/util/CellRangeAddressBase.java b/src/java/org/apache/poi/ss/util/CellRangeAddressBase.java index f94444d9a..e4dc4aeee 100644 --- a/src/java/org/apache/poi/ss/util/CellRangeAddressBase.java +++ b/src/java/org/apache/poi/ss/util/CellRangeAddressBase.java @@ -18,6 +18,7 @@ package org.apache.poi.ss.util; import org.apache.poi.ss.SpreadsheetVersion; +import org.apache.poi.ss.usermodel.Cell; /** @@ -125,6 +126,34 @@ public abstract class CellRangeAddressBase { _firstCol <= colInd && colInd <= _lastCol; //containsColumn } + /** + * Determines if the given {@link CellReference} lies within the bounds + * of this range. + * NOTE: It is up to the caller to ensure the reference is + * for the correct sheet, since this instance doesn't have a sheet reference. + * + * @param ref the CellReference to check + * @return True if the reference lies within the bounds, false otherwise. + * @see #intersects(CellRangeAddressBase) for checking if two ranges overlap + */ + public boolean isInRange(CellReference ref) { + return isInRange(ref.getRow(), ref.getCol()); + } + + /** + * Determines if the given {@link Cell} lies within the bounds + * of this range. + * NOTE: It is up to the caller to ensure the reference is + * for the correct sheet, since this instance doesn't have a sheet reference. + * + * @param cell the Cell to check + * @return True if the cell lies within the bounds, false otherwise. + * @see #intersects(CellRangeAddressBase) for checking if two ranges overlap + */ + public boolean isInRange(Cell cell) { + return isInRange(cell.getRowIndex(), cell.getColumnIndex()); + } + /** * Check if the row is in the specified cell range * diff --git a/src/java/org/apache/poi/ss/util/SheetUtil.java b/src/java/org/apache/poi/ss/util/SheetUtil.java index f234380fe..98c307f3e 100644 --- a/src/java/org/apache/poi/ss/util/SheetUtil.java +++ b/src/java/org/apache/poi/ss/util/SheetUtil.java @@ -351,6 +351,29 @@ public class SheetUtil { return cr.isInRange(rowIx, colIx); } + /** + * Return the cell, without taking account of merged regions. + * + * Use {@link #getCellWithMerges(Sheet, int, int)} if you want the top left + * cell from merged regions instead when the reference is a merged cell. + * + * Use this where you want to know if the given cell is explicitly defined + * or not. + * + * @param sheet + * @param rowIx + * @param colIx + * @return cell at the given location, or null if not defined + * @throws NullPointerException if sheet is null + */ + public static Cell getCell(Sheet sheet, int rowIx, int colIx) { + Row r = sheet.getRow(rowIx); + if (r != null) { + return r.getCell(colIx); + } + return null; + } + /** * Return the cell, taking account of merged regions. Allows you to find the * cell who's contents are shown in a given position in the sheet. @@ -361,22 +384,22 @@ public class SheetUtil { * then will return the cell itself. *If there is no cell defined at the given co-ordinates, will return
* null.
+ *
+ * @param sheet
+ * @param rowIx
+ * @param colIx
+ * @return cell at the given location, its base merged cell, or null if not defined
+ * @throws NullPointerException if sheet is null
*/
public static Cell getCellWithMerges(Sheet sheet, int rowIx, int colIx) {
- Row r = sheet.getRow(rowIx);
- if (r != null) {
- Cell c = r.getCell(colIx);
- if (c != null) {
- // Normal, non-merged cell
- return c;
- }
- }
+ final Cell c = getCell(sheet, rowIx, colIx);
+ if (c != null) return c;
for (CellRangeAddress mergedRegion : sheet.getMergedRegions()) {
if (mergedRegion.isInRange(rowIx, colIx)) {
// The cell wanted is in this merged range
// Return the primary (top-left) cell for the range
- r = sheet.getRow(mergedRegion.getFirstRow());
+ Row r = sheet.getRow(mergedRegion.getFirstRow());
if (r != null) {
return r.getCell(mergedRegion.getFirstColumn());
}
diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFConditionFilterData.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFConditionFilterData.java
new file mode 100644
index 000000000..59bfb9406
--- /dev/null
+++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFConditionFilterData.java
@@ -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.xssf.usermodel;
+
+import org.apache.poi.ss.usermodel.ConditionFilterData;
+import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTCfRule;
+
+public class XSSFConditionFilterData implements ConditionFilterData {
+
+ private final CTCfRule _cfRule;
+
+ /*package*/ XSSFConditionFilterData(CTCfRule cfRule) {
+ _cfRule = cfRule;
+ }
+
+ public boolean getAboveAverage() {
+ return _cfRule.getAboveAverage();
+ }
+
+ public boolean getBottom() {
+ return _cfRule.getBottom();
+ }
+
+ public boolean getEqualAverage() {
+ return _cfRule.getEqualAverage();
+ }
+
+ public boolean getPercent() {
+ return _cfRule.getPercent();
+ }
+
+ public long getRank() {
+ return _cfRule.getRank();
+ }
+
+ public int getStdDev() {
+ return _cfRule.getStdDev();
+ }
+
+}
diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFConditionalFormattingRule.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFConditionalFormattingRule.java
index 6c5c36479..69816b7fa 100644
--- a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFConditionalFormattingRule.java
+++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFConditionalFormattingRule.java
@@ -30,13 +30,14 @@ import org.apache.poi.xssf.model.StylesTable;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.*;
/**
- * XSSF suport for Conditional Formatting rules
+ * XSSF support for Conditional Formatting rules
*/
public class XSSFConditionalFormattingRule implements ConditionalFormattingRule {
private final CTCfRule _cfRule;
private XSSFSheet _sh;
private static Map