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.

CellFormat.java 18KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481
  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.format;
  16. import java.util.ArrayList;
  17. import java.util.Date;
  18. import java.util.List;
  19. import java.util.Locale;
  20. import java.util.Map;
  21. import java.util.WeakHashMap;
  22. import java.util.regex.Matcher;
  23. import java.util.regex.Pattern;
  24. import javax.swing.JLabel;
  25. import org.apache.logging.log4j.Level;
  26. import org.apache.logging.log4j.LogManager;
  27. import org.apache.logging.log4j.Logger;
  28. import org.apache.poi.ss.usermodel.Cell;
  29. import org.apache.poi.ss.usermodel.CellType;
  30. import org.apache.poi.ss.usermodel.ConditionalFormatting;
  31. import org.apache.poi.ss.usermodel.ConditionalFormattingRule;
  32. import org.apache.poi.ss.usermodel.DataFormatter;
  33. import org.apache.poi.ss.usermodel.DateUtil;
  34. import org.apache.poi.ss.util.DateFormatConverter;
  35. import org.apache.poi.util.LocaleUtil;
  36. /**
  37. * Format a value according to the standard Excel behavior. This "standard" is
  38. * not explicitly documented by Microsoft, so the behavior is determined by
  39. * experimentation; see the tests.
  40. * <p>
  41. * An Excel format has up to four parts, separated by semicolons. Each part
  42. * specifies what to do with particular kinds of values, depending on the number
  43. * of parts given:
  44. * <dl>
  45. * <dt>One part (example: {@code [Green]#.##})</dt>
  46. * <dd>If the value is a number, display according to this one part (example: green text,
  47. * with up to two decimal points). If the value is text, display it as is.</dd>
  48. *
  49. * <dt>Two parts (example: {@code [Green]#.##;[Red]#.##})</dt>
  50. * <dd>If the value is a positive number or zero, display according to the first part (example: green
  51. * text, with up to two decimal points); if it is a negative number, display
  52. * according to the second part (example: red text, with up to two decimal
  53. * points). If the value is text, display it as is.</dd>
  54. *
  55. * <dt>Three parts (example: {@code [Green]#.##;[Black]#.##;[Red]#.##})</dt>
  56. * <dd>If the value is a positive
  57. * number, display according to the first part (example: green text, with up to
  58. * two decimal points); if it is zero, display according to the second part
  59. * (example: black text, with up to two decimal points); if it is a negative
  60. * number, display according to the third part (example: red text, with up to
  61. * two decimal points). If the value is text, display it as is.</dd>
  62. *
  63. * <dt>Four parts (example: {@code [Green]#.##;[Black]#.##;[Red]#.##;[@]})</dt>
  64. * <dd>If the value is a positive number, display according to the first part (example: green text,
  65. * with up to two decimal points); if it is zero, display according to the
  66. * second part (example: black text, with up to two decimal points); if it is a
  67. * negative number, display according to the third part (example: red text, with
  68. * up to two decimal points). If the value is text, display according to the
  69. * fourth part (example: text in the cell's usual color, with the text value
  70. * surround by brackets).</dd>
  71. * </dl>
  72. * <p>
  73. * A given format part may specify a given Locale, by including something
  74. * like {@code [$$-409]} or {@code [$&pound;-809]} or {@code [$-40C]}. These
  75. * are (currently) largely ignored. You can use {@link DateFormatConverter}
  76. * to look these up into Java Locales if desired.
  77. * <p>
  78. * In addition to these, there is a general format that is used when no format
  79. * is specified.
  80. *
  81. * TODO Merge this with {@link DataFormatter} so we only have one set of
  82. * code for formatting numbers.
  83. * TODO Re-use parts of this logic with {@link ConditionalFormatting} /
  84. * {@link ConditionalFormattingRule} for reporting stylings which do/don't apply
  85. * TODO Support the full set of modifiers, including alternate calendars and
  86. * native character numbers, as documented at https://help.libreoffice.org/Common/Number_Format_Codes
  87. */
  88. public class CellFormat {
  89. /** The logger to use in the formatting code. */
  90. private static final Logger LOG = LogManager.getLogger(CellFormat.class);
  91. private static final Pattern ONE_PART = Pattern.compile(
  92. CellFormatPart.FORMAT_PAT.pattern() + "(;|$)",
  93. Pattern.COMMENTS | Pattern.CASE_INSENSITIVE);
  94. /*
  95. * Cells that cannot be formatted, e.g. cells that have a date or time
  96. * format and have an invalid date or time value, are displayed as 255
  97. * pound signs ("#").
  98. */
  99. private static final String INVALID_VALUE_FOR_FORMAT =
  100. "###################################################" +
  101. "###################################################" +
  102. "###################################################" +
  103. "###################################################" +
  104. "###################################################";
  105. private static final String QUOTE = "\"";
  106. private final Locale locale;
  107. private final String format;
  108. private final CellFormatPart posNumFmt;
  109. private final CellFormatPart zeroNumFmt;
  110. private final CellFormatPart negNumFmt;
  111. private final CellFormatPart textFmt;
  112. private final int formatPartCount;
  113. private static CellFormat createGeneralFormat(final Locale locale) {
  114. return new CellFormat(locale, "General") {
  115. @Override
  116. public CellFormatResult apply(Object value) {
  117. String text = (new CellGeneralFormatter(locale)).format(value);
  118. return new CellFormatResult(true, text, null);
  119. }
  120. };
  121. }
  122. /** Maps a format string to its parsed version for efficiencies sake. */
  123. private static final Map<Locale, Map<String, CellFormat>> formatCache =
  124. new WeakHashMap<>();
  125. /**
  126. * Returns a CellFormat that applies the given format. Two calls
  127. * with the same format may or may not return the same object.
  128. *
  129. * @param format The format.
  130. *
  131. * @return A CellFormat that applies the given format.
  132. */
  133. public static CellFormat getInstance(String format) {
  134. return getInstance(LocaleUtil.getUserLocale(), format);
  135. }
  136. /**
  137. * Returns a CellFormat that applies the given format. Two calls
  138. * with the same format may or may not return the same object.
  139. *
  140. * @param locale The locale.
  141. * @param format The format.
  142. *
  143. * @return A CellFormat that applies the given format.
  144. */
  145. public static synchronized CellFormat getInstance(Locale locale, String format) {
  146. Map<String, CellFormat> formatMap = formatCache.computeIfAbsent(locale, k -> new WeakHashMap<>());
  147. CellFormat fmt = formatMap.get(format);
  148. if (fmt == null) {
  149. if (format.equals("General") || format.equals("@"))
  150. fmt = createGeneralFormat(locale);
  151. else
  152. fmt = new CellFormat(locale, format);
  153. formatMap.put(format, fmt);
  154. }
  155. return fmt;
  156. }
  157. /**
  158. * Creates a new object.
  159. *
  160. * @param format The format.
  161. */
  162. private CellFormat(Locale locale, String format) {
  163. this.locale = locale;
  164. this.format = format;
  165. CellFormatPart defaultTextFormat = new CellFormatPart(locale, "@");
  166. Matcher m = ONE_PART.matcher(format);
  167. List<CellFormatPart> parts = new ArrayList<>();
  168. while (m.find()) {
  169. try {
  170. String valueDesc = m.group();
  171. // Strip out the semicolon if it's there
  172. if (valueDesc.endsWith(";"))
  173. valueDesc = valueDesc.substring(0, valueDesc.length() - 1);
  174. parts.add(new CellFormatPart(locale, valueDesc));
  175. } catch (RuntimeException e) {
  176. LOG.warn("Invalid format: {}", CellFormatter.quote(m.group()), e);
  177. parts.add(null);
  178. }
  179. }
  180. formatPartCount = parts.size();
  181. switch (formatPartCount) {
  182. case 1:
  183. posNumFmt = parts.get(0);
  184. negNumFmt = null;
  185. zeroNumFmt = null;
  186. textFmt = defaultTextFormat;
  187. break;
  188. case 2:
  189. posNumFmt = parts.get(0);
  190. negNumFmt = parts.get(1);
  191. zeroNumFmt = null;
  192. textFmt = defaultTextFormat;
  193. break;
  194. case 3:
  195. posNumFmt = parts.get(0);
  196. negNumFmt = parts.get(1);
  197. zeroNumFmt = parts.get(2);
  198. textFmt = defaultTextFormat;
  199. break;
  200. case 4:
  201. default:
  202. posNumFmt = parts.get(0);
  203. negNumFmt = parts.get(1);
  204. zeroNumFmt = parts.get(2);
  205. textFmt = parts.get(3);
  206. break;
  207. }
  208. }
  209. /**
  210. * Returns the result of applying the format to the given value. If the
  211. * value is a number (a type of {@link Number} object), the correct number
  212. * format type is chosen; otherwise it is considered a text object.
  213. *
  214. * @param value The value
  215. *
  216. * @return The result, in a {@link CellFormatResult}.
  217. */
  218. public CellFormatResult apply(Object value) {
  219. if (value instanceof Number) {
  220. Number num = (Number) value;
  221. double val = num.doubleValue();
  222. if (val < 0 &&
  223. ((formatPartCount == 2
  224. && !posNumFmt.hasCondition() && !negNumFmt.hasCondition())
  225. || (formatPartCount == 3 && !negNumFmt.hasCondition())
  226. || (formatPartCount == 4 && !negNumFmt.hasCondition()))) {
  227. // The negative number format has the negative formatting required,
  228. // e.g. minus sign or brackets, so pass a positive value so that
  229. // the default leading minus sign is not also output
  230. return negNumFmt.apply(-val);
  231. } else {
  232. return getApplicableFormatPart(val).apply(val);
  233. }
  234. } else if (value instanceof java.util.Date) {
  235. // Don't know (and can't get) the workbook date windowing (1900 or 1904)
  236. // so assume 1900 date windowing
  237. double numericValue = DateUtil.getExcelDate((Date) value);
  238. if (DateUtil.isValidExcelDate(numericValue)) {
  239. return getApplicableFormatPart(numericValue).apply(value);
  240. } else {
  241. throw new IllegalArgumentException("value " + numericValue + " of date " + value + " is not a valid Excel date");
  242. }
  243. } else {
  244. return textFmt.apply(value);
  245. }
  246. }
  247. /**
  248. * Returns the result of applying the format to the given date.
  249. *
  250. * @param date The date.
  251. * @param numericValue The numeric value for the date.
  252. *
  253. * @return The result, in a {@link CellFormatResult}.
  254. */
  255. private CellFormatResult apply(Date date, double numericValue) {
  256. return getApplicableFormatPart(numericValue).apply(date);
  257. }
  258. /**
  259. * Fetches the appropriate value from the cell, and returns the result of
  260. * applying it to the appropriate format. For formula cells, the computed
  261. * value is what is used.
  262. *
  263. * @param c The cell.
  264. *
  265. * @return The result, in a {@link CellFormatResult}.
  266. */
  267. public CellFormatResult apply(Cell c) {
  268. switch (ultimateType(c)) {
  269. case BLANK:
  270. return apply("");
  271. case BOOLEAN:
  272. return apply(c.getBooleanCellValue());
  273. case NUMERIC:
  274. double value = c.getNumericCellValue();
  275. if (getApplicableFormatPart(value).getCellFormatType() == CellFormatType.DATE) {
  276. if (DateUtil.isValidExcelDate(value)) {
  277. return apply(c.getDateCellValue(), value);
  278. } else {
  279. return apply(INVALID_VALUE_FOR_FORMAT);
  280. }
  281. } else {
  282. return apply(value);
  283. }
  284. case STRING:
  285. return apply(c.getStringCellValue());
  286. default:
  287. return apply("?");
  288. }
  289. }
  290. /**
  291. * Uses the result of applying this format to the value, setting the text
  292. * and color of a label before returning the result.
  293. *
  294. * @param label The label to apply to.
  295. * @param value The value to process.
  296. *
  297. * @return The result, in a {@link CellFormatResult}.
  298. */
  299. public CellFormatResult apply(JLabel label, Object value) {
  300. CellFormatResult result = apply(value);
  301. label.setText(result.text);
  302. if (result.textColor != null) {
  303. label.setForeground(result.textColor);
  304. }
  305. return result;
  306. }
  307. /**
  308. * Uses the result of applying this format to the given date, setting the text
  309. * and color of a label before returning the result.
  310. *
  311. * @param label The label to apply to.
  312. * @param date The date.
  313. * @param numericValue The numeric value for the date.
  314. *
  315. * @return The result, in a {@link CellFormatResult}.
  316. */
  317. private CellFormatResult apply(JLabel label, Date date, double numericValue) {
  318. CellFormatResult result = apply(date, numericValue);
  319. label.setText(result.text);
  320. if (result.textColor != null) {
  321. label.setForeground(result.textColor);
  322. }
  323. return result;
  324. }
  325. /**
  326. * Fetches the appropriate value from the cell, and uses the result, setting
  327. * the text and color of a label before returning the result.
  328. *
  329. * @param label The label to apply to.
  330. * @param c The cell.
  331. *
  332. * @return The result, in a {@link CellFormatResult}.
  333. */
  334. public CellFormatResult apply(JLabel label, Cell c) {
  335. switch (ultimateType(c)) {
  336. case BLANK:
  337. return apply(label, "");
  338. case BOOLEAN:
  339. return apply(label, c.getBooleanCellValue());
  340. case NUMERIC:
  341. double value = c.getNumericCellValue();
  342. if (getApplicableFormatPart(value).getCellFormatType() == CellFormatType.DATE) {
  343. if (DateUtil.isValidExcelDate(value)) {
  344. return apply(label, c.getDateCellValue(), value);
  345. } else {
  346. return apply(label, INVALID_VALUE_FOR_FORMAT);
  347. }
  348. } else {
  349. return apply(label, value);
  350. }
  351. case STRING:
  352. return apply(label, c.getStringCellValue());
  353. default:
  354. return apply(label, "?");
  355. }
  356. }
  357. /**
  358. * Returns the {@link CellFormatPart} that applies to the value. Result
  359. * depends on how many parts the cell format has, the cell value and any
  360. * conditions. The value must be a {@link Number}.
  361. *
  362. * @param value The value.
  363. * @return The {@link CellFormatPart} that applies to the value.
  364. */
  365. private CellFormatPart getApplicableFormatPart(Object value) {
  366. if (value instanceof Number) {
  367. double val = ((Number) value).doubleValue();
  368. if (formatPartCount == 1) {
  369. if (!posNumFmt.hasCondition()
  370. || (posNumFmt.hasCondition() && posNumFmt.applies(val))) {
  371. return posNumFmt;
  372. } else {
  373. return new CellFormatPart(locale, "General");
  374. }
  375. } else if (formatPartCount == 2) {
  376. if ((!posNumFmt.hasCondition() && val >= 0)
  377. || (posNumFmt.hasCondition() && posNumFmt.applies(val))) {
  378. return posNumFmt;
  379. } else if (!negNumFmt.hasCondition()
  380. || (negNumFmt.hasCondition() && negNumFmt.applies(val))) {
  381. return negNumFmt;
  382. } else {
  383. // Return ###...### (255 #s) to match Excel 2007 behaviour
  384. return new CellFormatPart(QUOTE + INVALID_VALUE_FOR_FORMAT + QUOTE);
  385. }
  386. } else {
  387. if ((!posNumFmt.hasCondition() && val > 0)
  388. || (posNumFmt.hasCondition() && posNumFmt.applies(val))) {
  389. return posNumFmt;
  390. } else if ((!negNumFmt.hasCondition() && val < 0)
  391. || (negNumFmt.hasCondition() && negNumFmt.applies(val))) {
  392. return negNumFmt;
  393. // Only the first two format parts can have conditions
  394. } else {
  395. return zeroNumFmt;
  396. }
  397. }
  398. } else {
  399. throw new IllegalArgumentException("value must be a Number");
  400. }
  401. }
  402. /**
  403. * Returns the ultimate cell type, following the results of formulas. If
  404. * the cell is a {@link CellType#FORMULA}, this returns the result of
  405. * {@link Cell#getCachedFormulaResultType()}. Otherwise this returns the
  406. * result of {@link Cell#getCellType()}.
  407. *
  408. * @param cell The cell.
  409. *
  410. * @return The ultimate type of this cell.
  411. */
  412. public static CellType ultimateType(Cell cell) {
  413. CellType type = cell.getCellType();
  414. if (type == CellType.FORMULA)
  415. return cell.getCachedFormulaResultType();
  416. else
  417. return type;
  418. }
  419. /**
  420. * Returns {@code true} if the other object is a CellFormat object
  421. * with the same format.
  422. *
  423. * @param obj The other object.
  424. *
  425. * @return {@code true} if the two objects are equal.
  426. */
  427. @Override
  428. public boolean equals(Object obj) {
  429. if (this == obj)
  430. return true;
  431. if (obj instanceof CellFormat) {
  432. CellFormat that = (CellFormat) obj;
  433. return format.equals(that.format);
  434. }
  435. return false;
  436. }
  437. /**
  438. * Returns a hash code for the format.
  439. *
  440. * @return A hash code for the format.
  441. */
  442. @Override
  443. public int hashCode() {
  444. return format.hashCode();
  445. }
  446. }