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.

DVRecord.java 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357
  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.record;
  16. import static org.apache.poi.util.GenericRecordUtil.getBitsAsString;
  17. import java.util.Map;
  18. import java.util.function.Supplier;
  19. import org.apache.poi.hssf.record.common.UnicodeString;
  20. import org.apache.poi.hssf.usermodel.HSSFDataValidation;
  21. import org.apache.poi.ss.formula.Formula;
  22. import org.apache.poi.ss.formula.ptg.Ptg;
  23. import org.apache.poi.ss.util.CellRangeAddressList;
  24. import org.apache.poi.util.BitField;
  25. import org.apache.poi.util.GenericRecordUtil;
  26. import org.apache.poi.util.LittleEndianOutput;
  27. import org.apache.poi.util.StringUtil;
  28. /**
  29. * This record stores data validation settings and a list of cell ranges which contain these settings.
  30. * The data validation settings of a sheet are stored in a sequential list of DV records.
  31. * This list is followed by DVAL record(s)
  32. */
  33. public final class DVRecord extends StandardRecord {
  34. public static final short sid = 0x01BE;
  35. /** the unicode string used for error/prompt title/text when not present */
  36. private static final UnicodeString NULL_TEXT_STRING = new UnicodeString("\0");
  37. /**
  38. * Option flags field
  39. *
  40. * @see HSSFDataValidation utility class
  41. */
  42. private static final BitField opt_data_type = new BitField(0x0000000F);
  43. private static final BitField opt_error_style = new BitField(0x00000070);
  44. private static final BitField opt_string_list_formula = new BitField(0x00000080);
  45. private static final BitField opt_empty_cell_allowed = new BitField(0x00000100);
  46. private static final BitField opt_suppress_dropdown_arrow = new BitField(0x00000200);
  47. private static final BitField opt_show_prompt_on_cell_selected = new BitField(0x00040000);
  48. private static final BitField opt_show_error_on_invalid_value = new BitField(0x00080000);
  49. private static final BitField opt_condition_operator = new BitField(0x00700000);
  50. private static final int[] FLAG_MASKS = { 0x0000000F,0x00000070,0x00000080,0x00000100,
  51. 0x00000200,0x00040000,0x00080000,0x00700000 };
  52. private static final String[] FLAG_NAMES = { "DATA_TYPE", "ERROR_STYLE", "STRING_LIST_FORMULA",
  53. "EMPTY_CELL_ALLOWED", "SUPPRESS_DROPDOWN_ARROW", "SHOW_PROMPT_ON_CELL_SELECTED",
  54. "SHOW_ERROR_ON_INVALID_VALUE", "CONDITION_OPERATOR" };
  55. /** Option flags */
  56. private int _option_flags;
  57. /** Title of the prompt box, cannot be longer than 32 chars */
  58. private final UnicodeString _promptTitle;
  59. /** Title of the error box, cannot be longer than 32 chars */
  60. private final UnicodeString _errorTitle;
  61. /** Text of the prompt box, cannot be longer than 255 chars */
  62. private final UnicodeString _promptText;
  63. /** Text of the error box, cannot be longer than 255 chars */
  64. private final UnicodeString _errorText;
  65. /** Not used - Excel seems to always write 0x3FE0 */
  66. private short _not_used_1 = 0x3FE0;
  67. /** Formula data for first condition (RPN token array without size field) */
  68. private final Formula _formula1;
  69. /** Not used - Excel seems to always write 0x0000 */
  70. @SuppressWarnings("RedundantFieldInitialization")
  71. private short _not_used_2 = 0x0000;
  72. /** Formula data for second condition (RPN token array without size field) */
  73. private final Formula _formula2;
  74. /** Cell range address list with all affected ranges */
  75. private final CellRangeAddressList _regions;
  76. public DVRecord(DVRecord other) {
  77. super(other);
  78. _option_flags = other._option_flags;
  79. _promptTitle = other._promptTitle.copy();
  80. _errorTitle = other._errorTitle.copy();
  81. _promptText = other._promptText.copy();
  82. _errorText = other._errorText.copy();
  83. _not_used_1 = other._not_used_1;
  84. _formula1 = (other._formula1 == null) ? null : other._formula1.copy();
  85. _not_used_2 = other._not_used_2;
  86. _formula2 = (other._formula2 == null) ? null : other._formula2.copy();
  87. _regions = (other._regions == null) ? null : other._regions.copy();
  88. }
  89. public DVRecord(int validationType, int operator, int errorStyle, boolean emptyCellAllowed,
  90. boolean suppressDropDownArrow, boolean isExplicitList,
  91. boolean showPromptBox, String promptTitle, String promptText,
  92. boolean showErrorBox, String errorTitle, String errorText,
  93. Ptg[] formula1, Ptg[] formula2,
  94. CellRangeAddressList regions) {
  95. // check length-limits
  96. if(promptTitle != null && promptTitle.length() > 32) {
  97. throw new IllegalStateException("Prompt-title cannot be longer than 32 characters, but had: " + promptTitle);
  98. }
  99. if(promptText != null && promptText.length() > 255) {
  100. throw new IllegalStateException("Prompt-text cannot be longer than 255 characters, but had: " + promptText);
  101. }
  102. if(errorTitle != null && errorTitle.length() > 32) {
  103. throw new IllegalStateException("Error-title cannot be longer than 32 characters, but had: " + errorTitle);
  104. }
  105. if(errorText != null && errorText.length() > 255) {
  106. throw new IllegalStateException("Error-text cannot be longer than 255 characters, but had: " + errorText);
  107. }
  108. int flags = 0;
  109. flags = opt_data_type.setValue(flags, validationType);
  110. flags = opt_condition_operator.setValue(flags, operator);
  111. flags = opt_error_style.setValue(flags, errorStyle);
  112. flags = opt_empty_cell_allowed.setBoolean(flags, emptyCellAllowed);
  113. flags = opt_suppress_dropdown_arrow.setBoolean(flags, suppressDropDownArrow);
  114. flags = opt_string_list_formula.setBoolean(flags, isExplicitList);
  115. flags = opt_show_prompt_on_cell_selected.setBoolean(flags, showPromptBox);
  116. flags = opt_show_error_on_invalid_value.setBoolean(flags, showErrorBox);
  117. _option_flags = flags;
  118. _promptTitle = resolveTitleText(promptTitle);
  119. _promptText = resolveTitleText(promptText);
  120. _errorTitle = resolveTitleText(errorTitle);
  121. _errorText = resolveTitleText(errorText);
  122. _formula1 = Formula.create(formula1);
  123. _formula2 = Formula.create(formula2);
  124. _regions = regions;
  125. }
  126. public DVRecord(RecordInputStream in) {
  127. _option_flags = in.readInt();
  128. _promptTitle = readUnicodeString(in);
  129. _errorTitle = readUnicodeString(in);
  130. _promptText = readUnicodeString(in);
  131. _errorText = readUnicodeString(in);
  132. int field_size_first_formula = in.readUShort();
  133. _not_used_1 = in.readShort();
  134. // "You may not use unions, intersections or array constants in Data Validation criteria"
  135. // read first formula data condition
  136. _formula1 = Formula.read(field_size_first_formula, in);
  137. int field_size_sec_formula = in.readUShort();
  138. _not_used_2 = in.readShort();
  139. // read sec formula data condition
  140. _formula2 = Formula.read(field_size_sec_formula, in);
  141. // read cell range address list with all affected ranges
  142. _regions = new CellRangeAddressList(in);
  143. }
  144. /**
  145. * @return the condition data type
  146. * @see org.apache.poi.ss.usermodel.DataValidationConstraint.ValidationType
  147. */
  148. public int getDataType() {
  149. return opt_data_type.getValue(_option_flags);
  150. }
  151. /**
  152. * @return the condition error style
  153. * @see org.apache.poi.ss.usermodel.DataValidation.ErrorStyle
  154. */
  155. public int getErrorStyle() {
  156. return opt_error_style.getValue(_option_flags);
  157. }
  158. /**
  159. * @return <code>true</code> if in list validations the string list is explicitly given in the
  160. * formula, <code>false</code> otherwise
  161. */
  162. public boolean getListExplicitFormula() {
  163. return (opt_string_list_formula.isSet(_option_flags));
  164. }
  165. /**
  166. * @return <code>true</code> if empty values are allowed in cells, <code>false</code> otherwise
  167. */
  168. public boolean getEmptyCellAllowed() {
  169. return (opt_empty_cell_allowed.isSet(_option_flags));
  170. }
  171. /**
  172. * @return <code>true</code> if drop down arrow should be suppressed when list validation is
  173. * used, <code>false</code> otherwise
  174. */
  175. public boolean getSuppressDropdownArrow() {
  176. return (opt_suppress_dropdown_arrow.isSet(_option_flags));
  177. }
  178. /**
  179. * @return <code>true</code> if a prompt window should appear when cell is selected, <code>false</code> otherwise
  180. */
  181. public boolean getShowPromptOnCellSelected() {
  182. return (opt_show_prompt_on_cell_selected.isSet(_option_flags));
  183. }
  184. /**
  185. * @return <code>true</code> if an error window should appear when an invalid value is entered
  186. * in the cell, <code>false</code> otherwise
  187. */
  188. public boolean getShowErrorOnInvalidValue() {
  189. return (opt_show_error_on_invalid_value.isSet(_option_flags));
  190. }
  191. /**
  192. * get the condition operator
  193. * @return the condition operator
  194. * @see HSSFDataValidation utility class
  195. */
  196. public int getConditionOperator() {
  197. return opt_condition_operator.getValue(_option_flags);
  198. }
  199. // <-- end option flags
  200. public String getPromptTitle() {
  201. return resolveTitleString(_promptTitle);
  202. }
  203. public String getErrorTitle() {
  204. return resolveTitleString(_errorTitle);
  205. }
  206. public String getPromptText() {
  207. return resolveTitleString(_promptText);
  208. }
  209. public String getErrorText() {
  210. return resolveTitleString(_errorText);
  211. }
  212. public Ptg[] getFormula1() {
  213. return Formula.getTokens(_formula1);
  214. }
  215. public Ptg[] getFormula2() {
  216. return Formula.getTokens(_formula2);
  217. }
  218. public CellRangeAddressList getCellRangeAddress() {
  219. return this._regions;
  220. }
  221. public void serialize(LittleEndianOutput out) {
  222. out.writeInt(_option_flags);
  223. serializeUnicodeString(_promptTitle, out);
  224. serializeUnicodeString(_errorTitle, out);
  225. serializeUnicodeString(_promptText, out);
  226. serializeUnicodeString(_errorText, out);
  227. out.writeShort(_formula1.getEncodedTokenSize());
  228. out.writeShort(_not_used_1);
  229. _formula1.serializeTokens(out);
  230. out.writeShort(_formula2.getEncodedTokenSize());
  231. out.writeShort(_not_used_2);
  232. _formula2.serializeTokens(out);
  233. _regions.serialize(out);
  234. }
  235. /**
  236. * When entered via the UI, Excel translates empty string into "\0"
  237. * While it is possible to encode the title/text as empty string (Excel doesn't exactly crash),
  238. * the resulting tool-tip text / message box looks wrong. It is best to do the same as the
  239. * Excel UI and encode 'not present' as "\0".
  240. */
  241. private static UnicodeString resolveTitleText(String str) {
  242. if (str == null || str.length() < 1) {
  243. return NULL_TEXT_STRING;
  244. }
  245. return new UnicodeString(str);
  246. }
  247. private static String resolveTitleString(UnicodeString us) {
  248. if (us == null || us.equals(NULL_TEXT_STRING)) {
  249. return null;
  250. }
  251. return us.getString();
  252. }
  253. private static UnicodeString readUnicodeString(RecordInputStream in) {
  254. return new UnicodeString(in);
  255. }
  256. private static void serializeUnicodeString(UnicodeString us, LittleEndianOutput out) {
  257. StringUtil.writeUnicodeString(out, us.getString());
  258. }
  259. private static int getUnicodeStringSize(UnicodeString us) {
  260. String str = us.getString();
  261. return 3 + str.length() * (StringUtil.hasMultibyte(str) ? 2 : 1);
  262. }
  263. protected int getDataSize() {
  264. int size = 4+2+2+2+2;//options_field+first_formula_size+first_unused+sec_formula_size+sec+unused;
  265. size += getUnicodeStringSize(_promptTitle);
  266. size += getUnicodeStringSize(_errorTitle);
  267. size += getUnicodeStringSize(_promptText);
  268. size += getUnicodeStringSize(_errorText);
  269. size += _formula1.getEncodedTokenSize();
  270. size += _formula2.getEncodedTokenSize();
  271. size += _regions.getSize();
  272. return size;
  273. }
  274. public short getSid() {
  275. return sid;
  276. }
  277. /** Clones the object. */
  278. @Override
  279. public DVRecord copy() {
  280. return new DVRecord(this);
  281. }
  282. @Override
  283. public HSSFRecordTypes getGenericRecordType() {
  284. return HSSFRecordTypes.DV;
  285. }
  286. @Override
  287. public Map<String, Supplier<?>> getGenericProperties() {
  288. return GenericRecordUtil.getGenericProperties(
  289. "optionFlags", getBitsAsString(() -> _option_flags, FLAG_MASKS, FLAG_NAMES),
  290. "promptTitle", this::getPromptTitle,
  291. "errorTitle", this::getErrorTitle,
  292. "promptText", this::getPromptText,
  293. "errorText", this::getErrorText,
  294. "formula1", this::getFormula1,
  295. "formula2", this::getFormula2,
  296. "regions", () -> _regions
  297. );
  298. }
  299. }