123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853 |
- /* ====================================================================
- Licensed to the Apache Software Foundation (ASF) under one or more
- contributor license agreements. See the NOTICE file distributed with
- this work for additional information regarding copyright ownership.
- The ASF licenses this file to You under the Apache License, Version 2.0
- (the "License"); you may not use this file except in compliance with
- the License. You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- ==================================================================== */
- package org.apache.poi.ss.format;
-
- import java.text.DecimalFormat;
- import java.text.DecimalFormatSymbols;
- import java.text.FieldPosition;
- import java.util.ArrayList;
- import java.util.Collections;
- import java.util.Formatter;
- import java.util.Iterator;
- import java.util.List;
- import java.util.ListIterator;
- import java.util.Locale;
- import java.util.Set;
- import java.util.TreeSet;
-
- import com.zaxxer.sparsebits.SparseBitSet;
- import org.apache.poi.util.LocaleUtil;
- import org.apache.poi.util.POILogFactory;
- import org.apache.poi.util.POILogger;
-
- /**
- * This class implements printing out a value using a number format.
- */
- public class CellNumberFormatter extends CellFormatter {
- private static final POILogger LOG = POILogFactory.getLogger(CellNumberFormatter.class);
-
- private final String desc;
- private final String printfFmt;
- private final double scale;
- private final Special decimalPoint;
- private final Special slash;
- private final Special exponent;
- private final Special numerator;
- private final Special afterInteger;
- private final Special afterFractional;
- private final boolean showGroupingSeparator;
- private final List<Special> specials = new ArrayList<>();
- private final List<Special> integerSpecials = new ArrayList<>();
- private final List<Special> fractionalSpecials = new ArrayList<>();
- private final List<Special> numeratorSpecials = new ArrayList<>();
- private final List<Special> denominatorSpecials = new ArrayList<>();
- private final List<Special> exponentSpecials = new ArrayList<>();
- private final List<Special> exponentDigitSpecials = new ArrayList<>();
- private final int maxDenominator;
- private final String numeratorFmt;
- private final String denominatorFmt;
- private final boolean improperFraction;
- private final DecimalFormat decimalFmt;
-
- // The CellNumberFormatter.simpleValue() method uses the SIMPLE_NUMBER
- // CellFormatter defined here. The CellFormat.GENERAL_FORMAT CellFormat
- // no longer uses the SIMPLE_NUMBER CellFormatter.
- // Note that the simpleValue()/SIMPLE_NUMBER CellFormatter format
- // ("#" for integer values, and "#.#" for floating-point values) is
- // different from the 'General' format for numbers ("#" for integer
- // values and "#.#########" for floating-point values).
- private final CellFormatter SIMPLE_NUMBER = new GeneralNumberFormatter(locale);
-
- private static class GeneralNumberFormatter extends CellFormatter {
- private GeneralNumberFormatter(Locale locale) {
- super(locale, "General");
- }
-
- public void formatValue(StringBuffer toAppendTo, Object value) {
- if (value == null) {
- return;
- }
-
- CellFormatter cf;
- if (value instanceof Number) {
- Number num = (Number) value;
- cf = (num.doubleValue() % 1.0 == 0) ? new CellNumberFormatter(locale, "#") :
- new CellNumberFormatter(locale, "#.#");
- } else {
- cf = CellTextFormatter.SIMPLE_TEXT;
- }
- cf.formatValue(toAppendTo, value);
- }
-
- public void simpleValue(StringBuffer toAppendTo, Object value) {
- formatValue(toAppendTo, value);
- }
- }
-
-
- /**
- * This class is used to mark where the special characters in the format
- * are, as opposed to the other characters that are simply printed.
- */
- /* package */ static class Special {
- final char ch;
- int pos;
-
- Special(char ch, int pos) {
- this.ch = ch;
- this.pos = pos;
- }
-
- @Override
- public String toString() {
- return "'" + ch + "' @ " + pos;
- }
- }
-
- /**
- * Creates a new cell number formatter.
- *
- * @param format The format to parse.
- */
- public CellNumberFormatter(String format) {
- this(LocaleUtil.getUserLocale(), format);
- }
-
- /**
- * Creates a new cell number formatter.
- *
- * @param locale The locale to use.
- * @param format The format to parse.
- */
- public CellNumberFormatter(Locale locale, String format) {
- super(locale, format);
-
- CellNumberPartHandler ph = new CellNumberPartHandler();
- StringBuffer descBuf = CellFormatPart.parseFormat(format, CellFormatType.NUMBER, ph);
-
- exponent = ph.getExponent();
- specials.addAll(ph.getSpecials());
- improperFraction = ph.isImproperFraction();
-
- // These are inconsistent settings, so ditch 'em
- if ((ph.getDecimalPoint() != null || ph.getExponent() != null) && ph.getSlash() != null) {
- slash = null;
- numerator = null;
- } else {
- slash = ph.getSlash();
- numerator = ph.getNumerator();
- }
-
- final int precision = interpretPrecision(ph.getDecimalPoint(), specials);
- int fractionPartWidth = 0;
- if (ph.getDecimalPoint() != null) {
- fractionPartWidth = 1 + precision;
- if (precision == 0) {
- // This means the format has a ".", but that output should have no decimals after it.
- // We just stop treating it specially
- specials.remove(ph.getDecimalPoint());
- decimalPoint = null;
- } else {
- decimalPoint = ph.getDecimalPoint();
- }
- } else {
- decimalPoint = null;
- }
-
- if (decimalPoint != null) {
- afterInteger = decimalPoint;
- } else if (exponent != null) {
- afterInteger = exponent;
- } else if (numerator != null) {
- afterInteger = numerator;
- } else {
- afterInteger = null;
- }
-
- if (exponent != null) {
- afterFractional = exponent;
- } else if (numerator != null) {
- afterFractional = numerator;
- } else {
- afterFractional = null;
- }
-
- double[] scaleByRef = {ph.getScale()};
- showGroupingSeparator = interpretIntegerCommas(descBuf, specials, decimalPoint, integerEnd(), fractionalEnd(), scaleByRef);
- if (exponent == null) {
- scale = scaleByRef[0];
- } else {
- // in "e" formats,% and trailing commas have no scaling effect
- scale = 1;
- }
-
- if (precision != 0) {
- // TODO: if decimalPoint is null (-> index == -1), return the whole list?
- fractionalSpecials.addAll(specials.subList(specials.indexOf(decimalPoint) + 1, fractionalEnd()));
- }
-
- if (exponent != null) {
- int exponentPos = specials.indexOf(exponent);
- exponentSpecials.addAll(specialsFor(exponentPos, 2));
- exponentDigitSpecials.addAll(specialsFor(exponentPos + 2));
- }
-
- if (slash != null) {
- if (numerator != null) {
- numeratorSpecials.addAll(specialsFor(specials.indexOf(numerator)));
- }
-
- denominatorSpecials.addAll(specialsFor(specials.indexOf(slash) + 1));
- if (denominatorSpecials.isEmpty()) {
- // no denominator follows the slash, drop the fraction idea
- numeratorSpecials.clear();
- maxDenominator = 1;
- numeratorFmt = null;
- denominatorFmt = null;
- } else {
- maxDenominator = maxValue(denominatorSpecials);
- numeratorFmt = singleNumberFormat(numeratorSpecials);
- denominatorFmt = singleNumberFormat(denominatorSpecials);
- }
- } else {
- maxDenominator = 1;
- numeratorFmt = null;
- denominatorFmt = null;
- }
-
- integerSpecials.addAll(specials.subList(0, integerEnd()));
-
- if (exponent == null) {
- StringBuilder fmtBuf = new StringBuilder("%");
-
- int integerPartWidth = calculateIntegerPartWidth();
- int totalWidth = integerPartWidth + fractionPartWidth;
-
- fmtBuf.append('0').append(totalWidth).append('.').append(precision);
-
- fmtBuf.append("f");
- printfFmt = fmtBuf.toString();
- decimalFmt = null;
- } else {
- StringBuffer fmtBuf = new StringBuffer();
- boolean first = true;
- if (integerSpecials.size() == 1) {
- // If we don't do this, we get ".6e5" instead of "6e4"
- fmtBuf.append("0");
- first = false;
- } else
- for (Special s : integerSpecials) {
- if (isDigitFmt(s)) {
- fmtBuf.append(first ? '#' : '0');
- first = false;
- }
- }
- if (fractionalSpecials.size() > 0) {
- fmtBuf.append('.');
- for (Special s : fractionalSpecials) {
- if (isDigitFmt(s)) {
- if (!first)
- fmtBuf.append('0');
- first = false;
- }
- }
- }
- fmtBuf.append('E');
- placeZeros(fmtBuf, exponentSpecials.subList(2, exponentSpecials.size()));
- decimalFmt = new DecimalFormat(fmtBuf.toString(), getDecimalFormatSymbols());
- printfFmt = null;
- }
-
- desc = descBuf.toString();
- }
-
- private DecimalFormatSymbols getDecimalFormatSymbols() {
- return DecimalFormatSymbols.getInstance(locale);
- }
-
- private static void placeZeros(StringBuffer sb, List<Special> specials) {
- for (Special s : specials) {
- if (isDigitFmt(s)) {
- sb.append('0');
- }
- }
- }
-
- private static CellNumberStringMod insertMod(Special special, CharSequence toAdd, int where) {
- return new CellNumberStringMod(special, toAdd, where);
- }
-
- private static CellNumberStringMod deleteMod(Special start, boolean startInclusive, Special end, boolean endInclusive) {
- return new CellNumberStringMod(start, startInclusive, end, endInclusive);
- }
-
- private static CellNumberStringMod replaceMod(Special start, boolean startInclusive, Special end, boolean endInclusive, char withChar) {
- return new CellNumberStringMod(start, startInclusive, end, endInclusive, withChar);
- }
-
- private static String singleNumberFormat(List<Special> numSpecials) {
- return "%0" + numSpecials.size() + "d";
- }
-
- private static int maxValue(List<Special> s) {
- return Math.toIntExact(Math.round(Math.pow(10, s.size()) - 1));
- }
-
- private List<Special> specialsFor(int pos, int takeFirst) {
- if (pos >= specials.size()) {
- return Collections.emptyList();
- }
- ListIterator<Special> it = specials.listIterator(pos + takeFirst);
- Special last = it.next();
- int end = pos + takeFirst;
- while (it.hasNext()) {
- Special s = it.next();
- if (!isDigitFmt(s) || s.pos - last.pos > 1)
- break;
- end++;
- last = s;
- }
- return specials.subList(pos, end + 1);
- }
-
- private List<Special> specialsFor(int pos) {
- return specialsFor(pos, 0);
- }
-
- private static boolean isDigitFmt(Special s) {
- return s.ch == '0' || s.ch == '?' || s.ch == '#';
- }
-
- private int calculateIntegerPartWidth() {
- int digitCount = 0;
- for (Special s : specials) {
- //!! Handle fractions: The previous set of digits before that is the numerator, so we should stop short of that
- if (s == afterInteger) {
- break;
- } else if (isDigitFmt(s)) {
- digitCount++;
- }
- }
- return digitCount;
- }
-
- private static int interpretPrecision(Special decimalPoint, List<Special> specials) {
- int idx = specials.indexOf(decimalPoint);
- int precision = 0;
- if (idx != -1) {
- // skip over the decimal point itself
- ListIterator<Special> it = specials.listIterator(idx+1);
- while (it.hasNext()) {
- Special s = it.next();
- if (!isDigitFmt(s)) {
- break;
- }
- precision++;
- }
- }
- return precision;
- }
-
- private static boolean interpretIntegerCommas
- (StringBuffer sb, List<Special> specials, Special decimalPoint, int integerEnd, int fractionalEnd, double[] scale) {
- // In the integer part, commas at the end are scaling commas; other commas mean to show thousand-grouping commas
- ListIterator<Special> it = specials.listIterator(integerEnd);
-
- boolean stillScaling = true;
- boolean integerCommas = false;
- while (it.hasPrevious()) {
- Special s = it.previous();
- if (s.ch != ',') {
- stillScaling = false;
- } else {
- if (stillScaling) {
- scale[0] /= 1000;
- } else {
- integerCommas = true;
- }
- }
- }
-
- if (decimalPoint != null) {
- it = specials.listIterator(fractionalEnd);
- while (it.hasPrevious()) {
- Special s = it.previous();
- if (s.ch != ',') {
- break;
- } else {
- scale[0] /= 1000;
- }
- }
- }
-
- // Now strip them out -- we only need their interpretation, not their presence
- it = specials.listIterator();
- int removed = 0;
- while (it.hasNext()) {
- Special s = it.next();
- s.pos -= removed;
- if (s.ch == ',') {
- removed++;
- it.remove();
- sb.deleteCharAt(s.pos);
- }
- }
-
- return integerCommas;
- }
-
- private int integerEnd() {
- return (afterInteger == null) ? specials.size() : specials.indexOf(afterInteger);
- }
-
- private int fractionalEnd() {
- return (afterFractional == null) ? specials.size() : specials.indexOf(afterFractional);
- }
-
- /** {@inheritDoc} */
- public void formatValue(StringBuffer toAppendTo, Object valueObject) {
- double value = ((Number) valueObject).doubleValue();
- value *= scale;
-
- // For negative numbers:
- // - If the cell format has a negative number format, this method
- // is called with a positive value and the number format has
- // the negative formatting required, e.g. minus sign or brackets.
- // - If the cell format does not have a negative number format,
- // this method is called with a negative value and the number is
- // formatted with a minus sign at the start.
- boolean negative = value < 0;
- if (negative)
- value = -value;
-
- // Split out the fractional part if we need to print a fraction
- double fractional = 0;
- if (slash != null) {
- if (improperFraction) {
- fractional = value;
- value = 0;
- } else {
- fractional = value % 1.0;
- //noinspection SillyAssignment
- value = (long) value;
- }
- }
-
- Set<CellNumberStringMod> mods = new TreeSet<>();
- StringBuffer output = new StringBuffer(localiseFormat(desc));
-
- if (exponent != null) {
- writeScientific(value, output, mods);
- } else if (improperFraction) {
- writeFraction(value, null, fractional, output, mods);
- } else {
- StringBuffer result = new StringBuffer();
- try (Formatter f = new Formatter(result, locale)) {
- f.format(locale, printfFmt, value);
- }
-
- if (numerator == null) {
- writeFractional(result, output);
- writeInteger(result, output, integerSpecials, mods, showGroupingSeparator);
- } else {
- writeFraction(value, result, fractional, output, mods);
- }
- }
-
- DecimalFormatSymbols dfs = getDecimalFormatSymbols();
- String groupingSeparator = Character.toString(dfs.getGroupingSeparator());
-
- // Now strip out any remaining '#'s and add any pending text ...
- Iterator<CellNumberStringMod> changes = mods.iterator();
- CellNumberStringMod nextChange = (changes.hasNext() ? changes.next() : null);
- // records chars already deleted
- SparseBitSet deletedChars = new SparseBitSet();
- int adjust = 0;
- for (Special s : specials) {
- int adjustedPos = s.pos + adjust;
- if (!deletedChars.get(s.pos) && output.charAt(adjustedPos) == '#') {
- output.deleteCharAt(adjustedPos);
- adjust--;
- deletedChars.set(s.pos);
- }
- while (nextChange != null && s == nextChange.getSpecial()) {
- int lenBefore = output.length();
- int modPos = s.pos + adjust;
- switch (nextChange.getOp()) {
- case CellNumberStringMod.AFTER:
- // ignore adding a comma after a deleted char (which was a '#')
- if (nextChange.getToAdd().equals(groupingSeparator) && deletedChars.get(s.pos)) {
- break;
- }
- output.insert(modPos + 1, nextChange.getToAdd());
- break;
- case CellNumberStringMod.BEFORE:
- output.insert(modPos, nextChange.getToAdd());
- break;
-
- case CellNumberStringMod.REPLACE:
- // delete starting pos in original coordinates
- int delPos = s.pos;
- if (!nextChange.isStartInclusive()) {
- delPos++;
- modPos++;
- }
-
- // Skip over anything already deleted
- while (deletedChars.get(delPos)) {
- delPos++;
- modPos++;
- }
-
- // delete end point in original
- int delEndPos = nextChange.getEnd().pos;
- if (nextChange.isEndInclusive()) {
- delEndPos++;
- }
-
- // delete end point in current
- int modEndPos = delEndPos + adjust;
-
- if (modPos < modEndPos) {
- if ("".equals(nextChange.getToAdd())) {
- output.delete(modPos, modEndPos);
- }
- else {
- char fillCh = nextChange.getToAdd().charAt(0);
- for (int i = modPos; i < modEndPos; i++) {
- output.setCharAt(i, fillCh);
- }
- }
- deletedChars.set(delPos, delEndPos);
- }
- break;
-
- default:
- throw new IllegalStateException("Unknown op: " + nextChange.getOp());
- }
- adjust += output.length() - lenBefore;
-
- nextChange = (changes.hasNext()) ? changes.next() : null;
- }
- }
-
- // Finally, add it to the string
- if (negative) {
- toAppendTo.append('-');
- }
- toAppendTo.append(output);
- }
-
- private void writeScientific(double value, StringBuffer output, Set<CellNumberStringMod> mods) {
-
- StringBuffer result = new StringBuffer();
- FieldPosition fractionPos = new FieldPosition(DecimalFormat.FRACTION_FIELD);
- decimalFmt.format(value, result, fractionPos);
- writeInteger(result, output, integerSpecials, mods, showGroupingSeparator);
- writeFractional(result, output);
-
- /*
- * Exponent sign handling is complex.
- *
- * In DecimalFormat, you never put the sign in the format, and the sign only
- * comes out of the format if it is negative.
- *
- * In Excel, you always say whether to always show the sign ("e+") or only
- * show negative signs ("e-").
- *
- * Also in Excel, where you put the sign in the format is NOT where it comes
- * out in the result. In the format, the sign goes with the "e"; in the
- * output it goes with the exponent value. That is, if you say "#e-|#" you
- * get "1e|-5", not "1e-|5". This makes sense I suppose, but it complicates
- * things.
- *
- * Finally, everything else in this formatting code assumes that the base of
- * the result is the original format, and that starting from that situation,
- * the indexes of the original special characters can be used to place the new
- * characters. As just described, this is not true for the exponent's sign.
- * <p>
- * So here is how we handle it:
- *
- * (1) When parsing the format, remove the sign from after the 'e' and put it
- * before the first digit of the exponent (where it will be shown).
- *
- * (2) Determine the result's sign.
- *
- * (3) If it's missing, put the sign into the output to keep the result
- * lined up with the output. (In the result, "after the 'e'" and "before the
- * first digit" are the same because the result has no extra chars to be in
- * the way.)
- *
- * (4) In the output, remove the sign if it should not be shown ("e-" was used
- * and the sign is negative) or set it to the correct value.
- */
-
- // (2) Determine the result's sign.
- int ePos = fractionPos.getEndIndex();
- int signPos = ePos + 1;
- char expSignRes = result.charAt(signPos);
- if (expSignRes != '-') {
- // not a sign, so it's a digit, and therefore a positive exponent
- expSignRes = '+';
- // (3) If it's missing, put the sign into the output to keep the result
- // lined up with the output.
- result.insert(signPos, '+');
- }
-
- // Now the result lines up like it is supposed to with the specials' indexes
- ListIterator<Special> it = exponentSpecials.listIterator(1);
- Special expSign = it.next();
- char expSignFmt = expSign.ch;
-
- // (4) In the output, remove the sign if it should not be shown or set it to
- // the correct value.
- if (expSignRes == '-' || expSignFmt == '+') {
- mods.add(replaceMod(expSign, true, expSign, true, expSignRes));
- } else {
- mods.add(deleteMod(expSign, true, expSign, true));
- }
-
- StringBuffer exponentNum = new StringBuffer(result.substring(signPos + 1));
- writeInteger(exponentNum, output, exponentDigitSpecials, mods, false);
- }
-
- @SuppressWarnings("unchecked")
- private void writeFraction(double value, StringBuffer result,
- double fractional, StringBuffer output, Set<CellNumberStringMod> mods) {
-
- // Figure out if we are to suppress either the integer or fractional part.
- // With # the suppressed part is removed; with ? it is replaced with spaces.
- if (!improperFraction) {
- // If fractional part is zero, and numerator doesn't have '0', write out
- // only the integer part and strip the rest.
- if (fractional == 0 && !hasChar('0', numeratorSpecials)) {
- writeInteger(result, output, integerSpecials, mods, false);
-
- Special start = lastSpecial(integerSpecials);
- Special end = lastSpecial(denominatorSpecials);
- if (hasChar('?', integerSpecials, numeratorSpecials, denominatorSpecials)) {
- //if any format has '?', then replace the fraction with spaces
- mods.add(replaceMod(start, false, end, true, ' '));
- } else {
- // otherwise, remove the fraction
- mods.add(deleteMod(start, false, end, true));
- }
-
- // That's all, just return
- return;
- } else {
- // New we check to see if we should remove the integer part
- boolean numNoZero = !hasChar('0', numeratorSpecials);
- boolean intNoZero = !hasChar('0', integerSpecials);
- boolean intOnlyHash = integerSpecials.isEmpty() || (integerSpecials.size() == 1 && hasChar('#', integerSpecials));
-
- boolean removeBecauseZero = fractional == 0 && (intOnlyHash || numNoZero);
- boolean removeBecauseFraction = fractional != 0 && intNoZero;
-
- if (value == 0 && (removeBecauseZero || removeBecauseFraction)) {
- Special start = lastSpecial(integerSpecials);
- boolean hasPlaceHolder = hasChar('?', integerSpecials, numeratorSpecials);
- CellNumberStringMod sm = hasPlaceHolder
- ? replaceMod(start, true, numerator, false, ' ')
- : deleteMod(start, true, numerator, false);
- mods.add(sm);
- } else {
- // Not removing the integer part -- print it out
- writeInteger(result, output, integerSpecials, mods, false);
- }
- }
- }
-
- // Calculate and print the actual fraction (improper or otherwise)
- try {
- int n;
- int d;
- // the "fractional % 1" captures integer values in improper fractions
- if (fractional == 0 || (improperFraction && fractional % 1 == 0)) {
- // 0 as a fraction is reported by excel as 0/1
- n = (int) Math.round(fractional);
- d = 1;
- } else {
- SimpleFraction frac = SimpleFraction.buildFractionMaxDenominator(fractional, maxDenominator);
- n = frac.getNumerator();
- d = frac.getDenominator();
- }
- if (improperFraction) {
- n += Math.round(value * d);
- }
- writeSingleInteger(numeratorFmt, n, output, numeratorSpecials, mods);
- writeSingleInteger(denominatorFmt, d, output, denominatorSpecials, mods);
- } catch (RuntimeException ignored) {
- LOG.log(POILogger.ERROR, "error while fraction evaluation", ignored);
- }
- }
-
- private String localiseFormat(String format) {
- DecimalFormatSymbols dfs = getDecimalFormatSymbols();
- if(format.contains(",") && dfs.getGroupingSeparator() != ',') {
- if(format.contains(".") && dfs.getDecimalSeparator() != '.') {
- format = replaceLast(format, "\\.", "[DECIMAL_SEPARATOR]");
- format = format.replace(',', dfs.getGroupingSeparator())
- .replace("[DECIMAL_SEPARATOR]", Character.toString(dfs.getDecimalSeparator()));
- } else {
- format = format.replace(',', dfs.getGroupingSeparator());
- }
- } else if(format.contains(".") && dfs.getDecimalSeparator() != '.') {
- format = format.replace('.', dfs.getDecimalSeparator());
- }
- return format;
- }
-
-
- private static String replaceLast(String text, String regex, String replacement) {
- return text.replaceFirst("(?s)(.*)" + regex, "$1" + replacement);
- }
-
- private static boolean hasChar(char ch, List<Special>... numSpecials) {
- for (List<Special> specials : numSpecials) {
- for (Special s : specials) {
- if (s.ch == ch) {
- return true;
- }
- }
- }
- return false;
- }
-
- private void writeSingleInteger(String fmt, int num, StringBuffer output, List<Special> numSpecials, Set<CellNumberStringMod> mods) {
-
- StringBuffer sb = new StringBuffer();
- try (Formatter formatter = new Formatter(sb, locale)) {
- formatter.format(locale, fmt, num);
- }
- writeInteger(sb, output, numSpecials, mods, false);
- }
-
- private void writeInteger(StringBuffer result, StringBuffer output,
- List<Special> numSpecials, Set<CellNumberStringMod> mods,
- boolean showGroupingSeparator) {
-
- DecimalFormatSymbols dfs = getDecimalFormatSymbols();
- String decimalSeparator = Character.toString(dfs.getDecimalSeparator());
- String groupingSeparator = Character.toString(dfs.getGroupingSeparator());
-
- int pos = result.indexOf(decimalSeparator) - 1;
- if (pos < 0) {
- if (exponent != null && numSpecials == integerSpecials) {
- pos = result.indexOf("E") - 1;
- } else {
- pos = result.length() - 1;
- }
- }
-
- int strip;
- for (strip = 0; strip < pos; strip++) {
- char resultCh = result.charAt(strip);
- if (resultCh != '0' && resultCh != dfs.getGroupingSeparator()) {
- break;
- }
- }
-
- ListIterator<Special> it = numSpecials.listIterator(numSpecials.size());
- boolean followWithGroupingSeparator = false;
- Special lastOutputIntegerDigit = null;
- int digit = 0;
- while (it.hasPrevious()) {
- char resultCh;
- if (pos >= 0) {
- resultCh = result.charAt(pos);
- } else {
- // If result is shorter than field, pretend there are leading zeros
- resultCh = '0';
- }
- Special s = it.previous();
- followWithGroupingSeparator = showGroupingSeparator && digit > 0 && digit % 3 == 0;
- boolean zeroStrip = false;
- if (resultCh != '0' || s.ch == '0' || s.ch == '?' || pos >= strip) {
- zeroStrip = s.ch == '?' && pos < strip;
- output.setCharAt(s.pos, (zeroStrip ? ' ' : resultCh));
- lastOutputIntegerDigit = s;
- }
- if (followWithGroupingSeparator) {
- mods.add(insertMod(s, zeroStrip ? " " : groupingSeparator, CellNumberStringMod.AFTER));
- followWithGroupingSeparator = false;
- }
- digit++;
- --pos;
- }
- if (pos >= 0) {
- // We ran out of places to put digits before we ran out of digits; put this aside so we can add it later
- // pos was decremented at the end of the loop above when the iterator was at its end
- ++pos;
- StringBuffer extraLeadingDigits = new StringBuffer(result.substring(0, pos));
- if (showGroupingSeparator) {
- while (pos > 0) {
- if (digit > 0 && digit % 3 == 0) {
- extraLeadingDigits.insert(pos, groupingSeparator);
- }
- digit++;
- --pos;
- }
- }
- mods.add(insertMod(lastOutputIntegerDigit, extraLeadingDigits, CellNumberStringMod.BEFORE));
- }
- }
-
- private void writeFractional(StringBuffer result, StringBuffer output) {
- int digit;
- int strip;
- if (fractionalSpecials.size() > 0) {
- String decimalSeparator = Character.toString(getDecimalFormatSymbols().getDecimalSeparator());
- digit = result.indexOf(decimalSeparator) + 1;
- if (exponent != null) {
- strip = result.indexOf("e") - 1;
- } else {
- strip = result.length() - 1;
- }
-
- while (strip > digit && result.charAt(strip) == '0') {
- strip--;
- }
-
- for (Special s : fractionalSpecials) {
- char resultCh = result.charAt(digit);
- if (resultCh != '0' || s.ch == '0' || digit < strip) {
- output.setCharAt(s.pos, resultCh);
- } else if (s.ch == '?') {
- // This is when we're in trailing zeros, and the format is '?'.
- // We still strip out remaining '#'s later
- output.setCharAt(s.pos, ' ');
- }
- digit++;
- }
- }
- }
-
- /**
- * {@inheritDoc}
- * <p>
- * For a number, this is <tt>"#"</tt> for integer values, and <tt>"#.#"</tt>
- * for floating-point values.
- */
- public void simpleValue(StringBuffer toAppendTo, Object value) {
- SIMPLE_NUMBER.formatValue(toAppendTo, value);
- }
-
- private static Special lastSpecial(List<Special> s) {
- return s.get(s.size() - 1);
- }
- }
|