Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

ConditionalFormattingEvaluator.java 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297
  1. /* ====================================================================
  2. Licensed to the Apache Software Foundation (ASF) under one or more
  3. contributor license agreements. See the NOTICE file distributed with
  4. this work for additional information regarding copyright ownership.
  5. The ASF licenses this file to You under the Apache License, Version 2.0
  6. (the "License"); you may not use this file except in compliance with
  7. the License. You may obtain a copy of the License at
  8. http://www.apache.org/licenses/LICENSE-2.0
  9. Unless required by applicable law or agreed to in writing, software
  10. distributed under the License is distributed on an "AS IS" BASIS,
  11. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. See the License for the specific language governing permissions and
  13. limitations under the License.
  14. ==================================================================== */
  15. package org.apache.poi.ss.formula;
  16. import java.util.ArrayList;
  17. import java.util.Collections;
  18. import java.util.HashMap;
  19. import java.util.List;
  20. import java.util.Map;
  21. import org.apache.poi.ss.usermodel.Cell;
  22. import org.apache.poi.ss.usermodel.ConditionalFormatting;
  23. import org.apache.poi.ss.usermodel.ConditionalFormattingRule;
  24. import org.apache.poi.ss.usermodel.FormulaEvaluator;
  25. import org.apache.poi.ss.usermodel.Row;
  26. import org.apache.poi.ss.usermodel.Sheet;
  27. import org.apache.poi.ss.usermodel.SheetConditionalFormatting;
  28. import org.apache.poi.ss.usermodel.Workbook;
  29. import org.apache.poi.ss.util.CellRangeAddress;
  30. import org.apache.poi.ss.util.CellRangeAddressBase;
  31. import org.apache.poi.ss.util.CellReference;
  32. /**
  33. * Evaluates Conditional Formatting constraints.<p>
  34. *
  35. * For performance reasons, this class keeps a cache of all previously evaluated rules and cells.
  36. * Be sure to call {@link #clearAllCachedFormats()} if any conditional formats are modified, added, or deleted,
  37. * and {@link #clearAllCachedValues()} whenever cell values change.
  38. * <p>
  39. *
  40. */
  41. public class ConditionalFormattingEvaluator {
  42. private final WorkbookEvaluator workbookEvaluator;
  43. private final Workbook workbook;
  44. /**
  45. * All the underlying structures, for both HSSF and XSSF, repeatedly go to the raw bytes/XML for the
  46. * different pieces used in the ConditionalFormatting* structures. That's highly inefficient,
  47. * and can cause significant lag when checking formats for large workbooks.
  48. * <p>
  49. * Instead we need a cached version that is discarded when definitions change.
  50. * <p>
  51. * Sheets don't implement equals, and since its an interface,
  52. * there's no guarantee instances won't be recreated on the fly by some implementation.
  53. * So we use sheet name.
  54. */
  55. private final Map<String, List<EvaluationConditionalFormatRule>> formats = new HashMap<>();
  56. /**
  57. * Evaluating rules for cells in their region(s) is expensive, so we want to cache them,
  58. * and empty/reevaluate the cache when values change.
  59. * <p>
  60. * Rule lists are in priority order, as evaluated by Excel (smallest priority # for XSSF, definition order for HSSF)
  61. * <p>
  62. * CellReference implements equals().
  63. */
  64. private final Map<CellReference, List<EvaluationConditionalFormatRule>> values = new HashMap<>();
  65. public ConditionalFormattingEvaluator(Workbook wb, WorkbookEvaluatorProvider provider) {
  66. this.workbook = wb;
  67. this.workbookEvaluator = provider._getWorkbookEvaluator();
  68. }
  69. protected WorkbookEvaluator getWorkbookEvaluator() {
  70. return workbookEvaluator;
  71. }
  72. /**
  73. * Call this whenever rules are added, reordered, or removed, or a rule formula is changed
  74. * (not the formula inputs but the formula expression itself)
  75. */
  76. public void clearAllCachedFormats() {
  77. formats.clear();
  78. }
  79. /**
  80. * Call this whenever cell values change in the workbook, so condional formats are re-evaluated
  81. * for all cells.
  82. * <p>
  83. * TODO: eventually this should work like {@link EvaluationCache#notifyUpdateCell(int, int, EvaluationCell)}
  84. * and only clear values that need recalculation based on the formula dependency tree.
  85. */
  86. public void clearAllCachedValues() {
  87. values.clear();
  88. }
  89. /**
  90. * lazy load by sheet since reading can be expensive
  91. *
  92. * @param sheet The sheet to look at
  93. * @return unmodifiable list of rules
  94. */
  95. protected List<EvaluationConditionalFormatRule> getRules(Sheet sheet) {
  96. final String sheetName = sheet.getSheetName();
  97. List<EvaluationConditionalFormatRule> rules = formats.get(sheetName);
  98. if (rules == null) {
  99. if (formats.containsKey(sheetName)) {
  100. return Collections.emptyList();
  101. }
  102. final SheetConditionalFormatting scf = sheet.getSheetConditionalFormatting();
  103. final int count = scf.getNumConditionalFormattings();
  104. rules = new ArrayList<>(count);
  105. formats.put(sheetName, rules);
  106. for (int i=0; i < count; i++) {
  107. ConditionalFormatting f = scf.getConditionalFormattingAt(i);
  108. //optimization, as this may be expensive for lots of ranges
  109. final CellRangeAddress[] regions = f.getFormattingRanges();
  110. for (int r=0; r < f.getNumberOfRules(); r++) {
  111. ConditionalFormattingRule rule = f.getRule(r);
  112. rules.add(new EvaluationConditionalFormatRule(workbookEvaluator, sheet, f, i, rule, r, regions));
  113. }
  114. }
  115. // need them in formatting and priority order so logic works right
  116. Collections.sort(rules);
  117. }
  118. return Collections.unmodifiableList(rules);
  119. }
  120. /**
  121. * This checks all applicable {@link ConditionalFormattingRule}s for the cell's sheet,
  122. * in defined "priority" order, returning the matches if any. This is a property currently
  123. * not exposed from <code>CTCfRule</code> in <code>XSSFConditionalFormattingRule</code>.
  124. * <p>
  125. * Most cells will have zero or one applied rule, but it is possible to define multiple rules
  126. * that apply at the same time to the same cell, thus the List result.
  127. * <p>
  128. * Note that to properly apply conditional rules, care must be taken to offset the base
  129. * formula by the relative position of the current cell, or the wrong value is checked.
  130. * This is handled by {@link WorkbookEvaluator#evaluate(String, CellReference, CellRangeAddressBase)}.
  131. * <p>
  132. * If the cell exists and is a formula cell, its cached value may be used for rule evaluation, so
  133. * make sure it is up to date. If values have changed, it is best to call
  134. * {@link FormulaEvaluator#evaluateFormulaCell(Cell)} or {@link FormulaEvaluator#evaluateAll()} first,
  135. * or the wrong conditional results may be returned.
  136. *
  137. * @param cellRef NOTE: if no sheet name is specified, this uses the workbook active sheet
  138. * @return Unmodifiable List of {@link EvaluationConditionalFormatRule}s that apply to the current cell value,
  139. * in priority order, as evaluated by Excel (smallest priority # for XSSF, definition order for HSSF),
  140. * or null if none apply
  141. */
  142. public List<EvaluationConditionalFormatRule> getConditionalFormattingForCell(final CellReference cellRef) {
  143. List<EvaluationConditionalFormatRule> rules = values.get(cellRef);
  144. if (rules == null) {
  145. // compute and cache them
  146. rules = new ArrayList<>();
  147. final Sheet sheet;
  148. if (cellRef.getSheetName() != null) {
  149. sheet = workbook.getSheet(cellRef.getSheetName());
  150. } else {
  151. sheet = workbook.getSheetAt(workbook.getActiveSheetIndex());
  152. }
  153. /*
  154. * Per Excel help:
  155. * https://support.office.com/en-us/article/Manage-conditional-formatting-rule-precedence-e09711a3-48df-4bcb-b82c-9d8b8b22463d#__toc269129417
  156. * stopIfTrue is true for all rules from HSSF files, and an explicit value for XSSF files.
  157. * thus the explicit ordering of the rule lists in #getFormattingRulesForSheet(Sheet)
  158. */
  159. boolean stopIfTrue = false;
  160. for (EvaluationConditionalFormatRule rule : getRules(sheet)) {
  161. if (stopIfTrue) {
  162. continue; // a previous rule matched and wants no more evaluations
  163. }
  164. if (rule.matches(cellRef)) {
  165. rules.add(rule);
  166. stopIfTrue = rule.getRule().getStopIfTrue();
  167. }
  168. }
  169. Collections.sort(rules);
  170. values.put(cellRef, rules);
  171. }
  172. return Collections.unmodifiableList(rules);
  173. }
  174. /**
  175. * This checks all applicable {@link ConditionalFormattingRule}s for the cell's sheet,
  176. * in defined "priority" order, returning the matches if any. This is a property currently
  177. * not exposed from <code>CTCfRule</code> in <code>XSSFConditionalFormattingRule</code>.
  178. * <p>
  179. * Most cells will have zero or one applied rule, but it is possible to define multiple rules
  180. * that apply at the same time to the same cell, thus the List result.
  181. * <p>
  182. * Note that to properly apply conditional rules, care must be taken to offset the base
  183. * formula by the relative position of the current cell, or the wrong value is checked.
  184. * This is handled by {@link WorkbookEvaluator#evaluate(String, CellReference, CellRangeAddressBase)}.
  185. * <p>
  186. * If the cell exists and is a formula cell, its cached value may be used for rule evaluation, so
  187. * make sure it is up to date. If values have changed, it is best to call
  188. * {@link FormulaEvaluator#evaluateFormulaCell(Cell)} or {@link FormulaEvaluator#evaluateAll()} first,
  189. * or the wrong conditional results may be returned.
  190. *
  191. * @param cell The cell to look for
  192. * @return Unmodifiable List of {@link EvaluationConditionalFormatRule}s that apply to the current cell value,
  193. * in priority order, as evaluated by Excel (smallest priority # for XSSF, definition order for HSSF),
  194. * or null if none apply
  195. */
  196. public List<EvaluationConditionalFormatRule> getConditionalFormattingForCell(Cell cell) {
  197. return getConditionalFormattingForCell(getRef(cell));
  198. }
  199. public static CellReference getRef(Cell cell) {
  200. return new CellReference(cell.getSheet().getSheetName(), cell.getRowIndex(), cell.getColumnIndex(), false, false);
  201. }
  202. /**
  203. * Retrieve all formatting rules for the sheet with the given name.
  204. *
  205. * @param sheetName The name of the sheet to look at
  206. * @return unmodifiable list of all Conditional format rules for the given sheet, if any
  207. */
  208. public List<EvaluationConditionalFormatRule> getFormatRulesForSheet(String sheetName) {
  209. return getFormatRulesForSheet(workbook.getSheet(sheetName));
  210. }
  211. /**
  212. * Retrieve all formatting rules for the given sheet.
  213. *
  214. * @param sheet The sheet to look at
  215. * @return unmodifiable list of all Conditional format rules for the given sheet, if any
  216. */
  217. public List<EvaluationConditionalFormatRule> getFormatRulesForSheet(Sheet sheet) {
  218. return getRules(sheet);
  219. }
  220. /**
  221. * Conditional formatting rules can apply only to cells in the sheet to which they are attached.
  222. * The POI data model does not have a back-reference to the owning sheet, so it must be passed in separately.
  223. * <p>
  224. * We could overload this with convenience methods taking a sheet name and sheet index as well.
  225. * <p>
  226. * @param sheet containing the rule
  227. * @param conditionalFormattingIndex of the {@link ConditionalFormatting} instance in the sheet's array
  228. * @param ruleIndex of the {@link ConditionalFormattingRule} instance within the {@link ConditionalFormatting}
  229. * @return unmodifiable List of all cells in the rule's region matching the rule's condition
  230. */
  231. public List<Cell> getMatchingCells(Sheet sheet, int conditionalFormattingIndex, int ruleIndex) {
  232. for (EvaluationConditionalFormatRule rule : getRules(sheet)) {
  233. if (rule.getSheet().equals(sheet) && rule.getFormattingIndex() == conditionalFormattingIndex && rule.getRuleIndex() == ruleIndex) {
  234. return getMatchingCells(rule);
  235. }
  236. }
  237. return Collections.emptyList();
  238. }
  239. /**
  240. * Retrieve all cells where the given formatting rule evaluates to true.
  241. *
  242. * @param rule The rule to look at
  243. * @return unmodifiable List of all cells in the rule's region matching the rule's condition
  244. */
  245. public List<Cell> getMatchingCells(EvaluationConditionalFormatRule rule) {
  246. final List<Cell> cells = new ArrayList<>();
  247. final Sheet sheet = rule.getSheet();
  248. for (CellRangeAddress region : rule.getRegions()) {
  249. for (int r = region.getFirstRow(); r <= region.getLastRow(); r++) {
  250. final Row row = sheet.getRow(r);
  251. if (row == null) {
  252. continue; // no cells to check
  253. }
  254. for (int c = region.getFirstColumn(); c <= region.getLastColumn(); c++) {
  255. final Cell cell = row.getCell(c);
  256. if (cell == null) {
  257. continue;
  258. }
  259. List<EvaluationConditionalFormatRule> cellRules = getConditionalFormattingForCell(cell);
  260. if (cellRules.contains(rule)) {
  261. cells.add(cell);
  262. }
  263. }
  264. }
  265. }
  266. return Collections.unmodifiableList(cells);
  267. }
  268. }