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 15KB

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