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.

CellNumberFormatter.java 33KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855
  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.text.DecimalFormat;
  17. import java.text.DecimalFormatSymbols;
  18. import java.text.FieldPosition;
  19. import java.text.NumberFormat;
  20. import java.util.ArrayList;
  21. import java.util.Collections;
  22. import java.util.Formatter;
  23. import java.util.IllegalFormatException;
  24. import java.util.Iterator;
  25. import java.util.List;
  26. import java.util.ListIterator;
  27. import java.util.Locale;
  28. import java.util.Set;
  29. import java.util.TreeSet;
  30. import com.zaxxer.sparsebits.SparseBitSet;
  31. import org.apache.logging.log4j.LogManager;
  32. import org.apache.logging.log4j.Logger;
  33. import org.apache.poi.util.LocaleUtil;
  34. /**
  35. * This class implements printing out a value using a number format.
  36. */
  37. public class CellNumberFormatter extends CellFormatter {
  38. private static final Logger LOG = LogManager.getLogger(CellNumberFormatter.class);
  39. private final String desc;
  40. private final String printfFmt;
  41. private final double scale;
  42. private final Special decimalPoint;
  43. private final Special slash;
  44. private final Special exponent;
  45. private final Special numerator;
  46. private final Special afterInteger;
  47. private final Special afterFractional;
  48. private final boolean showGroupingSeparator;
  49. private final List<Special> specials = new ArrayList<>();
  50. private final List<Special> integerSpecials = new ArrayList<>();
  51. private final List<Special> fractionalSpecials = new ArrayList<>();
  52. private final List<Special> numeratorSpecials = new ArrayList<>();
  53. private final List<Special> denominatorSpecials = new ArrayList<>();
  54. private final List<Special> exponentSpecials = new ArrayList<>();
  55. private final List<Special> exponentDigitSpecials = new ArrayList<>();
  56. private final int maxDenominator;
  57. private final String numeratorFmt;
  58. private final String denominatorFmt;
  59. private final boolean improperFraction;
  60. private final DecimalFormat decimalFmt;
  61. // The CellNumberFormatter.simpleValue() method uses the SIMPLE_NUMBER
  62. // CellFormatter defined here. The CellFormat.GENERAL_FORMAT CellFormat
  63. // no longer uses the SIMPLE_NUMBER CellFormatter.
  64. // Note that the simpleValue()/SIMPLE_NUMBER CellFormatter format
  65. // ("#" for integer values, and "#.#" for floating-point values) is
  66. // different from the 'General' format for numbers ("#" for integer
  67. // values and "#.#########" for floating-point values).
  68. private final CellFormatter SIMPLE_NUMBER = new GeneralNumberFormatter(locale);
  69. private static class GeneralNumberFormatter extends CellFormatter {
  70. private GeneralNumberFormatter(Locale locale) {
  71. super(locale, "General");
  72. }
  73. public void formatValue(StringBuffer toAppendTo, Object value) {
  74. if (value == null) {
  75. return;
  76. }
  77. CellFormatter cf;
  78. if (value instanceof Number) {
  79. Number num = (Number) value;
  80. cf = (num.doubleValue() % 1.0 == 0) ? new CellNumberFormatter(locale, "#") :
  81. new CellNumberFormatter(locale, "#.#");
  82. } else {
  83. cf = CellTextFormatter.SIMPLE_TEXT;
  84. }
  85. cf.formatValue(toAppendTo, value);
  86. }
  87. public void simpleValue(StringBuffer toAppendTo, Object value) {
  88. formatValue(toAppendTo, value);
  89. }
  90. }
  91. /**
  92. * This class is used to mark where the special characters in the format
  93. * are, as opposed to the other characters that are simply printed.
  94. */
  95. /* package */ static class Special {
  96. final char ch;
  97. int pos;
  98. Special(char ch, int pos) {
  99. this.ch = ch;
  100. this.pos = pos;
  101. }
  102. @Override
  103. public String toString() {
  104. return "'" + ch + "' @ " + pos;
  105. }
  106. }
  107. /**
  108. * Creates a new cell number formatter.
  109. *
  110. * @param format The format to parse.
  111. */
  112. public CellNumberFormatter(String format) {
  113. this(LocaleUtil.getUserLocale(), format);
  114. }
  115. /**
  116. * Creates a new cell number formatter.
  117. *
  118. * @param locale The locale to use.
  119. * @param format The format to parse.
  120. */
  121. public CellNumberFormatter(Locale locale, String format) {
  122. super(locale, format);
  123. CellNumberPartHandler ph = new CellNumberPartHandler();
  124. StringBuffer descBuf = CellFormatPart.parseFormat(format, CellFormatType.NUMBER, ph);
  125. exponent = ph.getExponent();
  126. specials.addAll(ph.getSpecials());
  127. improperFraction = ph.isImproperFraction();
  128. // These are inconsistent settings, so ditch 'em
  129. if ((ph.getDecimalPoint() != null || ph.getExponent() != null) && ph.getSlash() != null) {
  130. slash = null;
  131. numerator = null;
  132. } else {
  133. slash = ph.getSlash();
  134. numerator = ph.getNumerator();
  135. }
  136. final int precision = interpretPrecision(ph.getDecimalPoint(), specials);
  137. int fractionPartWidth = 0;
  138. if (ph.getDecimalPoint() != null) {
  139. fractionPartWidth = 1 + precision;
  140. if (precision == 0) {
  141. // This means the format has a ".", but that output should have no decimals after it.
  142. // We just stop treating it specially
  143. specials.remove(ph.getDecimalPoint());
  144. decimalPoint = null;
  145. } else {
  146. decimalPoint = ph.getDecimalPoint();
  147. }
  148. } else {
  149. decimalPoint = null;
  150. }
  151. if (decimalPoint != null) {
  152. afterInteger = decimalPoint;
  153. } else if (exponent != null) {
  154. afterInteger = exponent;
  155. } else if (numerator != null) {
  156. afterInteger = numerator;
  157. } else {
  158. afterInteger = null;
  159. }
  160. if (exponent != null) {
  161. afterFractional = exponent;
  162. } else if (numerator != null) {
  163. afterFractional = numerator;
  164. } else {
  165. afterFractional = null;
  166. }
  167. double[] scaleByRef = {ph.getScale()};
  168. showGroupingSeparator = interpretIntegerCommas(descBuf, specials, decimalPoint, integerEnd(), fractionalEnd(), scaleByRef);
  169. if (exponent == null) {
  170. scale = scaleByRef[0];
  171. } else {
  172. // in "e" formats,% and trailing commas have no scaling effect
  173. scale = 1;
  174. }
  175. if (precision != 0) {
  176. // TODO: if decimalPoint is null (-> index == -1), return the whole list?
  177. fractionalSpecials.addAll(specials.subList(specials.indexOf(decimalPoint) + 1, fractionalEnd()));
  178. }
  179. if (exponent != null) {
  180. int exponentPos = specials.indexOf(exponent);
  181. exponentSpecials.addAll(specialsFor(exponentPos, 2));
  182. exponentDigitSpecials.addAll(specialsFor(exponentPos + 2));
  183. }
  184. if (slash != null) {
  185. if (numerator != null) {
  186. numeratorSpecials.addAll(specialsFor(specials.indexOf(numerator)));
  187. }
  188. denominatorSpecials.addAll(specialsFor(specials.indexOf(slash) + 1));
  189. if (denominatorSpecials.isEmpty()) {
  190. // no denominator follows the slash, drop the fraction idea
  191. numeratorSpecials.clear();
  192. maxDenominator = 1;
  193. numeratorFmt = null;
  194. denominatorFmt = null;
  195. } else {
  196. maxDenominator = maxValue(denominatorSpecials);
  197. numeratorFmt = singleNumberFormat(numeratorSpecials);
  198. denominatorFmt = singleNumberFormat(denominatorSpecials);
  199. }
  200. } else {
  201. maxDenominator = 1;
  202. numeratorFmt = null;
  203. denominatorFmt = null;
  204. }
  205. integerSpecials.addAll(specials.subList(0, integerEnd()));
  206. if (exponent == null) {
  207. int integerPartWidth = calculateIntegerPartWidth();
  208. int totalWidth = integerPartWidth + fractionPartWidth;
  209. // need to handle empty width specially as %00.0f fails during formatting
  210. if(totalWidth == 0) {
  211. printfFmt = "";
  212. } else {
  213. printfFmt = "%0" + totalWidth + '.' + precision + "f";
  214. }
  215. decimalFmt = null;
  216. } else {
  217. StringBuffer fmtBuf = new StringBuffer();
  218. boolean first = true;
  219. if (integerSpecials.size() == 1) {
  220. // If we don't do this, we get ".6e5" instead of "6e4"
  221. fmtBuf.append("0");
  222. first = false;
  223. } else
  224. for (Special s : integerSpecials) {
  225. if (isDigitFmt(s)) {
  226. fmtBuf.append(first ? '#' : '0');
  227. first = false;
  228. }
  229. }
  230. if (fractionalSpecials.size() > 0) {
  231. fmtBuf.append('.');
  232. for (Special s : fractionalSpecials) {
  233. if (isDigitFmt(s)) {
  234. if (!first)
  235. fmtBuf.append('0');
  236. first = false;
  237. }
  238. }
  239. }
  240. fmtBuf.append('E');
  241. placeZeros(fmtBuf, exponentSpecials.subList(2, exponentSpecials.size()));
  242. decimalFmt = new DecimalFormat(fmtBuf.toString(), getDecimalFormatSymbols());
  243. printfFmt = null;
  244. }
  245. desc = descBuf.toString();
  246. }
  247. private DecimalFormatSymbols getDecimalFormatSymbols() {
  248. return DecimalFormatSymbols.getInstance(locale);
  249. }
  250. private static void placeZeros(StringBuffer sb, List<Special> specials) {
  251. for (Special s : specials) {
  252. if (isDigitFmt(s)) {
  253. sb.append('0');
  254. }
  255. }
  256. }
  257. private static CellNumberStringMod insertMod(Special special, CharSequence toAdd, int where) {
  258. return new CellNumberStringMod(special, toAdd, where);
  259. }
  260. private static CellNumberStringMod deleteMod(Special start, boolean startInclusive, Special end, boolean endInclusive) {
  261. return new CellNumberStringMod(start, startInclusive, end, endInclusive);
  262. }
  263. private static CellNumberStringMod replaceMod(Special start, boolean startInclusive, Special end, boolean endInclusive, char withChar) {
  264. return new CellNumberStringMod(start, startInclusive, end, endInclusive, withChar);
  265. }
  266. private static String singleNumberFormat(List<Special> numSpecials) {
  267. return "%0" + numSpecials.size() + "d";
  268. }
  269. private static int maxValue(List<Special> s) {
  270. return Math.toIntExact(Math.round(Math.pow(10, s.size()) - 1));
  271. }
  272. private List<Special> specialsFor(int pos, int takeFirst) {
  273. if (pos >= specials.size()) {
  274. return Collections.emptyList();
  275. }
  276. ListIterator<Special> it = specials.listIterator(pos + takeFirst);
  277. Special last = it.next();
  278. int end = pos + takeFirst;
  279. while (it.hasNext()) {
  280. Special s = it.next();
  281. if (!isDigitFmt(s) || s.pos - last.pos > 1)
  282. break;
  283. end++;
  284. last = s;
  285. }
  286. return specials.subList(pos, end + 1);
  287. }
  288. private List<Special> specialsFor(int pos) {
  289. return specialsFor(pos, 0);
  290. }
  291. private static boolean isDigitFmt(Special s) {
  292. return s.ch == '0' || s.ch == '?' || s.ch == '#';
  293. }
  294. private int calculateIntegerPartWidth() {
  295. int digitCount = 0;
  296. for (Special s : specials) {
  297. //!! Handle fractions: The previous set of digits before that is the numerator, so we should stop short of that
  298. if (s == afterInteger) {
  299. break;
  300. } else if (isDigitFmt(s)) {
  301. digitCount++;
  302. }
  303. }
  304. return digitCount;
  305. }
  306. private static int interpretPrecision(Special decimalPoint, List<Special> specials) {
  307. int idx = specials.indexOf(decimalPoint);
  308. int precision = 0;
  309. if (idx != -1) {
  310. // skip over the decimal point itself
  311. ListIterator<Special> it = specials.listIterator(idx+1);
  312. while (it.hasNext()) {
  313. Special s = it.next();
  314. if (!isDigitFmt(s)) {
  315. break;
  316. }
  317. precision++;
  318. }
  319. }
  320. return precision;
  321. }
  322. private static boolean interpretIntegerCommas
  323. (StringBuffer sb, List<Special> specials, Special decimalPoint, int integerEnd, int fractionalEnd, double[] scale) {
  324. // In the integer part, commas at the end are scaling commas; other commas mean to show thousand-grouping commas
  325. ListIterator<Special> it = specials.listIterator(integerEnd);
  326. boolean stillScaling = true;
  327. boolean integerCommas = false;
  328. while (it.hasPrevious()) {
  329. Special s = it.previous();
  330. if (s.ch != ',') {
  331. stillScaling = false;
  332. } else {
  333. if (stillScaling) {
  334. scale[0] /= 1000;
  335. } else {
  336. integerCommas = true;
  337. }
  338. }
  339. }
  340. if (decimalPoint != null) {
  341. it = specials.listIterator(fractionalEnd);
  342. while (it.hasPrevious()) {
  343. Special s = it.previous();
  344. if (s.ch != ',') {
  345. break;
  346. } else {
  347. scale[0] /= 1000;
  348. }
  349. }
  350. }
  351. // Now strip them out -- we only need their interpretation, not their presence
  352. it = specials.listIterator();
  353. int removed = 0;
  354. while (it.hasNext()) {
  355. Special s = it.next();
  356. s.pos -= removed;
  357. if (s.ch == ',') {
  358. removed++;
  359. it.remove();
  360. sb.deleteCharAt(s.pos);
  361. }
  362. }
  363. return integerCommas;
  364. }
  365. private int integerEnd() {
  366. return (afterInteger == null) ? specials.size() : specials.indexOf(afterInteger);
  367. }
  368. private int fractionalEnd() {
  369. return (afterFractional == null) ? specials.size() : specials.indexOf(afterFractional);
  370. }
  371. /** {@inheritDoc} */
  372. public void formatValue(StringBuffer toAppendTo, Object valueObject) {
  373. double value = ((Number) valueObject).doubleValue();
  374. value *= scale;
  375. // For negative numbers:
  376. // - If the cell format has a negative number format, this method
  377. // is called with a positive value and the number format has
  378. // the negative formatting required, e.g. minus sign or brackets.
  379. // - If the cell format does not have a negative number format,
  380. // this method is called with a negative value and the number is
  381. // formatted with a minus sign at the start.
  382. boolean negative = value < 0;
  383. if (negative)
  384. value = -value;
  385. // Split out the fractional part if we need to print a fraction
  386. double fractional = 0;
  387. if (slash != null) {
  388. if (improperFraction) {
  389. fractional = value;
  390. value = 0;
  391. } else {
  392. fractional = value % 1.0;
  393. //noinspection SillyAssignment
  394. value = (long) value;
  395. }
  396. }
  397. Set<CellNumberStringMod> mods = new TreeSet<>();
  398. StringBuffer output = new StringBuffer(localiseFormat(desc));
  399. if (exponent != null) {
  400. writeScientific(value, output, mods);
  401. } else if (improperFraction) {
  402. writeFraction(value, null, fractional, output, mods);
  403. } else {
  404. StringBuffer result = new StringBuffer();
  405. try (Formatter f = new Formatter(result, locale)) {
  406. f.format(locale, printfFmt, value);
  407. } catch (IllegalFormatException e) {
  408. throw new IllegalArgumentException("Format: " + printfFmt, e);
  409. }
  410. if (numerator == null) {
  411. writeFractional(result, output);
  412. writeInteger(result, output, integerSpecials, mods, showGroupingSeparator);
  413. } else {
  414. writeFraction(value, result, fractional, output, mods);
  415. }
  416. }
  417. DecimalFormatSymbols dfs = getDecimalFormatSymbols();
  418. String groupingSeparator = Character.toString(dfs.getGroupingSeparator());
  419. // Now strip out any remaining '#'s and add any pending text ...
  420. Iterator<CellNumberStringMod> changes = mods.iterator();
  421. CellNumberStringMod nextChange = (changes.hasNext() ? changes.next() : null);
  422. // records chars already deleted
  423. SparseBitSet deletedChars = new SparseBitSet();
  424. int adjust = 0;
  425. for (Special s : specials) {
  426. int adjustedPos = s.pos + adjust;
  427. if (!deletedChars.get(s.pos) && output.charAt(adjustedPos) == '#') {
  428. output.deleteCharAt(adjustedPos);
  429. adjust--;
  430. deletedChars.set(s.pos);
  431. }
  432. while (nextChange != null && s == nextChange.getSpecial()) {
  433. int lenBefore = output.length();
  434. int modPos = s.pos + adjust;
  435. switch (nextChange.getOp()) {
  436. case CellNumberStringMod.AFTER:
  437. // ignore adding a comma after a deleted char (which was a '#')
  438. if (nextChange.getToAdd().equals(groupingSeparator) && deletedChars.get(s.pos)) {
  439. break;
  440. }
  441. output.insert(modPos + 1, nextChange.getToAdd());
  442. break;
  443. case CellNumberStringMod.BEFORE:
  444. output.insert(modPos, nextChange.getToAdd());
  445. break;
  446. case CellNumberStringMod.REPLACE:
  447. // delete starting pos in original coordinates
  448. int delPos = s.pos;
  449. if (!nextChange.isStartInclusive()) {
  450. delPos++;
  451. modPos++;
  452. }
  453. // Skip over anything already deleted
  454. while (deletedChars.get(delPos)) {
  455. delPos++;
  456. modPos++;
  457. }
  458. // delete end point in original
  459. int delEndPos = nextChange.getEnd().pos;
  460. if (nextChange.isEndInclusive()) {
  461. delEndPos++;
  462. }
  463. // delete end point in current
  464. int modEndPos = delEndPos + adjust;
  465. if (modPos < modEndPos) {
  466. if ("".equals(nextChange.getToAdd())) {
  467. output.delete(modPos, modEndPos);
  468. }
  469. else {
  470. char fillCh = nextChange.getToAdd().charAt(0);
  471. for (int i = modPos; i < modEndPos; i++) {
  472. output.setCharAt(i, fillCh);
  473. }
  474. }
  475. deletedChars.set(delPos, delEndPos);
  476. }
  477. break;
  478. default:
  479. throw new IllegalStateException("Unknown op: " + nextChange.getOp());
  480. }
  481. adjust += output.length() - lenBefore;
  482. nextChange = (changes.hasNext()) ? changes.next() : null;
  483. }
  484. }
  485. // Finally, add it to the string
  486. if (negative) {
  487. toAppendTo.append('-');
  488. }
  489. toAppendTo.append(output);
  490. }
  491. private void writeScientific(double value, StringBuffer output, Set<CellNumberStringMod> mods) {
  492. StringBuffer result = new StringBuffer();
  493. FieldPosition fractionPos = new FieldPosition(NumberFormat.FRACTION_FIELD);
  494. decimalFmt.format(value, result, fractionPos);
  495. writeInteger(result, output, integerSpecials, mods, showGroupingSeparator);
  496. writeFractional(result, output);
  497. /*
  498. * Exponent sign handling is complex.
  499. *
  500. * In DecimalFormat, you never put the sign in the format, and the sign only
  501. * comes out of the format if it is negative.
  502. *
  503. * In Excel, you always say whether to always show the sign ("e+") or only
  504. * show negative signs ("e-").
  505. *
  506. * Also in Excel, where you put the sign in the format is NOT where it comes
  507. * out in the result. In the format, the sign goes with the "e"; in the
  508. * output it goes with the exponent value. That is, if you say "#e-|#" you
  509. * get "1e|-5", not "1e-|5". This makes sense I suppose, but it complicates
  510. * things.
  511. *
  512. * Finally, everything else in this formatting code assumes that the base of
  513. * the result is the original format, and that starting from that situation,
  514. * the indexes of the original special characters can be used to place the new
  515. * characters. As just described, this is not true for the exponent's sign.
  516. * <p>
  517. * So here is how we handle it:
  518. *
  519. * (1) When parsing the format, remove the sign from after the 'e' and put it
  520. * before the first digit of the exponent (where it will be shown).
  521. *
  522. * (2) Determine the result's sign.
  523. *
  524. * (3) If it's missing, put the sign into the output to keep the result
  525. * lined up with the output. (In the result, "after the 'e'" and "before the
  526. * first digit" are the same because the result has no extra chars to be in
  527. * the way.)
  528. *
  529. * (4) In the output, remove the sign if it should not be shown ("e-" was used
  530. * and the sign is negative) or set it to the correct value.
  531. */
  532. // (2) Determine the result's sign.
  533. int ePos = fractionPos.getEndIndex();
  534. int signPos = ePos + 1;
  535. char expSignRes = result.charAt(signPos);
  536. if (expSignRes != '-') {
  537. // not a sign, so it's a digit, and therefore a positive exponent
  538. expSignRes = '+';
  539. // (3) If it's missing, put the sign into the output to keep the result
  540. // lined up with the output.
  541. result.insert(signPos, '+');
  542. }
  543. // Now the result lines up like it is supposed to with the specials' indexes
  544. ListIterator<Special> it = exponentSpecials.listIterator(1);
  545. Special expSign = it.next();
  546. char expSignFmt = expSign.ch;
  547. // (4) In the output, remove the sign if it should not be shown or set it to
  548. // the correct value.
  549. if (expSignRes == '-' || expSignFmt == '+') {
  550. mods.add(replaceMod(expSign, true, expSign, true, expSignRes));
  551. } else {
  552. mods.add(deleteMod(expSign, true, expSign, true));
  553. }
  554. StringBuffer exponentNum = new StringBuffer(result.substring(signPos + 1));
  555. writeInteger(exponentNum, output, exponentDigitSpecials, mods, false);
  556. }
  557. @SuppressWarnings("unchecked")
  558. private void writeFraction(double value, StringBuffer result,
  559. double fractional, StringBuffer output, Set<CellNumberStringMod> mods) {
  560. // Figure out if we are to suppress either the integer or fractional part.
  561. // With # the suppressed part is removed; with ? it is replaced with spaces.
  562. if (!improperFraction) {
  563. // If fractional part is zero, and numerator doesn't have '0', write out
  564. // only the integer part and strip the rest.
  565. if (fractional == 0 && !hasChar('0', numeratorSpecials)) {
  566. writeInteger(result, output, integerSpecials, mods, false);
  567. Special start = lastSpecial(integerSpecials);
  568. Special end = lastSpecial(denominatorSpecials);
  569. if (hasChar('?', integerSpecials, numeratorSpecials, denominatorSpecials)) {
  570. //if any format has '?', then replace the fraction with spaces
  571. mods.add(replaceMod(start, false, end, true, ' '));
  572. } else {
  573. // otherwise, remove the fraction
  574. mods.add(deleteMod(start, false, end, true));
  575. }
  576. // That's all, just return
  577. return;
  578. } else {
  579. // New we check to see if we should remove the integer part
  580. boolean numNoZero = !hasChar('0', numeratorSpecials);
  581. boolean intNoZero = !hasChar('0', integerSpecials);
  582. boolean intOnlyHash = integerSpecials.isEmpty() || (integerSpecials.size() == 1 && hasChar('#', integerSpecials));
  583. boolean removeBecauseZero = fractional == 0 && (intOnlyHash || numNoZero);
  584. boolean removeBecauseFraction = fractional != 0 && intNoZero;
  585. if (value == 0 && (removeBecauseZero || removeBecauseFraction)) {
  586. Special start = lastSpecial(integerSpecials);
  587. boolean hasPlaceHolder = hasChar('?', integerSpecials, numeratorSpecials);
  588. CellNumberStringMod sm = hasPlaceHolder
  589. ? replaceMod(start, true, numerator, false, ' ')
  590. : deleteMod(start, true, numerator, false);
  591. mods.add(sm);
  592. } else {
  593. // Not removing the integer part -- print it out
  594. writeInteger(result, output, integerSpecials, mods, false);
  595. }
  596. }
  597. }
  598. // Calculate and print the actual fraction (improper or otherwise)
  599. try {
  600. int n;
  601. int d;
  602. // the "fractional % 1" captures integer values in improper fractions
  603. if (fractional == 0 || (improperFraction && fractional % 1 == 0)) {
  604. // 0 as a fraction is reported by excel as 0/1
  605. n = (int) Math.round(fractional);
  606. d = 1;
  607. } else {
  608. SimpleFraction frac = SimpleFraction.buildFractionMaxDenominator(fractional, maxDenominator);
  609. n = frac.getNumerator();
  610. d = frac.getDenominator();
  611. }
  612. if (improperFraction) {
  613. n += Math.round(value * d);
  614. }
  615. writeSingleInteger(numeratorFmt, n, output, numeratorSpecials, mods);
  616. writeSingleInteger(denominatorFmt, d, output, denominatorSpecials, mods);
  617. } catch (RuntimeException e) {
  618. LOG.atError().withThrowable(e).log("error while fraction evaluation");
  619. }
  620. }
  621. private String localiseFormat(String format) {
  622. DecimalFormatSymbols dfs = getDecimalFormatSymbols();
  623. if(format.contains(",") && dfs.getGroupingSeparator() != ',') {
  624. if(format.contains(".") && dfs.getDecimalSeparator() != '.') {
  625. format = replaceLast(format, "\\.", "[DECIMAL_SEPARATOR]");
  626. format = format.replace(',', dfs.getGroupingSeparator())
  627. .replace("[DECIMAL_SEPARATOR]", Character.toString(dfs.getDecimalSeparator()));
  628. } else {
  629. format = format.replace(',', dfs.getGroupingSeparator());
  630. }
  631. } else if(format.contains(".") && dfs.getDecimalSeparator() != '.') {
  632. format = format.replace('.', dfs.getDecimalSeparator());
  633. }
  634. return format;
  635. }
  636. private static String replaceLast(String text, String regex, String replacement) {
  637. return text.replaceFirst("(?s)(.*)" + regex, "$1" + replacement);
  638. }
  639. private static boolean hasChar(char ch, List<Special>... numSpecials) {
  640. for (List<Special> specials : numSpecials) {
  641. for (Special s : specials) {
  642. if (s.ch == ch) {
  643. return true;
  644. }
  645. }
  646. }
  647. return false;
  648. }
  649. private void writeSingleInteger(String fmt, int num, StringBuffer output, List<Special> numSpecials, Set<CellNumberStringMod> mods) {
  650. StringBuffer sb = new StringBuffer();
  651. try (Formatter formatter = new Formatter(sb, locale)) {
  652. formatter.format(locale, fmt, num);
  653. }
  654. writeInteger(sb, output, numSpecials, mods, false);
  655. }
  656. private void writeInteger(StringBuffer result, StringBuffer output,
  657. List<Special> numSpecials, Set<CellNumberStringMod> mods,
  658. boolean showGroupingSeparator) {
  659. DecimalFormatSymbols dfs = getDecimalFormatSymbols();
  660. String decimalSeparator = Character.toString(dfs.getDecimalSeparator());
  661. String groupingSeparator = Character.toString(dfs.getGroupingSeparator());
  662. int pos = result.indexOf(decimalSeparator) - 1;
  663. if (pos < 0) {
  664. if (exponent != null && numSpecials == integerSpecials) {
  665. pos = result.indexOf("E") - 1;
  666. } else {
  667. pos = result.length() - 1;
  668. }
  669. }
  670. int strip;
  671. for (strip = 0; strip < pos; strip++) {
  672. char resultCh = result.charAt(strip);
  673. if (resultCh != '0' && resultCh != dfs.getGroupingSeparator()) {
  674. break;
  675. }
  676. }
  677. ListIterator<Special> it = numSpecials.listIterator(numSpecials.size());
  678. Special lastOutputIntegerDigit = null;
  679. int digit = 0;
  680. while (it.hasPrevious()) {
  681. char resultCh;
  682. if (pos >= 0) {
  683. resultCh = result.charAt(pos);
  684. } else {
  685. // If result is shorter than field, pretend there are leading zeros
  686. resultCh = '0';
  687. }
  688. Special s = it.previous();
  689. boolean followWithGroupingSeparator = showGroupingSeparator && digit > 0 && digit % 3 == 0;
  690. boolean zeroStrip = false;
  691. if (resultCh != '0' || s.ch == '0' || s.ch == '?' || pos >= strip) {
  692. zeroStrip = s.ch == '?' && pos < strip;
  693. output.setCharAt(s.pos, (zeroStrip ? ' ' : resultCh));
  694. lastOutputIntegerDigit = s;
  695. }
  696. if (followWithGroupingSeparator) {
  697. mods.add(insertMod(s, zeroStrip ? " " : groupingSeparator, CellNumberStringMod.AFTER));
  698. }
  699. digit++;
  700. --pos;
  701. }
  702. if (pos >= 0) {
  703. // We ran out of places to put digits before we ran out of digits; put this aside so we can add it later
  704. // pos was decremented at the end of the loop above when the iterator was at its end
  705. ++pos;
  706. StringBuffer extraLeadingDigits = new StringBuffer(result.substring(0, pos));
  707. if (showGroupingSeparator) {
  708. while (pos > 0) {
  709. if (digit > 0 && digit % 3 == 0) {
  710. extraLeadingDigits.insert(pos, groupingSeparator);
  711. }
  712. digit++;
  713. --pos;
  714. }
  715. }
  716. mods.add(insertMod(lastOutputIntegerDigit, extraLeadingDigits, CellNumberStringMod.BEFORE));
  717. }
  718. }
  719. private void writeFractional(StringBuffer result, StringBuffer output) {
  720. int digit;
  721. int strip;
  722. if (fractionalSpecials.size() > 0) {
  723. String decimalSeparator = Character.toString(getDecimalFormatSymbols().getDecimalSeparator());
  724. digit = result.indexOf(decimalSeparator) + 1;
  725. if (exponent != null) {
  726. strip = result.indexOf("e") - 1;
  727. } else {
  728. strip = result.length() - 1;
  729. }
  730. while (strip > digit && result.charAt(strip) == '0') {
  731. strip--;
  732. }
  733. for (Special s : fractionalSpecials) {
  734. char resultCh = result.charAt(digit);
  735. if (resultCh != '0' || s.ch == '0' || digit < strip) {
  736. output.setCharAt(s.pos, resultCh);
  737. } else if (s.ch == '?') {
  738. // This is when we're in trailing zeros, and the format is '?'.
  739. // We still strip out remaining '#'s later
  740. output.setCharAt(s.pos, ' ');
  741. }
  742. digit++;
  743. }
  744. }
  745. }
  746. /**
  747. * {@inheritDoc}
  748. * <p>
  749. * For a number, this is <tt>"#"</tt> for integer values, and <tt>"#.#"</tt>
  750. * for floating-point values.
  751. */
  752. public void simpleValue(StringBuffer toAppendTo, Object value) {
  753. SIMPLE_NUMBER.formatValue(toAppendTo, value);
  754. }
  755. private static Special lastSpecial(List<Special> s) {
  756. return s.get(s.size() - 1);
  757. }
  758. }