You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

EvaluationConditionalFormatRule.java 38KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925
  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.text.DecimalFormat;
  17. import java.text.DecimalFormatSymbols;
  18. import java.util.ArrayList;
  19. import java.util.Collections;
  20. import java.util.HashMap;
  21. import java.util.HashSet;
  22. import java.util.LinkedHashSet;
  23. import java.util.List;
  24. import java.util.Locale;
  25. import java.util.Map;
  26. import java.util.Set;
  27. import org.apache.poi.ss.formula.eval.BlankEval;
  28. import org.apache.poi.ss.formula.eval.BoolEval;
  29. import org.apache.poi.ss.formula.eval.ErrorEval;
  30. import org.apache.poi.ss.formula.eval.NumberEval;
  31. import org.apache.poi.ss.formula.eval.RefEval;
  32. import org.apache.poi.ss.formula.eval.StringEval;
  33. import org.apache.poi.ss.formula.eval.ValueEval;
  34. import org.apache.poi.ss.formula.functions.AggregateFunction;
  35. import org.apache.poi.ss.usermodel.Cell;
  36. import org.apache.poi.ss.usermodel.CellType;
  37. import org.apache.poi.ss.usermodel.ConditionFilterData;
  38. import org.apache.poi.ss.usermodel.ConditionFilterType;
  39. import org.apache.poi.ss.usermodel.ConditionType;
  40. import org.apache.poi.ss.usermodel.ConditionalFormatting;
  41. import org.apache.poi.ss.usermodel.ConditionalFormattingRule;
  42. import org.apache.poi.ss.usermodel.ExcelNumberFormat;
  43. import org.apache.poi.ss.usermodel.Row;
  44. import org.apache.poi.ss.usermodel.Sheet;
  45. import org.apache.poi.ss.util.CellRangeAddress;
  46. import org.apache.poi.ss.util.CellReference;
  47. /**
  48. * Abstracted and cached version of a Conditional Format rule for use with a
  49. * {@link ConditionalFormattingEvaluator}. This references a rule, its owning
  50. * {@link ConditionalFormatting}, its priority order (lower index = higher priority in Excel),
  51. * and the information needed to evaluate the rule for a given cell.
  52. * <p>
  53. * Having this all combined and cached avoids repeated access calls to the
  54. * underlying structural objects, XSSF CT* objects and HSSF raw byte structures.
  55. * Those objects can be referenced from here. This object will be out of sync if
  56. * anything modifies the referenced structures' evaluation properties.
  57. * <p>
  58. * The assumption is that consuming applications will read the display properties once and
  59. * create whatever style objects they need, caching those at the application level.
  60. * Thus this class only caches values needed for evaluation, not display.
  61. */
  62. public class EvaluationConditionalFormatRule implements Comparable<EvaluationConditionalFormatRule> {
  63. private final WorkbookEvaluator workbookEvaluator;
  64. private final Sheet sheet;
  65. private final ConditionalFormatting formatting;
  66. private final ConditionalFormattingRule rule;
  67. /* cached values */
  68. private final CellRangeAddress[] regions;
  69. /**
  70. * Depending on the rule type, it may want to know about certain values in the region when evaluating {@link #matches(CellReference)},
  71. * such as top 10, unique, duplicate, average, etc. This collection stores those if needed so they are not repeatedly calculated
  72. */
  73. private final Map<CellRangeAddress, Set<ValueAndFormat>> meaningfulRegionValues = new HashMap<>();
  74. private final int priority;
  75. private final int formattingIndex;
  76. private final int ruleIndex;
  77. private final String formula1;
  78. private final String formula2;
  79. private final String text;
  80. private final OperatorEnum operator;
  81. private final ConditionType type;
  82. // cached for performance, to avoid reading the XMLBean every time a conditionally formatted cell is rendered
  83. private final ExcelNumberFormat numberFormat;
  84. // cached for performance, used to format numeric cells for string comparisons. See Bug #61764 for explanation
  85. private final DecimalFormat decimalTextFormat;
  86. /**
  87. *
  88. * @param workbookEvaluator
  89. * @param sheet
  90. * @param formatting
  91. * @param formattingIndex for priority, zero based
  92. * @param rule
  93. * @param ruleIndex for priority, zero based, if this is an HSSF rule. Unused for XSSF rules
  94. * @param regions could be read from formatting, but every call creates new objects in a new array.
  95. * this allows calling it once per formatting instance, and re-using the array.
  96. */
  97. public EvaluationConditionalFormatRule(WorkbookEvaluator workbookEvaluator, Sheet sheet, ConditionalFormatting formatting, int formattingIndex, ConditionalFormattingRule rule, int ruleIndex, CellRangeAddress[] regions) {
  98. super();
  99. this.workbookEvaluator = workbookEvaluator;
  100. this.sheet = sheet;
  101. this.formatting = formatting;
  102. this.rule = rule;
  103. this.formattingIndex = formattingIndex;
  104. this.ruleIndex = ruleIndex;
  105. this.priority = rule.getPriority();
  106. this.regions = regions;
  107. formula1 = rule.getFormula1();
  108. formula2 = rule.getFormula2();
  109. text = rule.getText();
  110. numberFormat = rule.getNumberFormat();
  111. operator = OperatorEnum.values()[rule.getComparisonOperation()];
  112. type = rule.getConditionType();
  113. decimalTextFormat = new DecimalFormat("0", DecimalFormatSymbols.getInstance(Locale.ENGLISH));
  114. decimalTextFormat.setMaximumFractionDigits(340); // DecimalFormat.DOUBLE_FRACTION_DIGITS, which is default scoped
  115. }
  116. /**
  117. * @return sheet
  118. */
  119. public Sheet getSheet() {
  120. return sheet;
  121. }
  122. /**
  123. * @return the formatting
  124. */
  125. public ConditionalFormatting getFormatting() {
  126. return formatting;
  127. }
  128. /**
  129. * @return conditional formatting index
  130. */
  131. public int getFormattingIndex() {
  132. return formattingIndex;
  133. }
  134. /**
  135. * @return Excel number format string to apply to matching cells, or null to keep the cell default
  136. */
  137. public ExcelNumberFormat getNumberFormat() {
  138. return numberFormat;
  139. }
  140. /**
  141. * @return the rule
  142. */
  143. public ConditionalFormattingRule getRule() {
  144. return rule;
  145. }
  146. /**
  147. * @return rule index
  148. */
  149. public int getRuleIndex() {
  150. return ruleIndex;
  151. }
  152. /**
  153. * @return the regions
  154. */
  155. public CellRangeAddress[] getRegions() {
  156. return regions;
  157. }
  158. /**
  159. * @return the priority
  160. */
  161. public int getPriority() {
  162. return priority;
  163. }
  164. /**
  165. * @return the formula1
  166. */
  167. public String getFormula1() {
  168. return formula1;
  169. }
  170. /**
  171. * @return the formula2
  172. */
  173. public String getFormula2() {
  174. return formula2;
  175. }
  176. /**
  177. * @return condition text if any, or null
  178. */
  179. public String getText() {
  180. return text;
  181. }
  182. /**
  183. * @return the operator
  184. */
  185. public OperatorEnum getOperator() {
  186. return operator;
  187. }
  188. /**
  189. * @return the type
  190. */
  191. public ConditionType getType() {
  192. return type;
  193. }
  194. /**
  195. * Defined as equal sheet name and formatting and rule indexes
  196. * @see java.lang.Object#equals(java.lang.Object)
  197. */
  198. @Override
  199. public boolean equals(Object obj) {
  200. if (obj == null) {
  201. return false;
  202. }
  203. if (! obj.getClass().equals(this.getClass())) {
  204. return false;
  205. }
  206. final EvaluationConditionalFormatRule r = (EvaluationConditionalFormatRule) obj;
  207. return getSheet().getSheetName().equalsIgnoreCase(r.getSheet().getSheetName())
  208. && getFormattingIndex() == r.getFormattingIndex()
  209. && getRuleIndex() == r.getRuleIndex();
  210. }
  211. /**
  212. * Per Excel Help, XSSF rule priority is sheet-wide, not just within the owning ConditionalFormatting object.
  213. * This can be seen by creating 4 rules applying to two different ranges and examining the XML.
  214. * <p>
  215. * HSSF priority is based on definition/persistence order.
  216. *
  217. * @param o
  218. * @return comparison based on sheet name, formatting index, and rule priority
  219. */
  220. @Override
  221. public int compareTo(EvaluationConditionalFormatRule o) {
  222. int cmp = getSheet().getSheetName().compareToIgnoreCase(o.getSheet().getSheetName());
  223. if (cmp != 0) {
  224. return cmp;
  225. }
  226. final int x = getPriority();
  227. final int y = o.getPriority();
  228. // logic from Integer.compare()
  229. cmp = Integer.compare(x, y);
  230. if (cmp != 0) {
  231. return cmp;
  232. }
  233. cmp = Integer.compare(getFormattingIndex(), o.getFormattingIndex());
  234. if (cmp != 0) {
  235. return cmp;
  236. }
  237. return Integer.compare(getRuleIndex(), o.getRuleIndex());
  238. }
  239. @Override
  240. public int hashCode() {
  241. int hash = sheet.getSheetName().hashCode();
  242. hash = 31 * hash + formattingIndex;
  243. hash = 31 * hash + ruleIndex;
  244. return hash;
  245. }
  246. /**
  247. * @param ref
  248. * @return true if this rule evaluates to true for the given cell
  249. */
  250. /* package */ boolean matches(CellReference ref) {
  251. // first check that it is in one of the regions defined for this format
  252. CellRangeAddress region = null;
  253. for (CellRangeAddress r : regions) {
  254. if (r.isInRange(ref)) {
  255. region = r;
  256. break;
  257. }
  258. }
  259. if (region == null) {
  260. // cell not in range of this rule
  261. return false;
  262. }
  263. final ConditionType ruleType = getRule().getConditionType();
  264. // these rules apply to all cells in a region. Specific condition criteria
  265. // may specify no special formatting for that value partition, but that's display logic
  266. if (ruleType.equals(ConditionType.COLOR_SCALE)
  267. || ruleType.equals(ConditionType.DATA_BAR)
  268. || ruleType.equals(ConditionType.ICON_SET)) {
  269. return true;
  270. }
  271. Cell cell = null;
  272. final Row row = sheet.getRow(ref.getRow());
  273. if (row != null) {
  274. cell = row.getCell(ref.getCol());
  275. }
  276. if (ruleType.equals(ConditionType.CELL_VALUE_IS)) {
  277. // undefined cells never match a VALUE_IS condition
  278. if (cell == null) return false;
  279. return checkValue(cell, region);
  280. }
  281. if (ruleType.equals(ConditionType.FORMULA)) {
  282. return checkFormula(ref, region);
  283. }
  284. if (ruleType.equals(ConditionType.FILTER)) {
  285. return checkFilter(cell, ref, region);
  286. }
  287. // TODO: anything else, we don't handle yet, such as top 10
  288. return false;
  289. }
  290. /**
  291. * @param cell the cell to check for
  292. * @param region for adjusting relative formulas
  293. * @return if the value of the cell is valid or not for the formatting rule
  294. */
  295. private boolean checkValue(Cell cell, CellRangeAddress region) {
  296. if (cell == null || DataValidationEvaluator.isType(cell, CellType.BLANK)
  297. || DataValidationEvaluator.isType(cell,CellType.ERROR)
  298. || (DataValidationEvaluator.isType(cell,CellType.STRING)
  299. && (cell.getStringCellValue() == null || cell.getStringCellValue().isEmpty())
  300. )
  301. ) {
  302. return false;
  303. }
  304. ValueEval eval = unwrapEval(workbookEvaluator.evaluate(rule.getFormula1(), ConditionalFormattingEvaluator.getRef(cell), region));
  305. String f2 = rule.getFormula2();
  306. ValueEval eval2 = BlankEval.instance;
  307. if (f2 != null && f2.length() > 0) {
  308. eval2 = unwrapEval(workbookEvaluator.evaluate(f2, ConditionalFormattingEvaluator.getRef(cell), region));
  309. }
  310. // we assume the cell has been evaluated, and the current formula value stored
  311. if (DataValidationEvaluator.isType(cell, CellType.BOOLEAN)
  312. && (eval == BlankEval.instance || eval instanceof BoolEval)
  313. && (eval2 == BlankEval.instance || eval2 instanceof BoolEval)
  314. ) {
  315. return operator.isValid(cell.getBooleanCellValue(), eval == BlankEval.instance ? null : ((BoolEval) eval).getBooleanValue(), eval2 == BlankEval.instance ? null : ((BoolEval) eval2).getBooleanValue());
  316. }
  317. if (DataValidationEvaluator.isType(cell, CellType.NUMERIC)
  318. && (eval == BlankEval.instance || eval instanceof NumberEval )
  319. && (eval2 == BlankEval.instance || eval2 instanceof NumberEval)
  320. ) {
  321. return operator.isValid(cell.getNumericCellValue(), eval == BlankEval.instance ? null : ((NumberEval) eval).getNumberValue(), eval2 == BlankEval.instance ? null : ((NumberEval) eval2).getNumberValue());
  322. }
  323. if (DataValidationEvaluator.isType(cell, CellType.STRING)
  324. && (eval == BlankEval.instance || eval instanceof StringEval )
  325. && (eval2 == BlankEval.instance || eval2 instanceof StringEval)
  326. ) {
  327. return operator.isValid(cell.getStringCellValue(), eval == BlankEval.instance ? null : ((StringEval) eval).getStringValue(), eval2 == BlankEval.instance ? null : ((StringEval) eval2).getStringValue());
  328. }
  329. return operator.isValidForIncompatibleTypes();
  330. }
  331. private ValueEval unwrapEval(ValueEval eval) {
  332. ValueEval comp = eval;
  333. while (comp instanceof RefEval) {
  334. RefEval ref = (RefEval) comp;
  335. comp = ref.getInnerValueEval(ref.getFirstSheetIndex());
  336. }
  337. return comp;
  338. }
  339. /**
  340. * @param ref needed for offsets from region anchor - may be null!
  341. * @param region for adjusting relative formulas
  342. * @return true/false using the same rules as Data Validation evaluations
  343. */
  344. private boolean checkFormula(CellReference ref, CellRangeAddress region) {
  345. ValueEval comp = unwrapEval(workbookEvaluator.evaluate(rule.getFormula1(), ref, region));
  346. // Copied for now from DataValidationEvaluator.ValidationEnum.FORMULA#isValidValue()
  347. if (comp instanceof BlankEval) {
  348. return true;
  349. }
  350. if (comp instanceof ErrorEval) {
  351. return false;
  352. }
  353. if (comp instanceof BoolEval) {
  354. return ((BoolEval) comp).getBooleanValue();
  355. }
  356. // empirically tested in Excel - 0=false, any other number = true/valid
  357. // see test file DataValidationEvaluations.xlsx
  358. if (comp instanceof NumberEval) {
  359. return ((NumberEval) comp).getNumberValue() != 0;
  360. }
  361. return false; // anything else is false, such as text
  362. }
  363. private boolean checkFilter(Cell cell, CellReference ref, CellRangeAddress region) {
  364. final ConditionFilterType filterType = rule.getConditionFilterType();
  365. if (filterType == null) {
  366. return false;
  367. }
  368. final ValueAndFormat cv = getCellValue(cell);
  369. // TODO: this could/should be delegated to the Enum type, but that's in the usermodel package,
  370. // we may not want evaluation code there. Of course, maybe the enum should go here in formula,
  371. // and not be returned by the SS model, but then we need the XSSF rule to expose the raw OOXML
  372. // type value, which isn't ideal either.
  373. switch (filterType) {
  374. case FILTER:
  375. return false; // we don't evaluate HSSF filters yet
  376. case TOP_10:
  377. // from testing, Excel only operates on numbers and dates (which are stored as numbers) in the range.
  378. // numbers stored as text are ignored, but numbers formatted as text are treated as numbers.
  379. if (! cv.isNumber()) {
  380. return false;
  381. }
  382. return getMeaningfulValues(region, false, new ValueFunction() {
  383. @Override
  384. public Set<ValueAndFormat> evaluate(List<ValueAndFormat> allValues) {
  385. final ConditionFilterData conf = rule.getFilterConfiguration();
  386. if (! conf.getBottom()) {
  387. allValues.sort(Collections.reverseOrder());
  388. } else {
  389. Collections.sort(allValues);
  390. }
  391. int limit = (int) conf.getRank();
  392. if (conf.getPercent()) {
  393. limit = allValues.size() * limit / 100;
  394. }
  395. if (allValues.size() <= limit) {
  396. return new HashSet<>(allValues);
  397. }
  398. return new HashSet<>(allValues.subList(0, limit));
  399. }
  400. }).contains(cv);
  401. case UNIQUE_VALUES:
  402. // Per Excel help, "duplicate" means matching value AND format
  403. // https://support.office.com/en-us/article/Filter-for-unique-values-or-remove-duplicate-values-ccf664b0-81d6-449b-bbe1-8daaec1e83c2
  404. return getMeaningfulValues(region, true, new ValueFunction() {
  405. @Override
  406. public Set<ValueAndFormat> evaluate(List<ValueAndFormat> allValues) {
  407. Collections.sort(allValues);
  408. final Set<ValueAndFormat> unique = new HashSet<>();
  409. for (int i = 0; i < allValues.size(); i++) {
  410. final ValueAndFormat v = allValues.get(i);
  411. // skip this if the current value matches the next one, or is the last one and matches the previous one
  412. if ( (i < allValues.size()-1 && v.equals(allValues.get(i+1)) ) || ( i > 0 && i == allValues.size()-1 && v.equals(allValues.get(i-1)) ) ) {
  413. // current value matches next value, skip both
  414. i++;
  415. continue;
  416. }
  417. unique.add(v);
  418. }
  419. return unique;
  420. }
  421. }).contains(cv);
  422. case DUPLICATE_VALUES:
  423. // Per Excel help, "duplicate" means matching value AND format
  424. // https://support.office.com/en-us/article/Filter-for-unique-values-or-remove-duplicate-values-ccf664b0-81d6-449b-bbe1-8daaec1e83c2
  425. return getMeaningfulValues(region, true, new ValueFunction() {
  426. @Override
  427. public Set<ValueAndFormat> evaluate(List<ValueAndFormat> allValues) {
  428. Collections.sort(allValues);
  429. final Set<ValueAndFormat> dup = new HashSet<>();
  430. for (int i = 0; i < allValues.size(); i++) {
  431. final ValueAndFormat v = allValues.get(i);
  432. // skip this if the current value matches the next one, or is the last one and matches the previous one
  433. if ( (i < allValues.size()-1 && v.equals(allValues.get(i+1)) ) || ( i > 0 && i == allValues.size()-1 && v.equals(allValues.get(i-1)) ) ) {
  434. // current value matches next value, add one
  435. dup.add(v);
  436. i++;
  437. }
  438. }
  439. return dup;
  440. }
  441. }).contains(cv);
  442. case ABOVE_AVERAGE:
  443. // from testing, Excel only operates on numbers and dates (which are stored as numbers) in the range.
  444. // numbers stored as text are ignored, but numbers formatted as text are treated as numbers.
  445. final ConditionFilterData conf = rule.getFilterConfiguration();
  446. // actually ordered, so iteration order is predictable
  447. List<ValueAndFormat> values = new ArrayList<>(getMeaningfulValues(region, false, new ValueFunction() {
  448. @Override
  449. public Set<ValueAndFormat> evaluate(List<ValueAndFormat> allValues) {
  450. double total = 0;
  451. ValueEval[] pop = new ValueEval[allValues.size()];
  452. for (int i = 0; i < allValues.size(); i++) {
  453. ValueAndFormat v = allValues.get(i);
  454. total += v.value.doubleValue();
  455. pop[i] = new NumberEval(v.value.doubleValue());
  456. }
  457. final Set<ValueAndFormat> avgSet = new LinkedHashSet<>(1);
  458. avgSet.add(new ValueAndFormat(Double.valueOf(allValues.size() == 0 ? 0 : total / allValues.size()), null, decimalTextFormat));
  459. final double stdDev = allValues.size() <= 1 ? 0 : ((NumberEval) AggregateFunction.STDEV.evaluate(pop, 0, 0)).getNumberValue();
  460. avgSet.add(new ValueAndFormat(Double.valueOf(stdDev), null, decimalTextFormat));
  461. return avgSet;
  462. }
  463. }));
  464. Double val = cv.isNumber() ? cv.getValue() : null;
  465. if (val == null) {
  466. return false;
  467. }
  468. double avg = values.get(0).value.doubleValue();
  469. double stdDev = values.get(1).value.doubleValue();
  470. /*
  471. * use StdDev, aboveAverage, equalAverage to find:
  472. * comparison value
  473. * operator type
  474. */
  475. Double comp = Double.valueOf(conf.getStdDev() > 0 ? (avg + (conf.getAboveAverage() ? 1 : -1) * stdDev * conf.getStdDev()) : avg) ;
  476. final OperatorEnum op;
  477. if (conf.getAboveAverage()) {
  478. if (conf.getEqualAverage()) {
  479. op = OperatorEnum.GREATER_OR_EQUAL;
  480. } else {
  481. op = OperatorEnum.GREATER_THAN;
  482. }
  483. } else {
  484. if (conf.getEqualAverage()) {
  485. op = OperatorEnum.LESS_OR_EQUAL;
  486. } else {
  487. op = OperatorEnum.LESS_THAN;
  488. }
  489. }
  490. return op.isValid(val, comp, null);
  491. case CONTAINS_TEXT:
  492. // implemented both by a cfRule "text" attribute and a formula. Use the text.
  493. return cv.toString().toLowerCase().contains(text.toLowerCase());
  494. case NOT_CONTAINS_TEXT:
  495. // implemented both by a cfRule "text" attribute and a formula. Use the text.
  496. return ! cv.toString().toLowerCase().contains(text.toLowerCase());
  497. case BEGINS_WITH:
  498. // implemented both by a cfRule "text" attribute and a formula. Use the text.
  499. return cv.toString().toLowerCase().startsWith(text.toLowerCase());
  500. case ENDS_WITH:
  501. // implemented both by a cfRule "text" attribute and a formula. Use the text.
  502. return cv.toString().toLowerCase().endsWith(text.toLowerCase());
  503. case CONTAINS_BLANKS:
  504. try {
  505. String v = cv.getString();
  506. // see TextFunction.TRIM for implementation
  507. return v == null || v.trim().length() == 0;
  508. } catch (Exception e) {
  509. // not a valid string value, and not a blank cell (that's checked earlier)
  510. return false;
  511. }
  512. case NOT_CONTAINS_BLANKS:
  513. try {
  514. String v = cv.getString();
  515. // see TextFunction.TRIM for implementation
  516. return v != null && v.trim().length() > 0;
  517. } catch (Exception e) {
  518. // not a valid string value, but not blank
  519. return true;
  520. }
  521. case CONTAINS_ERRORS:
  522. return cell != null && DataValidationEvaluator.isType(cell, CellType.ERROR);
  523. case NOT_CONTAINS_ERRORS:
  524. return cell == null || ! DataValidationEvaluator.isType(cell, CellType.ERROR);
  525. case TIME_PERIOD:
  526. // implemented both by a cfRule "text" attribute and a formula. Use the formula.
  527. return checkFormula(ref, region);
  528. default:
  529. return false;
  530. }
  531. }
  532. /**
  533. * from testing, Excel only operates on numbers and dates (which are stored as numbers) in the range.
  534. * numbers stored as text are ignored, but numbers formatted as text are treated as numbers.
  535. *
  536. * @param region
  537. * @return the meaningful values in the range of cells specified
  538. */
  539. private Set<ValueAndFormat> getMeaningfulValues(CellRangeAddress region, boolean withText, ValueFunction func) {
  540. Set<ValueAndFormat> values = meaningfulRegionValues.get(region);
  541. if (values != null) {
  542. return values;
  543. }
  544. List<ValueAndFormat> allValues = new ArrayList<>((region.getLastColumn() - region.getFirstColumn() + 1) * (region.getLastRow() - region.getFirstRow() + 1));
  545. for (int r=region.getFirstRow(); r <= region.getLastRow(); r++) {
  546. final Row row = sheet.getRow(r);
  547. if (row == null) {
  548. continue;
  549. }
  550. for (int c = region.getFirstColumn(); c <= region.getLastColumn(); c++) {
  551. Cell cell = row.getCell(c);
  552. final ValueAndFormat cv = getCellValue(cell);
  553. if (cv != null && (withText || cv.isNumber()) ) {
  554. allValues.add(cv);
  555. }
  556. }
  557. }
  558. values = func.evaluate(allValues);
  559. meaningfulRegionValues.put(region, values);
  560. return values;
  561. }
  562. private ValueAndFormat getCellValue(Cell cell) {
  563. if (cell != null) {
  564. final CellType type = cell.getCellType();
  565. if (type == CellType.NUMERIC || (type == CellType.FORMULA && cell.getCachedFormulaResultType() == CellType.NUMERIC) ) {
  566. return new ValueAndFormat(Double.valueOf(cell.getNumericCellValue()), cell.getCellStyle().getDataFormatString(), decimalTextFormat);
  567. } else if (type == CellType.STRING || (type == CellType.FORMULA && cell.getCachedFormulaResultType() == CellType.STRING) ) {
  568. return new ValueAndFormat(cell.getStringCellValue(), cell.getCellStyle().getDataFormatString());
  569. } else if (type == CellType.BOOLEAN || (type == CellType.FORMULA && cell.getCachedFormulaResultType() == CellType.BOOLEAN) ) {
  570. return new ValueAndFormat(cell.getStringCellValue(), cell.getCellStyle().getDataFormatString());
  571. }
  572. }
  573. return new ValueAndFormat("", "");
  574. }
  575. /**
  576. * instances evaluate the values for a region and return the positive matches for the function type.
  577. * TODO: when we get to use Java 8, this is obviously a Lambda Function.
  578. */
  579. protected interface ValueFunction {
  580. /**
  581. *
  582. * @param values
  583. * @return the desired values for the rules implemented by the current instance
  584. */
  585. Set<ValueAndFormat> evaluate(List<ValueAndFormat> values);
  586. }
  587. /**
  588. * Not calling it OperatorType to avoid confusion for now with other classes.
  589. * Definition order matches OOXML type ID indexes.
  590. * Note that this has NO_COMPARISON as the first item, unlike the similar
  591. * DataValidation operator enum. Thanks, Microsoft.
  592. */
  593. public static enum OperatorEnum {
  594. NO_COMPARISON {
  595. /** always false/invalid */
  596. @Override
  597. public <C extends Comparable<C>> boolean isValid(C cellValue, C v1, C v2) {
  598. return false;
  599. }
  600. },
  601. BETWEEN {
  602. @Override
  603. public <C extends Comparable<C>> boolean isValid(C cellValue, C v1, C v2) {
  604. if (v1 == null) {
  605. if (cellValue instanceof Number) {
  606. // use zero for null
  607. double n1 = 0;
  608. double n2 = v2 == null ? 0 : ((Number) v2).doubleValue();
  609. return Double.compare( ((Number) cellValue).doubleValue(), n1) >= 0 && Double.compare(((Number) cellValue).doubleValue(), n2) <= 0;
  610. } else if (cellValue instanceof String) {
  611. String n1 = "";
  612. String n2 = v2 == null ? "" : (String) v2;
  613. return ((String) cellValue).compareToIgnoreCase(n1) >= 0 && ((String) cellValue).compareToIgnoreCase(n2) <= 0;
  614. } else if (cellValue instanceof Boolean) return false;
  615. return false; // just in case - not a typical possibility
  616. }
  617. return cellValue.compareTo(v1) >= 0 && cellValue.compareTo(v2) <= 0;
  618. }
  619. },
  620. NOT_BETWEEN {
  621. @Override
  622. public <C extends Comparable<C>> boolean isValid(C cellValue, C v1, C v2) {
  623. if (v1 == null) {
  624. if (cellValue instanceof Number) {
  625. // use zero for null
  626. double n1 = 0;
  627. double n2 = v2 == null ? 0 : ((Number) v2).doubleValue();
  628. return Double.compare( ((Number) cellValue).doubleValue(), n1) < 0 || Double.compare(((Number) cellValue).doubleValue(), n2) > 0;
  629. } else if (cellValue instanceof String) {
  630. String n1 = "";
  631. String n2 = v2 == null ? "" : (String) v2;
  632. return ((String) cellValue).compareToIgnoreCase(n1) < 0 || ((String) cellValue).compareToIgnoreCase(n2) > 0;
  633. } else if (cellValue instanceof Boolean) return true;
  634. return false; // just in case - not a typical possibility
  635. }
  636. return cellValue.compareTo(v1) < 0 || cellValue.compareTo(v2) > 0;
  637. }
  638. public boolean isValidForIncompatibleTypes() {
  639. return true;
  640. }
  641. },
  642. EQUAL {
  643. @Override
  644. public <C extends Comparable<C>> boolean isValid(C cellValue, C v1, C v2) {
  645. if (v1 == null) {
  646. if (cellValue instanceof Number) {
  647. // use zero for null
  648. return Double.compare( ((Number) cellValue).doubleValue(), 0) == 0;
  649. } else if (cellValue instanceof String) {
  650. return false; // even an empty string is not equal the empty cell, only another empty cell is, handled higher up
  651. } else if (cellValue instanceof Boolean) return false;
  652. return false; // just in case - not a typical possibility
  653. }
  654. // need to avoid instanceof, to work around a 1.6 compiler bug
  655. if (cellValue.getClass() == String.class) {
  656. return cellValue.toString().compareToIgnoreCase(v1.toString()) == 0;
  657. }
  658. return cellValue.compareTo(v1) == 0;
  659. }
  660. },
  661. NOT_EQUAL {
  662. @Override
  663. public <C extends Comparable<C>> boolean isValid(C cellValue, C v1, C v2) {
  664. if (v1 == null) {
  665. return true; // non-null not equal null, returns true
  666. }
  667. // need to avoid instanceof, to work around a 1.6 compiler bug
  668. if (cellValue.getClass() == String.class) {
  669. return cellValue.toString().compareToIgnoreCase(v1.toString()) == 0;
  670. }
  671. return cellValue.compareTo(v1) != 0;
  672. }
  673. public boolean isValidForIncompatibleTypes() {
  674. return true;
  675. }
  676. },
  677. GREATER_THAN {
  678. @Override
  679. public <C extends Comparable<C>> boolean isValid(C cellValue, C v1, C v2) {
  680. if (v1 == null) {
  681. if (cellValue instanceof Number) {
  682. // use zero for null
  683. return Double.compare( ((Number) cellValue).doubleValue(), 0) > 0;
  684. } else if (cellValue instanceof String) {
  685. return true; // non-null string greater than empty cell
  686. } else if (cellValue instanceof Boolean) return true;
  687. return false; // just in case - not a typical possibility
  688. }
  689. return cellValue.compareTo(v1) > 0;
  690. }
  691. },
  692. LESS_THAN {
  693. @Override
  694. public <C extends Comparable<C>> boolean isValid(C cellValue, C v1, C v2) {
  695. if (v1 == null) {
  696. if (cellValue instanceof Number) {
  697. // use zero for null
  698. return Double.compare( ((Number) cellValue).doubleValue(), 0) < 0;
  699. } else if (cellValue instanceof String) {
  700. return false; // non-null string greater than empty cell
  701. } else if (cellValue instanceof Boolean) return false;
  702. return false; // just in case - not a typical possibility
  703. }
  704. return cellValue.compareTo(v1) < 0;
  705. }
  706. },
  707. GREATER_OR_EQUAL {
  708. @Override
  709. public <C extends Comparable<C>> boolean isValid(C cellValue, C v1, C v2) {
  710. if (v1 == null) {
  711. if (cellValue instanceof Number) {
  712. // use zero for null
  713. return Double.compare( ((Number) cellValue).doubleValue(), 0) >= 0;
  714. } else if (cellValue instanceof String) {
  715. return true; // non-null string greater than empty cell
  716. } else if (cellValue instanceof Boolean) return true;
  717. return false; // just in case - not a typical possibility
  718. }
  719. return cellValue.compareTo(v1) >= 0;
  720. }
  721. },
  722. LESS_OR_EQUAL {
  723. @Override
  724. public <C extends Comparable<C>> boolean isValid(C cellValue, C v1, C v2) {
  725. if (v1 == null) {
  726. if (cellValue instanceof Number) {
  727. // use zero for null
  728. return Double.compare( ((Number) cellValue).doubleValue(), 0) <= 0;
  729. } else if (cellValue instanceof String) {
  730. return false; // non-null string not less than empty cell
  731. } else if (cellValue instanceof Boolean) return false; // for completeness
  732. return false; // just in case - not a typical possibility
  733. }
  734. return cellValue.compareTo(v1) <= 0;
  735. }
  736. },
  737. ;
  738. /**
  739. * Evaluates comparison using operator instance rules
  740. * @param cellValue won't be null, assumption is previous checks handled that
  741. * @param v1 if null, per Excel behavior various results depending on the type of cellValue and the specific enum instance
  742. * @param v2 null if not needed. If null when needed, various results, per Excel behavior
  743. * @return true if the comparison is valid
  744. */
  745. public abstract <C extends Comparable<C>> boolean isValid(C cellValue, C v1, C v2);
  746. /**
  747. * Called when the cell and comparison values are of different data types
  748. * Needed for negation operators, which should return true.
  749. * @return true if this comparison is true when the types to compare are different
  750. */
  751. public boolean isValidForIncompatibleTypes() {
  752. return false;
  753. }
  754. }
  755. /**
  756. * Note: this class has a natural ordering that is inconsistent with equals.
  757. */
  758. protected static class ValueAndFormat implements Comparable<ValueAndFormat> {
  759. private final Double value;
  760. private final String string;
  761. private final String format;
  762. private final DecimalFormat decimalTextFormat;
  763. public ValueAndFormat(Double value, String format, DecimalFormat df) {
  764. this.value = value;
  765. this.format = format;
  766. string = null;
  767. decimalTextFormat = df;
  768. }
  769. public ValueAndFormat(String value, String format) {
  770. this.value = null;
  771. this.format = format;
  772. string = value;
  773. decimalTextFormat = null;
  774. }
  775. public boolean isNumber() {
  776. return value != null;
  777. }
  778. public Double getValue() {
  779. return value;
  780. }
  781. public String getString() {
  782. return string;
  783. }
  784. public String toString() {
  785. if(isNumber()) {
  786. return decimalTextFormat.format(getValue().doubleValue());
  787. } else {
  788. return getString();
  789. }
  790. }
  791. @Override
  792. public boolean equals(Object obj) {
  793. if (!(obj instanceof ValueAndFormat)) {
  794. return false;
  795. }
  796. ValueAndFormat o = (ValueAndFormat) obj;
  797. return ( value == o.value || value.equals(o.value))
  798. && ( format == o.format || format.equals(o.format))
  799. && (string == o.string || string.equals(o.string));
  800. }
  801. /**
  802. * Note: this class has a natural ordering that is inconsistent with equals.
  803. * @param o
  804. * @return value comparison
  805. */
  806. @Override
  807. public int compareTo(ValueAndFormat o) {
  808. if (value == null && o.value != null) {
  809. return 1;
  810. }
  811. if (o.value == null && value != null) {
  812. return -1;
  813. }
  814. int cmp = value == null ? 0 : value.compareTo(o.value);
  815. if (cmp != 0) {
  816. return cmp;
  817. }
  818. if (string == null && o.string != null) {
  819. return 1;
  820. }
  821. if (o.string == null && string != null) {
  822. return -1;
  823. }
  824. return string == null ? 0 : string.compareTo(o.string);
  825. }
  826. @Override
  827. public int hashCode() {
  828. return (string == null ? 0 : string.hashCode()) * 37 * 37 + 37 * (value == null ? 0 : value.hashCode()) + (format == null ? 0 : format.hashCode());
  829. }
  830. }
  831. }