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.

DVConstraint.java 19KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514
  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.hssf.usermodel;
  16. import java.text.ParseException;
  17. import java.text.SimpleDateFormat;
  18. import java.util.Date;
  19. import java.util.regex.Pattern;
  20. import org.apache.poi.hssf.model.HSSFFormulaParser;
  21. import org.apache.poi.hssf.record.DVRecord;
  22. import org.apache.poi.ss.formula.FormulaRenderer;
  23. import org.apache.poi.ss.formula.FormulaRenderingWorkbook;
  24. import org.apache.poi.ss.formula.FormulaType;
  25. import org.apache.poi.ss.formula.ptg.NumberPtg;
  26. import org.apache.poi.ss.formula.ptg.Ptg;
  27. import org.apache.poi.ss.formula.ptg.StringPtg;
  28. import org.apache.poi.ss.usermodel.DataValidationConstraint;
  29. import org.apache.poi.ss.usermodel.DateUtil;
  30. import org.apache.poi.util.LocaleUtil;
  31. /**
  32. * Data Validation Constraint
  33. */
  34. public class DVConstraint implements DataValidationConstraint {
  35. /* package */ static final class FormulaPair {
  36. private final Ptg[] _formula1;
  37. private final Ptg[] _formula2;
  38. FormulaPair(Ptg[] formula1, Ptg[] formula2) {
  39. _formula1 = (formula1 == null) ? null : formula1.clone();
  40. _formula2 = (formula2 == null) ? null : formula2.clone();
  41. }
  42. public Ptg[] getFormula1() {
  43. return _formula1;
  44. }
  45. public Ptg[] getFormula2() {
  46. return _formula2;
  47. }
  48. }
  49. private final int _validationType;
  50. private int _operator;
  51. private String[] _explicitListValues;
  52. private String _formula1;
  53. private String _formula2;
  54. private Double _value1;
  55. private Double _value2;
  56. private DVConstraint(int validationType, int comparisonOperator, String formulaA,
  57. String formulaB, Double value1, Double value2, String[] explicitListValues) {
  58. _validationType = validationType;
  59. _operator = comparisonOperator;
  60. _formula1 = formulaA;
  61. _formula2 = formulaB;
  62. _value1 = value1;
  63. _value2 = value2;
  64. _explicitListValues = (explicitListValues == null) ? null : explicitListValues.clone();
  65. }
  66. /**
  67. * Creates a list constraint
  68. */
  69. private DVConstraint(String listFormula, String[] explicitListValues) {
  70. this(ValidationType.LIST, OperatorType.IGNORED,
  71. listFormula, null, null, null, explicitListValues);
  72. }
  73. /**
  74. * Creates a number based data validation constraint. The text values entered for expr1 and expr2
  75. * can be either standard Excel formulas or formatted number values. If the expression starts
  76. * with '=' it is parsed as a formula, otherwise it is parsed as a formatted number.
  77. *
  78. * @param validationType one of {@link org.apache.poi.ss.usermodel.DataValidationConstraint.ValidationType#ANY},
  79. * {@link org.apache.poi.ss.usermodel.DataValidationConstraint.ValidationType#DECIMAL},
  80. * {@link org.apache.poi.ss.usermodel.DataValidationConstraint.ValidationType#INTEGER},
  81. * {@link org.apache.poi.ss.usermodel.DataValidationConstraint.ValidationType#TEXT_LENGTH}
  82. * @param comparisonOperator any constant from {@link org.apache.poi.ss.usermodel.DataValidationConstraint.OperatorType} enum
  83. * @param expr1 date formula (when first char is '=') or formatted number value
  84. * @param expr2 date formula (when first char is '=') or formatted number value
  85. */
  86. public static DVConstraint createNumericConstraint(int validationType, int comparisonOperator,
  87. String expr1, String expr2) {
  88. switch (validationType) {
  89. case ValidationType.ANY:
  90. if (expr1 != null || expr2 != null) {
  91. throw new IllegalArgumentException("expr1 and expr2 must be null for validation type 'any'");
  92. }
  93. break;
  94. case ValidationType.DECIMAL:
  95. case ValidationType.INTEGER:
  96. case ValidationType.TEXT_LENGTH:
  97. if (expr1 == null) {
  98. throw new IllegalArgumentException("expr1 must be supplied");
  99. }
  100. OperatorType.validateSecondArg(comparisonOperator, expr2);
  101. break;
  102. default:
  103. throw new IllegalArgumentException("Validation Type ("
  104. + validationType + ") not supported with this method");
  105. }
  106. // formula1 and value1 are mutually exclusive
  107. String formula1 = getFormulaFromTextExpression(expr1);
  108. Double value1 = formula1 == null ? convertNumber(expr1) : null;
  109. // formula2 and value2 are mutually exclusive
  110. String formula2 = getFormulaFromTextExpression(expr2);
  111. Double value2 = formula2 == null ? convertNumber(expr2) : null;
  112. return new DVConstraint(validationType, comparisonOperator, formula1, formula2, value1, value2, null);
  113. }
  114. public static DVConstraint createFormulaListConstraint(String listFormula) {
  115. return new DVConstraint(listFormula, null);
  116. }
  117. public static DVConstraint createExplicitListConstraint(String[] explicitListValues) {
  118. return new DVConstraint(null, explicitListValues);
  119. }
  120. /**
  121. * Creates a time based data validation constraint. The text values entered for expr1 and expr2
  122. * can be either standard Excel formulas or formatted time values. If the expression starts
  123. * with '=' it is parsed as a formula, otherwise it is parsed as a formatted time. To parse
  124. * formatted times, two formats are supported: "HH:MM" or "HH:MM:SS". This is contrary to
  125. * Excel which uses the default time format from the OS.
  126. *
  127. * @param comparisonOperator constant from {@link org.apache.poi.ss.usermodel.DataValidationConstraint.OperatorType} enum
  128. * @param expr1 date formula (when first char is '=') or formatted time value
  129. * @param expr2 date formula (when first char is '=') or formatted time value
  130. */
  131. public static DVConstraint createTimeConstraint(int comparisonOperator, String expr1, String expr2) {
  132. if (expr1 == null) {
  133. throw new IllegalArgumentException("expr1 must be supplied");
  134. }
  135. OperatorType.validateSecondArg(comparisonOperator, expr1);
  136. // formula1 and value1 are mutually exclusive
  137. String formula1 = getFormulaFromTextExpression(expr1);
  138. Double value1 = formula1 == null ? convertTime(expr1) : null;
  139. // formula2 and value2 are mutually exclusive
  140. String formula2 = getFormulaFromTextExpression(expr2);
  141. Double value2 = formula2 == null ? convertTime(expr2) : null;
  142. return new DVConstraint(ValidationType.TIME, comparisonOperator, formula1, formula2, value1, value2, null);
  143. }
  144. /**
  145. * Creates a date based data validation constraint. The text values entered for expr1 and expr2
  146. * can be either standard Excel formulas or formatted date values. If the expression starts
  147. * with '=' it is parsed as a formula, otherwise it is parsed as a formatted date (Excel uses
  148. * the same convention). To parse formatted dates, a date format needs to be specified. This
  149. * is contrary to Excel which uses the default short date format from the OS.
  150. *
  151. * @param comparisonOperator constant from {@link org.apache.poi.ss.usermodel.DataValidationConstraint.OperatorType} enum
  152. * @param expr1 date formula (when first char is '=') or formatted date value
  153. * @param expr2 date formula (when first char is '=') or formatted date value
  154. * @param dateFormat ignored if both expr1 and expr2 are formulas. Default value is "YYYY/MM/DD"
  155. * otherwise any other valid argument for <tt>SimpleDateFormat</tt> can be used
  156. * @see <a href='http://java.sun.com/j2se/1.5.0/docs/api/java/text/DateFormat.html'>SimpleDateFormat</a>
  157. */
  158. public static DVConstraint createDateConstraint(int comparisonOperator, String expr1, String expr2, String dateFormat) {
  159. if (expr1 == null) {
  160. throw new IllegalArgumentException("expr1 must be supplied");
  161. }
  162. OperatorType.validateSecondArg(comparisonOperator, expr2);
  163. SimpleDateFormat df = null;
  164. if (dateFormat != null) {
  165. df = new SimpleDateFormat(dateFormat, LocaleUtil.getUserLocale());
  166. df.setTimeZone(LocaleUtil.getUserTimeZone());
  167. }
  168. // formula1 and value1 are mutually exclusive
  169. String formula1 = getFormulaFromTextExpression(expr1);
  170. Double value1 = formula1 == null ? convertDate(expr1, df) : null;
  171. // formula2 and value2 are mutually exclusive
  172. String formula2 = getFormulaFromTextExpression(expr2);
  173. Double value2 = formula2 == null ? convertDate(expr2, df) : null;
  174. return new DVConstraint(ValidationType.DATE, comparisonOperator, formula1, formula2, value1, value2, null);
  175. }
  176. /**
  177. * Distinguishes formula expressions from simple value expressions. This logic is only
  178. * required by a few factory methods in this class that create data validation constraints
  179. * from more or less the same parameters that would have been entered in the Excel UI. The
  180. * data validation dialog box uses the convention that formulas begin with '='. Other methods
  181. * in this class follow the POI convention (formulas and values are distinct), so the '='
  182. * convention is not used there.
  183. *
  184. * @param textExpr a formula or value expression
  185. * @return all text after '=' if textExpr begins with '='. Otherwise <code>null</code> if textExpr does not begin with '='
  186. */
  187. private static String getFormulaFromTextExpression(String textExpr) {
  188. if (textExpr == null) {
  189. return null;
  190. }
  191. if (textExpr.length() < 1) {
  192. throw new IllegalArgumentException("Empty string is not a valid formula/value expression");
  193. }
  194. if (textExpr.charAt(0) == '=') {
  195. return textExpr.substring(1);
  196. }
  197. return null;
  198. }
  199. /**
  200. * @return <code>null</code> if numberStr is <code>null</code>
  201. */
  202. private static Double convertNumber(String numberStr) {
  203. if (numberStr == null) {
  204. return null;
  205. }
  206. try {
  207. return Double.valueOf(numberStr);
  208. } catch (NumberFormatException e) {
  209. throw new RuntimeException("The supplied text '" + numberStr
  210. + "' could not be parsed as a number");
  211. }
  212. }
  213. /**
  214. * @return <code>null</code> if timeStr is <code>null</code>
  215. */
  216. private static Double convertTime(String timeStr) {
  217. if (timeStr == null) {
  218. return null;
  219. }
  220. return Double.valueOf(DateUtil.convertTime(timeStr));
  221. }
  222. /**
  223. * @param dateFormat pass <code>null</code> for default YYYYMMDD
  224. * @return <code>null</code> if timeStr is <code>null</code>
  225. */
  226. private static Double convertDate(String dateStr, SimpleDateFormat dateFormat) {
  227. if (dateStr == null) {
  228. return null;
  229. }
  230. Date dateVal;
  231. if (dateFormat == null) {
  232. dateVal = DateUtil.parseYYYYMMDDDate(dateStr);
  233. } else {
  234. try {
  235. dateVal = dateFormat.parse(dateStr);
  236. } catch (ParseException e) {
  237. throw new RuntimeException("Failed to parse date '" + dateStr
  238. + "' using specified format '" + dateFormat + "'", e);
  239. }
  240. }
  241. return Double.valueOf(DateUtil.getExcelDate(dateVal));
  242. }
  243. public static DVConstraint createCustomFormulaConstraint(String formula) {
  244. if (formula == null) {
  245. throw new IllegalArgumentException("formula must be supplied");
  246. }
  247. return new DVConstraint(ValidationType.FORMULA, OperatorType.IGNORED, formula, null, null, null, null);
  248. }
  249. /* (non-Javadoc)
  250. * @see org.apache.poi.hssf.usermodel.DataValidationConstraint#getValidationType()
  251. */
  252. public int getValidationType() {
  253. return _validationType;
  254. }
  255. /**
  256. * Convenience method
  257. * @return <code>true</code> if this constraint is a 'list' validation
  258. */
  259. public boolean isListValidationType() {
  260. return _validationType == ValidationType.LIST;
  261. }
  262. /**
  263. * Convenience method
  264. * @return <code>true</code> if this constraint is a 'list' validation with explicit values
  265. */
  266. public boolean isExplicitList() {
  267. return _validationType == ValidationType.LIST && _explicitListValues != null;
  268. }
  269. /* (non-Javadoc)
  270. * @see org.apache.poi.hssf.usermodel.DataValidationConstraint#getOperator()
  271. */
  272. public int getOperator() {
  273. return _operator;
  274. }
  275. /* (non-Javadoc)
  276. * @see org.apache.poi.hssf.usermodel.DataValidationConstraint#setOperator(int)
  277. */
  278. public void setOperator(int operator) {
  279. _operator = operator;
  280. }
  281. /* (non-Javadoc)
  282. * @see org.apache.poi.hssf.usermodel.DataValidationConstraint#getExplicitListValues()
  283. */
  284. public String[] getExplicitListValues() {
  285. return _explicitListValues;
  286. }
  287. /* (non-Javadoc)
  288. * @see org.apache.poi.hssf.usermodel.DataValidationConstraint#setExplicitListValues(java.lang.String[])
  289. */
  290. public void setExplicitListValues(String[] explicitListValues) {
  291. if (_validationType != ValidationType.LIST) {
  292. throw new RuntimeException("Cannot setExplicitListValues on non-list constraint");
  293. }
  294. _formula1 = null;
  295. _explicitListValues = explicitListValues;
  296. }
  297. /* (non-Javadoc)
  298. * @see org.apache.poi.hssf.usermodel.DataValidationConstraint#getFormula1()
  299. */
  300. public String getFormula1() {
  301. return _formula1;
  302. }
  303. /* (non-Javadoc)
  304. * @see org.apache.poi.hssf.usermodel.DataValidationConstraint#setFormula1(java.lang.String)
  305. */
  306. public void setFormula1(String formula1) {
  307. _value1 = null;
  308. _explicitListValues = null;
  309. _formula1 = formula1;
  310. }
  311. /* (non-Javadoc)
  312. * @see org.apache.poi.hssf.usermodel.DataValidationConstraint#getFormula2()
  313. */
  314. public String getFormula2() {
  315. return _formula2;
  316. }
  317. /* (non-Javadoc)
  318. * @see org.apache.poi.hssf.usermodel.DataValidationConstraint#setFormula2(java.lang.String)
  319. */
  320. public void setFormula2(String formula2) {
  321. _value2 = null;
  322. _formula2 = formula2;
  323. }
  324. /**
  325. * @return the numeric value for expression 1. May be <code>null</code>
  326. */
  327. public Double getValue1() {
  328. return _value1;
  329. }
  330. /**
  331. * Sets a numeric value for expression 1.
  332. */
  333. public void setValue1(double value1) {
  334. _formula1 = null;
  335. _value1 = Double.valueOf(value1);
  336. }
  337. /**
  338. * @return the numeric value for expression 2. May be <code>null</code>
  339. */
  340. public Double getValue2() {
  341. return _value2;
  342. }
  343. /**
  344. * Sets a numeric value for expression 2.
  345. */
  346. public void setValue2(double value2) {
  347. _formula2 = null;
  348. _value2 = Double.valueOf(value2);
  349. }
  350. /**
  351. * @return both parsed formulas (for expression 1 and 2).
  352. */
  353. /* package */ FormulaPair createFormulas(HSSFSheet sheet) {
  354. Ptg[] formula1;
  355. Ptg[] formula2;
  356. if (isListValidationType()) {
  357. formula1 = createListFormula(sheet);
  358. formula2 = Ptg.EMPTY_PTG_ARRAY;
  359. } else {
  360. formula1 = convertDoubleFormula(_formula1, _value1, sheet);
  361. formula2 = convertDoubleFormula(_formula2, _value2, sheet);
  362. }
  363. return new FormulaPair(formula1, formula2);
  364. }
  365. @SuppressWarnings("resource")
  366. private Ptg[] createListFormula(HSSFSheet sheet) {
  367. if (_explicitListValues == null) {
  368. HSSFWorkbook wb = sheet.getWorkbook();
  369. // formula is parsed with slightly different RVA rules: (root node type must be 'reference')
  370. return HSSFFormulaParser.parse(_formula1, wb, FormulaType.DATAVALIDATION_LIST, wb.getSheetIndex(sheet));
  371. // To do: Excel places restrictions on the available operations within a list formula.
  372. // Some things like union and intersection are not allowed.
  373. }
  374. // explicit list was provided
  375. StringBuilder sb = new StringBuilder(_explicitListValues.length * 16);
  376. for (int i = 0; i < _explicitListValues.length; i++) {
  377. if (i > 0) {
  378. sb.append('\0'); // list delimiter is the nul char
  379. }
  380. sb.append(_explicitListValues[i]);
  381. }
  382. return new Ptg[] { new StringPtg(sb.toString()), };
  383. }
  384. /**
  385. * @return The parsed token array representing the formula or value specified.
  386. * Empty array if both formula and value are <code>null</code>
  387. */
  388. @SuppressWarnings("resource")
  389. private static Ptg[] convertDoubleFormula(String formula, Double value, HSSFSheet sheet) {
  390. if (formula == null) {
  391. if (value == null) {
  392. return Ptg.EMPTY_PTG_ARRAY;
  393. }
  394. return new Ptg[] { new NumberPtg(value.doubleValue()), };
  395. }
  396. if (value != null) {
  397. throw new IllegalStateException("Both formula and value cannot be present");
  398. }
  399. HSSFWorkbook wb = sheet.getWorkbook();
  400. return HSSFFormulaParser.parse(formula, wb, FormulaType.CELL, wb.getSheetIndex(sheet));
  401. }
  402. static DVConstraint createDVConstraint(DVRecord dvRecord, FormulaRenderingWorkbook book) {
  403. switch (dvRecord.getDataType()) {
  404. case ValidationType.ANY:
  405. return new DVConstraint(ValidationType.ANY, dvRecord.getConditionOperator(), null, null, null, null, null);
  406. case ValidationType.INTEGER:
  407. case ValidationType.DECIMAL:
  408. case ValidationType.DATE:
  409. case ValidationType.TIME:
  410. case ValidationType.TEXT_LENGTH:
  411. FormulaValuePair pair1 = toFormulaString(dvRecord.getFormula1(), book);
  412. FormulaValuePair pair2 = toFormulaString(dvRecord.getFormula2(), book);
  413. return new DVConstraint(dvRecord.getDataType(), dvRecord.getConditionOperator(), pair1.formula(),
  414. pair2.formula(), pair1.value(), pair2.value(), null);
  415. case ValidationType.LIST:
  416. if (dvRecord.getListExplicitFormula()) {
  417. String values = toFormulaString(dvRecord.getFormula1(), book).string();
  418. if (values.startsWith("\"")) {
  419. values = values.substring(1);
  420. }
  421. if (values.endsWith("\"")) {
  422. values = values.substring(0, values.length() - 1);
  423. }
  424. String[] explicitListValues = values.split(Pattern.quote("\0"));
  425. return createExplicitListConstraint(explicitListValues);
  426. } else {
  427. String listFormula = toFormulaString(dvRecord.getFormula1(), book).string();
  428. return createFormulaListConstraint(listFormula);
  429. }
  430. case ValidationType.FORMULA:
  431. return createCustomFormulaConstraint(toFormulaString(dvRecord.getFormula1(), book).string());
  432. default:
  433. throw new UnsupportedOperationException("validationType="+dvRecord.getDataType());
  434. }
  435. }
  436. private static class FormulaValuePair {
  437. private String _formula;
  438. private String _value;
  439. public String formula() {
  440. return _formula;
  441. }
  442. public Double value() {
  443. if (_value == null) {
  444. return null;
  445. }
  446. return Double.valueOf(_value);
  447. }
  448. public String string() {
  449. if (_formula != null) {
  450. return _formula;
  451. }
  452. if (_value != null) {
  453. return _value;
  454. }
  455. return null;
  456. }
  457. }
  458. private static FormulaValuePair toFormulaString(Ptg[] ptgs, FormulaRenderingWorkbook book) {
  459. FormulaValuePair pair = new FormulaValuePair();
  460. if (ptgs != null && ptgs.length > 0) {
  461. String string = FormulaRenderer.toFormulaString(book, ptgs);
  462. if (ptgs.length == 1 && ptgs[0].getClass() == NumberPtg.class) {
  463. pair._value = string;
  464. } else {
  465. pair._formula = string;
  466. }
  467. }
  468. return pair;
  469. }
  470. }