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.

CFRule12Record.java 18KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488
  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 java.util.Collections;
  17. import java.util.LinkedHashMap;
  18. import java.util.Map;
  19. import java.util.function.Supplier;
  20. import org.apache.poi.hssf.record.cf.ColorGradientFormatting;
  21. import org.apache.poi.hssf.record.cf.ColorGradientThreshold;
  22. import org.apache.poi.hssf.record.cf.DataBarFormatting;
  23. import org.apache.poi.hssf.record.cf.DataBarThreshold;
  24. import org.apache.poi.hssf.record.cf.IconMultiStateFormatting;
  25. import org.apache.poi.hssf.record.cf.IconMultiStateThreshold;
  26. import org.apache.poi.hssf.record.cf.Threshold;
  27. import org.apache.poi.hssf.record.common.ExtendedColor;
  28. import org.apache.poi.hssf.record.common.FtrHeader;
  29. import org.apache.poi.hssf.record.common.FutureRecord;
  30. import org.apache.poi.hssf.usermodel.HSSFSheet;
  31. import org.apache.poi.ss.formula.Formula;
  32. import org.apache.poi.ss.formula.ptg.Ptg;
  33. import org.apache.poi.ss.usermodel.ConditionalFormattingThreshold.RangeType;
  34. import org.apache.poi.ss.usermodel.IconMultiStateFormatting.IconSet;
  35. import org.apache.poi.ss.util.CellRangeAddress;
  36. import org.apache.poi.util.IOUtils;
  37. import org.apache.poi.util.LittleEndianOutput;
  38. import org.apache.poi.util.POILogger;
  39. /**
  40. * Conditional Formatting v12 Rule Record (0x087A).
  41. *
  42. * <p>This is for newer-style Excel conditional formattings,
  43. * from Excel 2007 onwards.
  44. *
  45. * <p>{@link CFRuleRecord} is used where the condition type is
  46. * {@link #CONDITION_TYPE_CELL_VALUE_IS} or {@link #CONDITION_TYPE_FORMULA},
  47. * this is only used for the other types
  48. */
  49. public final class CFRule12Record extends CFRuleBase implements FutureRecord {
  50. //arbitrarily selected; may need to increase
  51. private static final int MAX_RECORD_LENGTH = 100_000;
  52. public static final short sid = 0x087A;
  53. private FtrHeader futureHeader;
  54. private int ext_formatting_length;
  55. private byte[] ext_formatting_data;
  56. private Formula formula_scale;
  57. private byte ext_opts;
  58. private int priority;
  59. private int template_type;
  60. private byte template_param_length;
  61. private byte[] template_params;
  62. private DataBarFormatting data_bar;
  63. private IconMultiStateFormatting multistate;
  64. private ColorGradientFormatting color_gradient;
  65. // TODO Parse this, see #58150
  66. private byte[] filter_data;
  67. public CFRule12Record(CFRule12Record other) {
  68. super(other);
  69. futureHeader = (other.futureHeader == null) ? null : other.futureHeader.copy();
  70. // use min() to gracefully handle cases where the length-property and the array-length do not match
  71. // we saw some such files in circulation
  72. ext_formatting_length = Math.min(other.ext_formatting_length, other.ext_formatting_data.length);
  73. ext_formatting_data = other.ext_formatting_data.clone();
  74. formula_scale = other.formula_scale.copy();
  75. ext_opts = other.ext_opts;
  76. priority = other.priority;
  77. template_type = other.template_type;
  78. template_param_length = other.template_param_length;
  79. template_params = (other.template_params == null) ? null : other.template_params.clone();
  80. color_gradient = (other.color_gradient == null) ? null : other.color_gradient.copy();
  81. multistate = (other.multistate == null) ? null : other.multistate.copy();
  82. data_bar = (other.data_bar == null) ? null : other.data_bar.copy();
  83. filter_data = (other.filter_data == null) ? null : other.filter_data.clone();
  84. }
  85. private CFRule12Record(byte conditionType, byte comparisonOperation) {
  86. super(conditionType, comparisonOperation);
  87. setDefaults();
  88. }
  89. private CFRule12Record(byte conditionType, byte comparisonOperation, Ptg[] formula1, Ptg[] formula2, Ptg[] formulaScale) {
  90. super(conditionType, comparisonOperation, formula1, formula2);
  91. setDefaults();
  92. this.formula_scale = Formula.create(formulaScale);
  93. }
  94. private void setDefaults() {
  95. futureHeader = new FtrHeader();
  96. futureHeader.setRecordType(sid);
  97. ext_formatting_length = 0;
  98. ext_formatting_data = new byte[4];
  99. formula_scale = Formula.create(Ptg.EMPTY_PTG_ARRAY);
  100. ext_opts = 0;
  101. priority = 0;
  102. template_type = getConditionType();
  103. template_param_length = 16;
  104. template_params = IOUtils.safelyAllocate(template_param_length, MAX_RECORD_LENGTH);
  105. }
  106. /**
  107. * Creates a new comparison operation rule
  108. *
  109. * @param sheet the sheet
  110. * @param formulaText the first formula text
  111. *
  112. * @return a new comparison operation rule
  113. */
  114. public static CFRule12Record create(HSSFSheet sheet, String formulaText) {
  115. Ptg[] formula1 = parseFormula(formulaText, sheet);
  116. return new CFRule12Record(CONDITION_TYPE_FORMULA, ComparisonOperator.NO_COMPARISON,
  117. formula1, null, null);
  118. }
  119. /**
  120. * Creates a new comparison operation rule
  121. *
  122. * @param sheet the sheet
  123. * @param comparisonOperation the comparison operation
  124. * @param formulaText1 the first formula text
  125. * @param formulaText2 the second formula text
  126. *
  127. * @return a new comparison operation rule
  128. */
  129. public static CFRule12Record create(HSSFSheet sheet, byte comparisonOperation,
  130. String formulaText1, String formulaText2) {
  131. Ptg[] formula1 = parseFormula(formulaText1, sheet);
  132. Ptg[] formula2 = parseFormula(formulaText2, sheet);
  133. return new CFRule12Record(CONDITION_TYPE_CELL_VALUE_IS, comparisonOperation,
  134. formula1, formula2, null);
  135. }
  136. /**
  137. * Creates a new comparison operation rule
  138. *
  139. * @param sheet the sheet
  140. * @param comparisonOperation the comparison operation
  141. * @param formulaText1 the first formula text
  142. * @param formulaText2 the second formula text
  143. * @param formulaTextScale the scale to apply for the comparison
  144. *
  145. * @return a new comparison operation rule
  146. */
  147. public static CFRule12Record create(HSSFSheet sheet, byte comparisonOperation,
  148. String formulaText1, String formulaText2, String formulaTextScale) {
  149. Ptg[] formula1 = parseFormula(formulaText1, sheet);
  150. Ptg[] formula2 = parseFormula(formulaText2, sheet);
  151. Ptg[] formula3 = parseFormula(formulaTextScale, sheet);
  152. return new CFRule12Record(CONDITION_TYPE_CELL_VALUE_IS, comparisonOperation,
  153. formula1, formula2, formula3);
  154. }
  155. /**
  156. * Creates a new Data Bar formatting
  157. *
  158. * @param sheet the sheet
  159. * @param color the data bar color
  160. *
  161. * @return a new Data Bar formatting
  162. */
  163. public static CFRule12Record create(HSSFSheet sheet, ExtendedColor color) {
  164. CFRule12Record r = new CFRule12Record(CONDITION_TYPE_DATA_BAR,
  165. ComparisonOperator.NO_COMPARISON);
  166. DataBarFormatting dbf = r.createDataBarFormatting();
  167. dbf.setColor(color);
  168. dbf.setPercentMin((byte)0);
  169. dbf.setPercentMax((byte)100);
  170. DataBarThreshold min = new DataBarThreshold();
  171. min.setType(RangeType.MIN.id);
  172. dbf.setThresholdMin(min);
  173. DataBarThreshold max = new DataBarThreshold();
  174. max.setType(RangeType.MAX.id);
  175. dbf.setThresholdMax(max);
  176. return r;
  177. }
  178. /**
  179. * Creates a new Icon Set / Multi-State formatting
  180. *
  181. * @param sheet the sheet
  182. * @param iconSet the icon set
  183. *
  184. * @return a new Icon Set / Multi-State formatting
  185. */
  186. public static CFRule12Record create(HSSFSheet sheet, IconSet iconSet) {
  187. Threshold[] ts = new Threshold[iconSet.num];
  188. for (int i=0; i<ts.length; i++) {
  189. ts[i] = new IconMultiStateThreshold();
  190. }
  191. CFRule12Record r = new CFRule12Record(CONDITION_TYPE_ICON_SET,
  192. ComparisonOperator.NO_COMPARISON);
  193. IconMultiStateFormatting imf = r.createMultiStateFormatting();
  194. imf.setIconSet(iconSet);
  195. imf.setThresholds(ts);
  196. return r;
  197. }
  198. /**
  199. * Creates a new Color Scale / Color Gradient formatting
  200. *
  201. * @param sheet the sheet
  202. *
  203. * @return a new Color Scale / Color Gradient formatting
  204. */
  205. public static CFRule12Record createColorScale(HSSFSheet sheet) {
  206. int numPoints = 3;
  207. ExtendedColor[] colors = new ExtendedColor[numPoints];
  208. ColorGradientThreshold[] ts = new ColorGradientThreshold[numPoints];
  209. for (int i=0; i<ts.length; i++) {
  210. ts[i] = new ColorGradientThreshold();
  211. colors[i] = new ExtendedColor();
  212. }
  213. CFRule12Record r = new CFRule12Record(CONDITION_TYPE_COLOR_SCALE,
  214. ComparisonOperator.NO_COMPARISON);
  215. ColorGradientFormatting cgf = r.createColorGradientFormatting();
  216. cgf.setNumControlPoints(numPoints);
  217. cgf.setThresholds(ts);
  218. cgf.setColors(colors);
  219. return r;
  220. }
  221. public CFRule12Record(RecordInputStream in) {
  222. futureHeader = new FtrHeader(in);
  223. setConditionType(in.readByte());
  224. setComparisonOperation(in.readByte());
  225. int field_3_formula1_len = in.readUShort();
  226. int field_4_formula2_len = in.readUShort();
  227. ext_formatting_length = in.readInt();
  228. ext_formatting_data = new byte[0];
  229. if (ext_formatting_length == 0) {
  230. // 2 bytes reserved
  231. in.readUShort();
  232. } else {
  233. long len = readFormatOptions(in);
  234. if (len < ext_formatting_length) {
  235. ext_formatting_data = IOUtils.safelyAllocate(ext_formatting_length-len, MAX_RECORD_LENGTH);
  236. in.readFully(ext_formatting_data);
  237. }
  238. }
  239. setFormula1(Formula.read(field_3_formula1_len, in));
  240. setFormula2(Formula.read(field_4_formula2_len, in));
  241. int formula_scale_len = in.readUShort();
  242. formula_scale = Formula.read(formula_scale_len, in);
  243. ext_opts = in.readByte();
  244. priority = in.readUShort();
  245. template_type = in.readUShort();
  246. template_param_length = in.readByte();
  247. if (template_param_length == 0 || template_param_length == 16) {
  248. template_params = IOUtils.safelyAllocate(template_param_length, MAX_RECORD_LENGTH);
  249. in.readFully(template_params);
  250. } else {
  251. LOG.log(POILogger.WARN, "CF Rule v12 template params length should be 0 or 16, found " + template_param_length);
  252. in.readRemainder();
  253. }
  254. byte type = getConditionType();
  255. if (type == CONDITION_TYPE_COLOR_SCALE) {
  256. color_gradient = new ColorGradientFormatting(in);
  257. } else if (type == CONDITION_TYPE_DATA_BAR) {
  258. data_bar = new DataBarFormatting(in);
  259. } else if (type == CONDITION_TYPE_FILTER) {
  260. filter_data = in.readRemainder();
  261. } else if (type == CONDITION_TYPE_ICON_SET) {
  262. multistate = new IconMultiStateFormatting(in);
  263. }
  264. }
  265. public boolean containsDataBarBlock() {
  266. return (data_bar != null);
  267. }
  268. public DataBarFormatting getDataBarFormatting() {
  269. return data_bar;
  270. }
  271. public DataBarFormatting createDataBarFormatting() {
  272. if (data_bar != null) return data_bar;
  273. // Convert, setup and return
  274. setConditionType(CONDITION_TYPE_DATA_BAR);
  275. data_bar = new DataBarFormatting();
  276. return data_bar;
  277. }
  278. public boolean containsMultiStateBlock() {
  279. return (multistate != null);
  280. }
  281. public IconMultiStateFormatting getMultiStateFormatting() {
  282. return multistate;
  283. }
  284. public IconMultiStateFormatting createMultiStateFormatting() {
  285. if (multistate != null) return multistate;
  286. // Convert, setup and return
  287. setConditionType(CONDITION_TYPE_ICON_SET);
  288. multistate = new IconMultiStateFormatting();
  289. return multistate;
  290. }
  291. public boolean containsColorGradientBlock() {
  292. return (color_gradient != null);
  293. }
  294. public ColorGradientFormatting getColorGradientFormatting() {
  295. return color_gradient;
  296. }
  297. public ColorGradientFormatting createColorGradientFormatting() {
  298. if (color_gradient != null) return color_gradient;
  299. // Convert, setup and return
  300. setConditionType(CONDITION_TYPE_COLOR_SCALE);
  301. color_gradient = new ColorGradientFormatting();
  302. return color_gradient;
  303. }
  304. /**
  305. * get the stack of the scale expression as a list
  306. *
  307. * @return list of tokens (casts stack to a list and returns it!)
  308. * this method can return null is we are unable to create Ptgs from
  309. * existing excel file
  310. * callers should check for null!
  311. */
  312. public Ptg[] getParsedExpressionScale() {
  313. return formula_scale.getTokens();
  314. }
  315. public void setParsedExpressionScale(Ptg[] ptgs) {
  316. formula_scale = Formula.create(ptgs);
  317. }
  318. public int getPriority() {
  319. return priority;
  320. }
  321. public void setPriority(int priority) {
  322. this.priority = priority;
  323. }
  324. public short getSid() {
  325. return sid;
  326. }
  327. /**
  328. * called by the class that is responsible for writing this sucker.
  329. * Subclasses should implement this so that their data is passed back in a
  330. * byte array.
  331. *
  332. * @param out the stream to write to
  333. */
  334. public void serialize(LittleEndianOutput out) {
  335. futureHeader.serialize(out);
  336. int formula1Len=getFormulaSize(getFormula1());
  337. int formula2Len=getFormulaSize(getFormula2());
  338. out.writeByte(getConditionType());
  339. out.writeByte(getComparisonOperation());
  340. out.writeShort(formula1Len);
  341. out.writeShort(formula2Len);
  342. // TODO Update ext_formatting_length
  343. if (ext_formatting_length == 0) {
  344. out.writeInt(0);
  345. out.writeShort(0);
  346. } else {
  347. out.writeInt(ext_formatting_length);
  348. serializeFormattingBlock(out);
  349. out.write(ext_formatting_data);
  350. }
  351. getFormula1().serializeTokens(out);
  352. getFormula2().serializeTokens(out);
  353. out.writeShort(getFormulaSize(formula_scale));
  354. formula_scale.serializeTokens(out);
  355. out.writeByte(ext_opts);
  356. out.writeShort(priority);
  357. out.writeShort(template_type);
  358. out.writeByte(template_param_length);
  359. out.write(template_params);
  360. byte type = getConditionType();
  361. if (type == CONDITION_TYPE_COLOR_SCALE) {
  362. color_gradient.serialize(out);
  363. } else if (type == CONDITION_TYPE_DATA_BAR) {
  364. data_bar.serialize(out);
  365. } else if (type == CONDITION_TYPE_FILTER) {
  366. out.write(filter_data);
  367. } else if (type == CONDITION_TYPE_ICON_SET) {
  368. multistate.serialize(out);
  369. }
  370. }
  371. protected int getDataSize() {
  372. int len = FtrHeader.getDataSize() + 6;
  373. if (ext_formatting_length == 0) {
  374. len += 6;
  375. } else {
  376. len += 4 + getFormattingBlockSize() + ext_formatting_data.length;
  377. }
  378. len += getFormulaSize(getFormula1());
  379. len += getFormulaSize(getFormula2());
  380. len += 2 + getFormulaSize(formula_scale);
  381. len += 6 + template_params.length;
  382. byte type = getConditionType();
  383. if (type == CONDITION_TYPE_COLOR_SCALE) {
  384. len += color_gradient.getDataLength();
  385. } else if (type == CONDITION_TYPE_DATA_BAR) {
  386. len += data_bar.getDataLength();
  387. } else if (type == CONDITION_TYPE_FILTER) {
  388. len += filter_data.length;
  389. } else if (type == CONDITION_TYPE_ICON_SET) {
  390. len += multistate.getDataLength();
  391. }
  392. return len;
  393. }
  394. @Override
  395. public CFRule12Record copy() {
  396. return new CFRule12Record(this);
  397. }
  398. public short getFutureRecordType() {
  399. return futureHeader.getRecordType();
  400. }
  401. public FtrHeader getFutureHeader() {
  402. return futureHeader;
  403. }
  404. public CellRangeAddress getAssociatedRange() {
  405. return futureHeader.getAssociatedRange();
  406. }
  407. @Override
  408. public HSSFRecordTypes getGenericRecordType() {
  409. return HSSFRecordTypes.CF_RULE_12;
  410. }
  411. @Override
  412. public Map<String, Supplier<?>> getGenericProperties() {
  413. final Map<String, Supplier<?>> m = new LinkedHashMap<>(super.getGenericProperties());
  414. m.put("dxFn12Length", () -> ext_formatting_length);
  415. m.put("futureHeader", this::getFutureHeader);
  416. m.put("dxFn12Ext", () -> ext_formatting_data);
  417. m.put("formulaScale", this::getParsedExpressionScale);
  418. m.put("extOptions", () -> ext_opts);
  419. m.put("priority", this::getPriority);
  420. m.put("templateType", () -> template_type);
  421. m.put("templateParams", () -> template_params);
  422. m.put("filterData", () -> filter_data);
  423. m.put("dataBar", this::getDataBarFormatting);
  424. m.put("multiState", this::getMultiStateFormatting);
  425. m.put("colorGradient", this::getColorGradientFormatting);
  426. return Collections.unmodifiableMap(m);
  427. }
  428. }