| Index: intl/lib/src/intl/number_format.dart
|
| diff --git a/intl/lib/src/intl/number_format.dart b/intl/lib/src/intl/number_format.dart
|
| deleted file mode 100644
|
| index 9e0de51c8846f5971dafa85da0b935e6ad7575a5..0000000000000000000000000000000000000000
|
| --- a/intl/lib/src/intl/number_format.dart
|
| +++ /dev/null
|
| @@ -1,1081 +0,0 @@
|
| -// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
|
| -// for details. All rights reserved. Use of this source code is governed by a
|
| -// BSD-style license that can be found in the LICENSE file.
|
| -
|
| -part of intl;
|
| -
|
| -/**
|
| - * Provides the ability to format a number in a locale-specific way. The
|
| - * format is specified as a pattern using a subset of the ICU formatting
|
| - * patterns.
|
| - *
|
| - * - `0` A single digit
|
| - * - `#` A single digit, omitted if the value is zero
|
| - * - `.` Decimal separator
|
| - * - `-` Minus sign
|
| - * - `,` Grouping separator
|
| - * - `E` Separates mantissa and expontent
|
| - * - `+` - Before an exponent, indicates it should be prefixed with a plus sign.
|
| - * - `%` - In prefix or suffix, multiply by 100 and show as percentage
|
| - * - `‰ (\u2030)` In prefix or suffix, multiply by 1000 and show as per mille
|
| - * - `¤ (\u00A4)` Currency sign, replaced by currency name
|
| - * - `'` Used to quote special characters
|
| - * - `;` Used to separate the positive and negative patterns if both are present
|
| - *
|
| - * For example,
|
| - * var f = new NumberFormat("###.0#", "en_US");
|
| - * print(f.format(12.345));
|
| - * ==> 12.34
|
| - * If the locale is not specified, it will default to the current locale. If
|
| - * the format is not specified it will print in a basic format with at least
|
| - * one integer digit and three fraction digits.
|
| - *
|
| - * There are also standard patterns available via the special constructors. e.g.
|
| - * var percent = new NumberFormat.percentFormat("ar");
|
| - * var eurosInUSFormat = new NumberFormat.currencyPattern("en_US", "€");
|
| - * There are four such constructors: decimalFormat, percentFormat,
|
| - * scientificFormat and currencyFormat. However, at the moment,
|
| - * scientificFormat prints only as equivalent to "#E0" and does not take
|
| - * into account significant digits. The currencyFormat will default to the
|
| - * three-letter name of the currency if no explicit name/symbol is provided.
|
| - */
|
| -class NumberFormat {
|
| - /** Variables to determine how number printing behaves. */
|
| - // TODO(alanknight): If these remain as variables and are set based on the
|
| - // pattern, can we make them final?
|
| - String _negativePrefix = '-';
|
| - String _positivePrefix = '';
|
| - String _negativeSuffix = '';
|
| - String _positiveSuffix = '';
|
| - /**
|
| - * How many numbers in a group when using punctuation to group digits in
|
| - * large numbers. e.g. in en_US: "1,000,000" has a grouping size of 3 digits
|
| - * between commas.
|
| - */
|
| - int _groupingSize = 3;
|
| - /**
|
| - * In some formats the last grouping size may be different than previous
|
| - * ones, e.g. Hindi.
|
| - */
|
| - int _finalGroupingSize = 3;
|
| - /**
|
| - * Set to true if the format has explicitly set the grouping size.
|
| - */
|
| - bool _groupingSizeSetExplicitly = false;
|
| - bool _decimalSeparatorAlwaysShown = false;
|
| - bool _useSignForPositiveExponent = false;
|
| - bool _useExponentialNotation = false;
|
| -
|
| - int maximumIntegerDigits = 40;
|
| - int minimumIntegerDigits = 1;
|
| - int maximumFractionDigits = 3;
|
| - int minimumFractionDigits = 0;
|
| - int minimumExponentDigits = 0;
|
| -
|
| - /**
|
| - * For percent and permille, what are we multiplying by in order to
|
| - * get the printed value, e.g. 100 for percent.
|
| - */
|
| - int get _multiplier => _internalMultiplier;
|
| - set _multiplier(int x) {
|
| - _internalMultiplier = x;
|
| - _multiplierDigits = (log(_multiplier) / LN10).round();
|
| - }
|
| - int _internalMultiplier = 1;
|
| -
|
| - /** How many digits are there in the [_multiplier]. */
|
| - int _multiplierDigits = 0;
|
| -
|
| - /**
|
| - * Stores the pattern used to create this format. This isn't used, but
|
| - * is helpful in debugging.
|
| - */
|
| - String _pattern;
|
| -
|
| - /** The locale in which we print numbers. */
|
| - final String _locale;
|
| -
|
| - /** Caches the symbols used for our locale. */
|
| - NumberSymbols _symbols;
|
| -
|
| - /** The name (or symbol) of the currency to print. */
|
| - String currencyName;
|
| -
|
| - /**
|
| - * Transient internal state in which to build up the result of the format
|
| - * operation. We can have this be just an instance variable because Dart is
|
| - * single-threaded and unless we do an asynchronous operation in the process
|
| - * of formatting then there will only ever be one number being formatted
|
| - * at a time. In languages with threads we'd need to pass this on the stack.
|
| - */
|
| - final StringBuffer _buffer = new StringBuffer();
|
| -
|
| - /**
|
| - * Create a number format that prints using [newPattern] as it applies in
|
| - * [locale].
|
| - */
|
| - factory NumberFormat([String newPattern, String locale]) =>
|
| - new NumberFormat._forPattern(locale, (x) => newPattern);
|
| -
|
| - /** Create a number format that prints as DECIMAL_PATTERN. */
|
| - NumberFormat.decimalPattern([String locale])
|
| - : this._forPattern(locale, (x) => x.DECIMAL_PATTERN);
|
| -
|
| - /** Create a number format that prints as PERCENT_PATTERN. */
|
| - NumberFormat.percentPattern([String locale])
|
| - : this._forPattern(locale, (x) => x.PERCENT_PATTERN);
|
| -
|
| - /** Create a number format that prints as SCIENTIFIC_PATTERN. */
|
| - NumberFormat.scientificPattern([String locale])
|
| - : this._forPattern(locale, (x) => x.SCIENTIFIC_PATTERN);
|
| -
|
| - /**
|
| - * Create a number format that prints as CURRENCY_PATTERN. If provided,
|
| - * use [nameOrSymbol] in place of the default currency name. e.g.
|
| - * var eurosInCurrentLocale = new NumberFormat
|
| - * .currencyPattern(Intl.defaultLocale, "€");
|
| - */
|
| - NumberFormat.currencyPattern([String locale, String nameOrSymbol])
|
| - : this._forPattern(locale, (x) => x.CURRENCY_PATTERN, nameOrSymbol);
|
| -
|
| - /**
|
| - * Create a number format that prints in a pattern we get from
|
| - * the [getPattern] function using the locale [locale].
|
| - */
|
| - NumberFormat._forPattern(String locale, Function getPattern,
|
| - [this.currencyName])
|
| - : _locale = Intl.verifiedLocale(locale, localeExists) {
|
| - _symbols = numberFormatSymbols[_locale];
|
| - if (currencyName == null) {
|
| - currencyName = _symbols.DEF_CURRENCY_CODE;
|
| - }
|
| - _setPattern(getPattern(_symbols));
|
| - }
|
| -
|
| - /**
|
| - * Return the locale code in which we operate, e.g. 'en_US' or 'pt'.
|
| - */
|
| - String get locale => _locale;
|
| -
|
| - /**
|
| - * Return true if the locale exists, or if it is null. The null case
|
| - * is interpreted to mean that we use the default locale.
|
| - */
|
| - static bool localeExists(localeName) {
|
| - if (localeName == null) return false;
|
| - return numberFormatSymbols.containsKey(localeName);
|
| - }
|
| -
|
| - /**
|
| - * Return the symbols which are used in our locale. Cache them to avoid
|
| - * repeated lookup.
|
| - */
|
| - NumberSymbols get symbols => _symbols;
|
| -
|
| - /**
|
| - * Format [number] according to our pattern and return the formatted string.
|
| - */
|
| - String format(number) {
|
| - if (_isNaN(number)) return symbols.NAN;
|
| - if (_isInfinite(number)) return "${_signPrefix(number)}${symbols.INFINITY}";
|
| -
|
| - _add(_signPrefix(number));
|
| - _formatNumber(number.abs());
|
| - _add(_signSuffix(number));
|
| -
|
| - var result = _buffer.toString();
|
| - _buffer.clear();
|
| - return result;
|
| - }
|
| -
|
| - /**
|
| - * Parse the number represented by the string. If it's not
|
| - * parseable, throws a [FormatException].
|
| - */
|
| - num parse(String text) => new _NumberParser(this, text).value;
|
| -
|
| - /**
|
| - * Format the main part of the number in the form dictated by the pattern.
|
| - */
|
| - void _formatNumber(number) {
|
| - if (_useExponentialNotation) {
|
| - _formatExponential(number);
|
| - } else {
|
| - _formatFixed(number);
|
| - }
|
| - }
|
| -
|
| - /** Format the number in exponential notation. */
|
| - void _formatExponential(num number) {
|
| - if (number == 0.0) {
|
| - _formatFixed(number);
|
| - _formatExponent(0);
|
| - return;
|
| - }
|
| -
|
| - var exponent = (log(number) / log(10)).floor();
|
| - var mantissa = number / pow(10.0, exponent);
|
| -
|
| - var minIntDigits = minimumIntegerDigits;
|
| - if (maximumIntegerDigits > 1 &&
|
| - maximumIntegerDigits > minimumIntegerDigits) {
|
| - // A repeating range is defined; adjust to it as follows.
|
| - // If repeat == 3, we have 6,5,4=>3; 3,2,1=>0; 0,-1,-2=>-3;
|
| - // -3,-4,-5=>-6, etc. This takes into account that the
|
| - // exponent we have here is off by one from what we expect;
|
| - // it is for the format 0.MMMMMx10^n.
|
| - while ((exponent % maximumIntegerDigits) != 0) {
|
| - mantissa *= 10;
|
| - exponent--;
|
| - }
|
| - minIntDigits = 1;
|
| - } else {
|
| - // No repeating range is defined, use minimum integer digits.
|
| - if (minimumIntegerDigits < 1) {
|
| - exponent++;
|
| - mantissa /= 10;
|
| - } else {
|
| - exponent -= minimumIntegerDigits - 1;
|
| - mantissa *= pow(10, minimumIntegerDigits - 1);
|
| - }
|
| - }
|
| - _formatFixed(mantissa);
|
| - _formatExponent(exponent);
|
| - }
|
| -
|
| - /**
|
| - * Format the exponent portion, e.g. in "1.3e-5" the "e-5".
|
| - */
|
| - void _formatExponent(num exponent) {
|
| - _add(symbols.EXP_SYMBOL);
|
| - if (exponent < 0) {
|
| - exponent = -exponent;
|
| - _add(symbols.MINUS_SIGN);
|
| - } else if (_useSignForPositiveExponent) {
|
| - _add(symbols.PLUS_SIGN);
|
| - }
|
| - _pad(minimumExponentDigits, exponent.toString());
|
| - }
|
| -
|
| - /** Used to test if we have exceeded Javascript integer limits. */
|
| - final _maxInt = pow(2, 52);
|
| -
|
| - /**
|
| - * Helpers to check numbers that don't conform to the [num] interface,
|
| - * e.g. Int64
|
| - */
|
| - _isInfinite(number) => number is num ? number.isInfinite : false;
|
| - _isNaN(number) => number is num ? number.isNaN : false;
|
| - _round(number) => number is num ? number.round() : number;
|
| - _floor(number) => number is num ? number.floor() : number;
|
| -
|
| - /**
|
| - * Format the basic number portion, inluding the fractional digits.
|
| - */
|
| - void _formatFixed(number) {
|
| - var integerPart;
|
| - int fractionPart;
|
| - int extraIntegerDigits;
|
| -
|
| - final power = pow(10, maximumFractionDigits);
|
| - final digitMultiplier = power * _multiplier;
|
| -
|
| - if (_isInfinite(number)) {
|
| - integerPart = number.toInt();
|
| - extraIntegerDigits = 0;
|
| - fractionPart = 0;
|
| - } else {
|
| - // We have three possible pieces. First, the basic integer part. If this
|
| - // is a percent or permille, the additional 2 or 3 digits. Finally the
|
| - // fractional part.
|
| - // We avoid multiplying the number because it might overflow if we have
|
| - // a fixed-size integer type, so we extract each of the three as an
|
| - // integer pieces.
|
| - integerPart = _floor(number);
|
| - var fraction = number - integerPart;
|
| - // Multiply out to the number of decimal places and the percent, then
|
| - // round. For fixed-size integer types this should always be zero, so
|
| - // multiplying is OK.
|
| - var remainingDigits = _round(fraction * digitMultiplier).toInt();
|
| - // However, in rounding we may overflow into the main digits.
|
| - if (remainingDigits >= digitMultiplier) {
|
| - integerPart++;
|
| - remainingDigits -= digitMultiplier;
|
| - }
|
| - // Separate out the extra integer parts from the fraction part.
|
| - extraIntegerDigits = remainingDigits ~/ power;
|
| - fractionPart = remainingDigits % power;
|
| - }
|
| - var fractionPresent = minimumFractionDigits > 0 || fractionPart > 0;
|
| -
|
| - var integerDigits = _integerDigits(integerPart, extraIntegerDigits);
|
| - var digitLength = integerDigits.length;
|
| -
|
| - if (_hasIntegerDigits(integerDigits)) {
|
| - _pad(minimumIntegerDigits - digitLength);
|
| - for (var i = 0; i < digitLength; i++) {
|
| - _addDigit(integerDigits.codeUnitAt(i));
|
| - _group(digitLength, i);
|
| - }
|
| - } else if (!fractionPresent) {
|
| - // If neither fraction nor integer part exists, just print zero.
|
| - _addZero();
|
| - }
|
| -
|
| - _decimalSeparator(fractionPresent);
|
| - _formatFractionPart((fractionPart + power).toString());
|
| - }
|
| -
|
| - /**
|
| - * Compute the raw integer digits which will then be printed with
|
| - * grouping and translated to localized digits.
|
| - */
|
| - String _integerDigits(integerPart, extraIntegerDigits) {
|
| - // If the int part is larger than 2^52 and we're on Javascript (so it's
|
| - // really a float) it will lose precision, so pad out the rest of it
|
| - // with zeros. Check for Javascript by seeing if an integer is double.
|
| - var paddingDigits = '';
|
| - if (1 is double && integerPart is num && integerPart > _maxInt) {
|
| - var howManyDigitsTooBig = (log(integerPart) / LN10).ceil() - 16;
|
| - var divisor = pow(10, howManyDigitsTooBig).round();
|
| - paddingDigits = symbols.ZERO_DIGIT * howManyDigitsTooBig.toInt();
|
| - integerPart = (integerPart / divisor).truncate();
|
| - }
|
| -
|
| - var extra = extraIntegerDigits == 0 ? '' : extraIntegerDigits.toString();
|
| - var intDigits = _mainIntegerDigits(integerPart);
|
| - var paddedExtra =
|
| - intDigits.isEmpty ? extra : extra.padLeft(_multiplierDigits, '0');
|
| - return "${intDigits}${paddedExtra}${paddingDigits}";
|
| - }
|
| -
|
| - /**
|
| - * The digit string of the integer part. This is the empty string if the
|
| - * integer part is zero and otherwise is the toString() of the integer
|
| - * part, stripping off any minus sign.
|
| - */
|
| - String _mainIntegerDigits(integer) {
|
| - if (integer == 0) return '';
|
| - var digits = integer.toString();
|
| - // If we have a fixed-length int representation, it can have a negative
|
| - // number whose negation is also negative, e.g. 2^-63 in 64-bit.
|
| - // Remove the minus sign.
|
| - return digits.startsWith('-') ? digits.substring(1) : digits;
|
| - }
|
| -
|
| - /**
|
| - * Format the part after the decimal place in a fixed point number.
|
| - */
|
| - void _formatFractionPart(String fractionPart) {
|
| - var fractionCodes = fractionPart.codeUnits;
|
| - var fractionLength = fractionPart.length;
|
| - while (fractionCodes[fractionLength - 1] == _zero &&
|
| - fractionLength > minimumFractionDigits + 1) {
|
| - fractionLength--;
|
| - }
|
| - for (var i = 1; i < fractionLength; i++) {
|
| - _addDigit(fractionCodes[i]);
|
| - }
|
| - }
|
| -
|
| - /** Print the decimal separator if appropriate. */
|
| - void _decimalSeparator(bool fractionPresent) {
|
| - if (_decimalSeparatorAlwaysShown || fractionPresent) {
|
| - _add(symbols.DECIMAL_SEP);
|
| - }
|
| - }
|
| -
|
| - /**
|
| - * Return true if we have a main integer part which is printable, either
|
| - * because we have digits left of the decimal point (this may include digits
|
| - * which have been moved left because of percent or permille formatting),
|
| - * or because the minimum number of printable digits is greater than 1.
|
| - */
|
| - bool _hasIntegerDigits(String digits) =>
|
| - digits.isNotEmpty || minimumIntegerDigits > 0;
|
| -
|
| - /** A group of methods that provide support for writing digits and other
|
| - * required characters into [_buffer] easily.
|
| - */
|
| - void _add(String x) {
|
| - _buffer.write(x);
|
| - }
|
| - void _addCharCode(int x) {
|
| - _buffer.writeCharCode(x);
|
| - }
|
| - void _addZero() {
|
| - _buffer.write(symbols.ZERO_DIGIT);
|
| - }
|
| - void _addDigit(int x) {
|
| - _buffer.writeCharCode(_localeZero + x - _zero);
|
| - }
|
| -
|
| - /** Print padding up to [numberOfDigits] above what's included in [basic]. */
|
| - void _pad(int numberOfDigits, [String basic = '']) {
|
| - for (var i = 0; i < numberOfDigits - basic.length; i++) {
|
| - _add(symbols.ZERO_DIGIT);
|
| - }
|
| - for (var x in basic.codeUnits) {
|
| - _addDigit(x);
|
| - }
|
| - }
|
| -
|
| - /**
|
| - * We are printing the digits of the number from left to right. We may need
|
| - * to print a thousands separator or other grouping character as appropriate
|
| - * to the locale. So we find how many places we are from the end of the number
|
| - * by subtracting our current [position] from the [totalLength] and printing
|
| - * the separator character every [_groupingSize] digits, with the final
|
| - * grouping possibly being of a different size, [_finalGroupingSize].
|
| - */
|
| - void _group(int totalLength, int position) {
|
| - var distanceFromEnd = totalLength - position;
|
| - if (distanceFromEnd <= 1 || _groupingSize <= 0) return;
|
| - if (distanceFromEnd == _finalGroupingSize + 1) {
|
| - _add(symbols.GROUP_SEP);
|
| - } else if ((distanceFromEnd > _finalGroupingSize) &&
|
| - (distanceFromEnd - _finalGroupingSize) % _groupingSize == 1) {
|
| - _add(symbols.GROUP_SEP);
|
| - }
|
| - }
|
| -
|
| - /** Returns the code point for the character '0'. */
|
| - final _zero = '0'.codeUnits.first;
|
| -
|
| - /** Returns the code point for the locale's zero digit. */
|
| - // Note that there is a slight risk of a locale's zero digit not fitting
|
| - // into a single code unit, but it seems very unlikely, and if it did,
|
| - // there's a pretty good chance that our assumptions about being able to do
|
| - // arithmetic on it would also be invalid.
|
| - get _localeZero => symbols.ZERO_DIGIT.codeUnits.first;
|
| -
|
| - /**
|
| - * Returns the prefix for [x] based on whether it's positive or negative.
|
| - * In en_US this would be '' and '-' respectively.
|
| - */
|
| - String _signPrefix(x) => x.isNegative ? _negativePrefix : _positivePrefix;
|
| -
|
| - /**
|
| - * Returns the suffix for [x] based on wether it's positive or negative.
|
| - * In en_US there are no suffixes for positive or negative.
|
| - */
|
| - String _signSuffix(x) => x.isNegative ? _negativeSuffix : _positiveSuffix;
|
| -
|
| - void _setPattern(String newPattern) {
|
| - if (newPattern == null) return;
|
| - // Make spaces non-breaking
|
| - _pattern = newPattern.replaceAll(' ', '\u00a0');
|
| - var parser = new _NumberFormatParser(this, newPattern, currencyName);
|
| - parser.parse();
|
| - }
|
| -
|
| - String toString() => "NumberFormat($_locale, $_pattern)";
|
| -}
|
| -
|
| -/**
|
| - * A one-time object for parsing a particular numeric string. One-time here
|
| - * means an instance can only parse one string. This is implemented by
|
| - * transforming from a locale-specific format to one that the system can parse,
|
| - * then calls the system parsing methods on it.
|
| - */
|
| -class _NumberParser {
|
| -
|
| - /** The format for which we are parsing. */
|
| - final NumberFormat format;
|
| -
|
| - /** The text we are parsing. */
|
| - final String text;
|
| -
|
| - /** What we use to iterate over the input text. */
|
| - final _Stream input;
|
| -
|
| - /**
|
| - * The result of parsing [text] according to [format]. Automatically
|
| - * populated in the constructor.
|
| - */
|
| - num value;
|
| -
|
| - /** The symbols used by our format. */
|
| - NumberSymbols get symbols => format.symbols;
|
| -
|
| - /** Where we accumulate the normalized representation of the number. */
|
| - final StringBuffer _normalized = new StringBuffer();
|
| -
|
| - /**
|
| - * Did we see something that indicates this is, or at least might be,
|
| - * a positive number.
|
| - */
|
| - bool gotPositive = false;
|
| -
|
| - /**
|
| - * Did we see something that indicates this is, or at least might be,
|
| - * a negative number.
|
| - */
|
| - bool gotNegative = false;
|
| - /**
|
| - * Did we see the required positive suffix at the end. Should
|
| - * match [gotPositive].
|
| - */
|
| - bool gotPositiveSuffix = false;
|
| - /**
|
| - * Did we see the required negative suffix at the end. Should
|
| - * match [gotNegative].
|
| - */
|
| - bool gotNegativeSuffix = false;
|
| -
|
| - /** Should we stop parsing before hitting the end of the string. */
|
| - bool done = false;
|
| -
|
| - /** Have we already skipped over any required prefixes. */
|
| - bool prefixesSkipped = false;
|
| -
|
| - /** If the number is percent or permill, what do we divide by at the end. */
|
| - int scale = 1;
|
| -
|
| - String get _positivePrefix => format._positivePrefix;
|
| - String get _negativePrefix => format._negativePrefix;
|
| - String get _positiveSuffix => format._positiveSuffix;
|
| - String get _negativeSuffix => format._negativeSuffix;
|
| - int get _zero => format._zero;
|
| - int get _localeZero => format._localeZero;
|
| -
|
| - /**
|
| - * Create a new [_NumberParser] on which we can call parse().
|
| - */
|
| - _NumberParser(this.format, text)
|
| - : this.text = text,
|
| - this.input = new _Stream(text) {
|
| - value = parse();
|
| - }
|
| -
|
| - /**
|
| - * The strings we might replace with functions that return the replacement
|
| - * values. They are functions because we might need to check something
|
| - * in the context. Note that the ordering is important here. For example,
|
| - * [symbols.PERCENT] might be " %", and we must handle that before we
|
| - * look at an individual space.
|
| - */
|
| - Map<String, Function> get replacements => _replacements == null
|
| - ? _replacements = _initializeReplacements()
|
| - : _replacements;
|
| -
|
| - var _replacements;
|
| -
|
| - Map _initializeReplacements() => {
|
| - symbols.DECIMAL_SEP: () => '.',
|
| - symbols.EXP_SYMBOL: () => 'E',
|
| - symbols.GROUP_SEP: handleSpace,
|
| - symbols.PERCENT: () {
|
| - scale = _NumberFormatParser._PERCENT_SCALE;
|
| - return '';
|
| - },
|
| - symbols.PERMILL: () {
|
| - scale = _NumberFormatParser._PER_MILLE_SCALE;
|
| - return '';
|
| - },
|
| - ' ': handleSpace,
|
| - '\u00a0': handleSpace,
|
| - '+': () => '+',
|
| - '-': () => '-',
|
| - };
|
| -
|
| - invalidFormat() =>
|
| - throw new FormatException("Invalid number: ${input.contents}");
|
| -
|
| - /**
|
| - * Replace a space in the number with the normalized form. If space is not
|
| - * a significant character (normally grouping) then it's just invalid. If it
|
| - * is the grouping character, then it's only valid if it's followed by a
|
| - * digit. e.g. '$12 345.00'
|
| - */
|
| - handleSpace() =>
|
| - groupingIsNotASpaceOrElseItIsSpaceFollowedByADigit ? '' : invalidFormat();
|
| -
|
| - /**
|
| - * Determine if a space is a valid character in the number. See [handleSpace].
|
| - */
|
| - bool get groupingIsNotASpaceOrElseItIsSpaceFollowedByADigit {
|
| - if (symbols.GROUP_SEP != '\u00a0' || symbols.GROUP_SEP != ' ') return true;
|
| - var peeked = input.peek(symbols.GROUP_SEP.length + 1);
|
| - return asDigit(peeked[peeked.length - 1]) != null;
|
| - }
|
| -
|
| - /**
|
| - * Turn [char] into a number representing a digit, or null if it doesn't
|
| - * represent a digit in this locale.
|
| - */
|
| - int asDigit(String char) {
|
| - var charCode = char.codeUnitAt(0);
|
| - var digitValue = charCode - _localeZero;
|
| - if (digitValue >= 0 && digitValue < 10) {
|
| - return digitValue;
|
| - } else {
|
| - return null;
|
| - }
|
| - }
|
| -
|
| - /**
|
| - * Check to see if the input begins with either the positive or negative
|
| - * prefixes. Set the [gotPositive] and [gotNegative] variables accordingly.
|
| - */
|
| - void checkPrefixes({bool skip: false}) {
|
| - bool checkPrefix(String prefix, skip) {
|
| - var matched = prefix.isNotEmpty && input.startsWith(prefix);
|
| - if (skip && matched) input.read(prefix.length);
|
| - return matched;
|
| - }
|
| -
|
| - // TODO(alanknight): There's a faint possibility of a bug here where
|
| - // a positive prefix is followed by a negative prefix that's also a valid
|
| - // part of the number, but that seems very unlikely.
|
| - if (checkPrefix(_positivePrefix, skip)) gotPositive = true;
|
| - if (checkPrefix(_negativePrefix, skip)) gotNegative = true;
|
| -
|
| - // Copied from Closure. It doesn't seem to be necessary to pass the test
|
| - // suite, so I'm not sure it's really needed.
|
| - if (gotPositive && gotNegative) {
|
| - if (_positivePrefix.length > _negativePrefix.length) {
|
| - gotNegative = false;
|
| - } else if (_negativePrefix.length > _positivePrefix.length) {
|
| - gotPositive = false;
|
| - }
|
| - }
|
| - }
|
| -
|
| - /**
|
| - * If the rest of our input is either the positive or negative suffix,
|
| - * set [gotPositiveSuffix] or [gotNegativeSuffix] accordingly.
|
| - */
|
| - void checkSuffixes() {
|
| - var remainder = input.rest();
|
| - if (remainder == _positiveSuffix) gotPositiveSuffix = true;
|
| - if (remainder == _negativeSuffix) gotNegativeSuffix = true;
|
| - }
|
| -
|
| - /**
|
| - * We've encountered a character that's not a digit. Go through our
|
| - * replacement rules looking for how to handle it. If we see something
|
| - * that's not a digit and doesn't have a replacement, then we're done
|
| - * and the number is probably invalid.
|
| - */
|
| - void processNonDigit() {
|
| - for (var key in replacements.keys) {
|
| - if (input.startsWith(key)) {
|
| - _normalized.write(replacements[key]());
|
| - input.read(key.length);
|
| - return;
|
| - }
|
| - }
|
| - // It might just be a prefix that we haven't skipped. We don't want to
|
| - // skip them initially because they might also be semantically meaningful,
|
| - // e.g. leading %. So we allow them through the loop, but only once.
|
| - if (input.index == 0 && !prefixesSkipped) {
|
| - prefixesSkipped = true;
|
| - checkPrefixes(skip: true);
|
| - } else {
|
| - done = true;
|
| - }
|
| - }
|
| -
|
| - /**
|
| - * Parse [text] and return the resulting number. Throws [FormatException]
|
| - * if we can't parse it.
|
| - */
|
| - num parse() {
|
| - if (text == symbols.NAN) return double.NAN;
|
| - if (text == "$_positivePrefix${symbols.INFINITY}$_positiveSuffix") {
|
| - return double.INFINITY;
|
| - }
|
| - if (text == "$_negativePrefix${symbols.INFINITY}$_negativeSuffix") {
|
| - return double.NEGATIVE_INFINITY;
|
| - }
|
| -
|
| - checkPrefixes();
|
| - var parsed = parseNumber(input);
|
| -
|
| - if (gotPositive && !gotPositiveSuffix) invalidNumber();
|
| - if (gotNegative && !gotNegativeSuffix) invalidNumber();
|
| - if (!input.atEnd()) invalidNumber();
|
| -
|
| - return parsed;
|
| - }
|
| -
|
| - /** The number is invalid, throw a [FormatException]. */
|
| - void invalidNumber() =>
|
| - throw new FormatException("Invalid Number: ${input.contents}");
|
| -
|
| - /**
|
| - * Parse the number portion of the input, i.e. not any prefixes or suffixes,
|
| - * and assuming NaN and Infinity are already handled.
|
| - */
|
| - num parseNumber(_Stream input) {
|
| - while (!done && !input.atEnd()) {
|
| - int digit = asDigit(input.peek());
|
| - if (digit != null) {
|
| - _normalized.writeCharCode(_zero + digit);
|
| - input.next();
|
| - } else {
|
| - processNonDigit();
|
| - }
|
| - checkSuffixes();
|
| - }
|
| -
|
| - var normalizedText = _normalized.toString();
|
| - num parsed = int.parse(normalizedText, onError: (message) => null);
|
| - if (parsed == null) parsed = double.parse(normalizedText);
|
| - return parsed / scale;
|
| - }
|
| -}
|
| -
|
| -/**
|
| - * Private class that parses the numeric formatting pattern and sets the
|
| - * variables in [format] to appropriate values. Instances of this are
|
| - * transient and store parsing state in instance variables, so can only be used
|
| - * to parse a single pattern.
|
| - */
|
| -class _NumberFormatParser {
|
| -
|
| - /**
|
| - * The special characters in the pattern language. All others are treated
|
| - * as literals.
|
| - */
|
| - static const _PATTERN_SEPARATOR = ';';
|
| - static const _QUOTE = "'";
|
| - static const _PATTERN_DIGIT = '#';
|
| - static const _PATTERN_ZERO_DIGIT = '0';
|
| - static const _PATTERN_GROUPING_SEPARATOR = ',';
|
| - static const _PATTERN_DECIMAL_SEPARATOR = '.';
|
| - static const _PATTERN_CURRENCY_SIGN = '\u00A4';
|
| - static const _PATTERN_PER_MILLE = '\u2030';
|
| - static const _PER_MILLE_SCALE = 1000;
|
| - static const _PATTERN_PERCENT = '%';
|
| - static const _PERCENT_SCALE = 100;
|
| - static const _PATTERN_EXPONENT = 'E';
|
| - static const _PATTERN_PLUS = '+';
|
| -
|
| - /** The format whose state we are setting. */
|
| - final NumberFormat format;
|
| -
|
| - /** The pattern we are parsing. */
|
| - final _StringIterator pattern;
|
| -
|
| - /** We can be passed a specific currency symbol, regardless of the locale. */
|
| - String currencyName;
|
| -
|
| - /**
|
| - * Create a new [_NumberFormatParser] for a particular [NumberFormat] and
|
| - * [input] pattern.
|
| - */
|
| - _NumberFormatParser(this.format, input, this.currencyName)
|
| - : pattern = _iterator(input) {
|
| - pattern.moveNext();
|
| - }
|
| -
|
| - /** The [NumberSymbols] for the locale in which our [format] prints. */
|
| - NumberSymbols get symbols => format.symbols;
|
| -
|
| - /** Parse the input pattern and set the values. */
|
| - void parse() {
|
| - format._positivePrefix = _parseAffix();
|
| - var trunk = _parseTrunk();
|
| - format._positiveSuffix = _parseAffix();
|
| - // If we have separate positive and negative patterns, now parse the
|
| - // the negative version.
|
| - if (pattern.current == _NumberFormatParser._PATTERN_SEPARATOR) {
|
| - pattern.moveNext();
|
| - format._negativePrefix = _parseAffix();
|
| - // Skip over the negative trunk, verifying that it's identical to the
|
| - // positive trunk.
|
| - for (var each in _iterable(trunk)) {
|
| - if (pattern.current != each && pattern.current != null) {
|
| - throw new FormatException(
|
| - "Positive and negative trunks must be the same");
|
| - }
|
| - pattern.moveNext();
|
| - }
|
| - format._negativeSuffix = _parseAffix();
|
| - } else {
|
| - // If no negative affix is specified, they share the same positive affix.
|
| - format._negativePrefix = format._negativePrefix + format._positivePrefix;
|
| - format._negativeSuffix = format._positiveSuffix + format._negativeSuffix;
|
| - }
|
| - }
|
| -
|
| - /**
|
| - * Variable used in parsing prefixes and suffixes to keep track of
|
| - * whether or not we are in a quoted region.
|
| - */
|
| - bool inQuote = false;
|
| -
|
| - /**
|
| - * Parse a prefix or suffix and return the prefix/suffix string. Note that
|
| - * this also may modify the state of [format].
|
| - */
|
| - String _parseAffix() {
|
| - var affix = new StringBuffer();
|
| - inQuote = false;
|
| - while (parseCharacterAffix(affix) && pattern.moveNext());
|
| - return affix.toString();
|
| - }
|
| -
|
| - /**
|
| - * Parse an individual character as part of a prefix or suffix. Return true
|
| - * if we should continue to look for more affix characters, and false if
|
| - * we have reached the end.
|
| - */
|
| - bool parseCharacterAffix(StringBuffer affix) {
|
| - var ch = pattern.current;
|
| - if (ch == null) return false;
|
| - if (ch == _QUOTE) {
|
| - if (pattern.peek == _QUOTE) {
|
| - pattern.moveNext();
|
| - affix.write(_QUOTE); // 'don''t'
|
| - } else {
|
| - inQuote = !inQuote;
|
| - }
|
| - return true;
|
| - }
|
| -
|
| - if (inQuote) {
|
| - affix.write(ch);
|
| - } else {
|
| - switch (ch) {
|
| - case _PATTERN_DIGIT:
|
| - case _PATTERN_ZERO_DIGIT:
|
| - case _PATTERN_GROUPING_SEPARATOR:
|
| - case _PATTERN_DECIMAL_SEPARATOR:
|
| - case _PATTERN_SEPARATOR:
|
| - return false;
|
| - case _PATTERN_CURRENCY_SIGN:
|
| - // TODO(alanknight): Handle the local/global/portable currency signs
|
| - affix.write(currencyName);
|
| - break;
|
| - case _PATTERN_PERCENT:
|
| - if (format._multiplier != 1 && format._multiplier != _PERCENT_SCALE) {
|
| - throw new FormatException('Too many percent/permill');
|
| - }
|
| - format._multiplier = _PERCENT_SCALE;
|
| - affix.write(symbols.PERCENT);
|
| - break;
|
| - case _PATTERN_PER_MILLE:
|
| - if (format._multiplier != 1 &&
|
| - format._multiplier != _PER_MILLE_SCALE) {
|
| - throw new FormatException('Too many percent/permill');
|
| - }
|
| - format._multiplier = _PER_MILLE_SCALE;
|
| - affix.write(symbols.PERMILL);
|
| - break;
|
| - default:
|
| - affix.write(ch);
|
| - }
|
| - }
|
| - return true;
|
| - }
|
| -
|
| - /** Variables used in [_parseTrunk] and [parseTrunkCharacter]. */
|
| - var decimalPos = -1;
|
| - var digitLeftCount = 0;
|
| - var zeroDigitCount = 0;
|
| - var digitRightCount = 0;
|
| - var groupingCount = -1;
|
| -
|
| - /**
|
| - * Parse the "trunk" portion of the pattern, the piece that doesn't include
|
| - * positive or negative prefixes or suffixes.
|
| - */
|
| - String _parseTrunk() {
|
| - var loop = true;
|
| - var trunk = new StringBuffer();
|
| - while (pattern.current != null && loop) {
|
| - loop = parseTrunkCharacter(trunk);
|
| - }
|
| -
|
| - if (zeroDigitCount == 0 && digitLeftCount > 0 && decimalPos >= 0) {
|
| - // Handle '###.###' and '###.' and '.###'
|
| - // Handle '.###'
|
| - var n = decimalPos == 0 ? 1 : decimalPos;
|
| - digitRightCount = digitLeftCount - n;
|
| - digitLeftCount = n - 1;
|
| - zeroDigitCount = 1;
|
| - }
|
| -
|
| - // Do syntax checking on the digits.
|
| - if (decimalPos < 0 && digitRightCount > 0 ||
|
| - decimalPos >= 0 &&
|
| - (decimalPos < digitLeftCount ||
|
| - decimalPos > digitLeftCount + zeroDigitCount) ||
|
| - groupingCount == 0) {
|
| - throw new FormatException('Malformed pattern "${pattern.input}"');
|
| - }
|
| - var totalDigits = digitLeftCount + zeroDigitCount + digitRightCount;
|
| -
|
| - format.maximumFractionDigits =
|
| - decimalPos >= 0 ? totalDigits - decimalPos : 0;
|
| - if (decimalPos >= 0) {
|
| - format.minimumFractionDigits =
|
| - digitLeftCount + zeroDigitCount - decimalPos;
|
| - if (format.minimumFractionDigits < 0) {
|
| - format.minimumFractionDigits = 0;
|
| - }
|
| - }
|
| -
|
| - // The effectiveDecimalPos is the position the decimal is at or would be at
|
| - // if there is no decimal. Note that if decimalPos<0, then digitTotalCount
|
| - // == digitLeftCount + zeroDigitCount.
|
| - var effectiveDecimalPos = decimalPos >= 0 ? decimalPos : totalDigits;
|
| - format.minimumIntegerDigits = effectiveDecimalPos - digitLeftCount;
|
| - if (format._useExponentialNotation) {
|
| - format.maximumIntegerDigits =
|
| - digitLeftCount + format.minimumIntegerDigits;
|
| -
|
| - // In exponential display, we need to at least show something.
|
| - if (format.maximumFractionDigits == 0 &&
|
| - format.minimumIntegerDigits == 0) {
|
| - format.minimumIntegerDigits = 1;
|
| - }
|
| - }
|
| -
|
| - format._finalGroupingSize = max(0, groupingCount);
|
| - if (!format._groupingSizeSetExplicitly) {
|
| - format._groupingSize = format._finalGroupingSize;
|
| - }
|
| - format._decimalSeparatorAlwaysShown =
|
| - decimalPos == 0 || decimalPos == totalDigits;
|
| -
|
| - return trunk.toString();
|
| - }
|
| -
|
| - /**
|
| - * Parse an individual character of the trunk. Return true if we should
|
| - * continue to look for additional trunk characters or false if we have
|
| - * reached the end.
|
| - */
|
| - bool parseTrunkCharacter(trunk) {
|
| - var ch = pattern.current;
|
| - switch (ch) {
|
| - case _PATTERN_DIGIT:
|
| - if (zeroDigitCount > 0) {
|
| - digitRightCount++;
|
| - } else {
|
| - digitLeftCount++;
|
| - }
|
| - if (groupingCount >= 0 && decimalPos < 0) {
|
| - groupingCount++;
|
| - }
|
| - break;
|
| - case _PATTERN_ZERO_DIGIT:
|
| - if (digitRightCount > 0) {
|
| - throw new FormatException(
|
| - 'Unexpected "0" in pattern "' + pattern.input + '"');
|
| - }
|
| - zeroDigitCount++;
|
| - if (groupingCount >= 0 && decimalPos < 0) {
|
| - groupingCount++;
|
| - }
|
| - break;
|
| - case _PATTERN_GROUPING_SEPARATOR:
|
| - if (groupingCount > 0) {
|
| - format._groupingSizeSetExplicitly = true;
|
| - format._groupingSize = groupingCount;
|
| - }
|
| - groupingCount = 0;
|
| - break;
|
| - case _PATTERN_DECIMAL_SEPARATOR:
|
| - if (decimalPos >= 0) {
|
| - throw new FormatException(
|
| - 'Multiple decimal separators in pattern "$pattern"');
|
| - }
|
| - decimalPos = digitLeftCount + zeroDigitCount + digitRightCount;
|
| - break;
|
| - case _PATTERN_EXPONENT:
|
| - trunk.write(ch);
|
| - if (format._useExponentialNotation) {
|
| - throw new FormatException(
|
| - 'Multiple exponential symbols in pattern "$pattern"');
|
| - }
|
| - format._useExponentialNotation = true;
|
| - format.minimumExponentDigits = 0;
|
| -
|
| - // exponent pattern can have a optional '+'.
|
| - pattern.moveNext();
|
| - var nextChar = pattern.current;
|
| - if (nextChar == _PATTERN_PLUS) {
|
| - trunk.write(pattern.current);
|
| - pattern.moveNext();
|
| - format._useSignForPositiveExponent = true;
|
| - }
|
| -
|
| - // Use lookahead to parse out the exponential part
|
| - // of the pattern, then jump into phase 2.
|
| - while (pattern.current == _PATTERN_ZERO_DIGIT) {
|
| - trunk.write(pattern.current);
|
| - pattern.moveNext();
|
| - format.minimumExponentDigits++;
|
| - }
|
| -
|
| - if ((digitLeftCount + zeroDigitCount) < 1 ||
|
| - format.minimumExponentDigits < 1) {
|
| - throw new FormatException('Malformed exponential pattern "$pattern"');
|
| - }
|
| - return false;
|
| - default:
|
| - return false;
|
| - }
|
| - trunk.write(ch);
|
| - pattern.moveNext();
|
| - return true;
|
| - }
|
| -}
|
| -
|
| -/**
|
| - * Returns an [Iterable] on the string as a list of substrings.
|
| - */
|
| -Iterable _iterable(String s) => new _StringIterable(s);
|
| -
|
| -/**
|
| - * Return an iterator on the string as a list of substrings.
|
| - */
|
| -Iterator _iterator(String s) => new _StringIterator(s);
|
| -
|
| -// TODO(nweiz): remove this when issue 3780 is fixed.
|
| -/**
|
| - * Provides an Iterable that wraps [_iterator] so it can be used in a `for`
|
| - * loop.
|
| - */
|
| -class _StringIterable extends IterableBase<String> {
|
| - final Iterator<String> iterator;
|
| -
|
| - _StringIterable(String s) : iterator = _iterator(s);
|
| -}
|
| -
|
| -/**
|
| - * Provides an iterator over a string as a list of substrings, and also
|
| - * gives us a lookahead of one via the [peek] method.
|
| - */
|
| -class _StringIterator implements Iterator<String> {
|
| - final String input;
|
| - int nextIndex = 0;
|
| - String _current = null;
|
| -
|
| - _StringIterator(input) : input = _validate(input);
|
| -
|
| - String get current => _current;
|
| -
|
| - bool moveNext() {
|
| - if (nextIndex >= input.length) {
|
| - _current = null;
|
| - return false;
|
| - }
|
| - _current = input[nextIndex++];
|
| - return true;
|
| - }
|
| -
|
| - String get peek => nextIndex >= input.length ? null : input[nextIndex];
|
| -
|
| - Iterator<String> get iterator => this;
|
| -
|
| - static String _validate(input) {
|
| - if (input is! String) throw new ArgumentError(input);
|
| - return input;
|
| - }
|
| -}
|
|
|