1484 lines
		
	
	
		
			48 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			1484 lines
		
	
	
		
			48 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| // Copyright 2006 The Closure Library Authors. All Rights Reserved.
 | |
| //
 | |
| // Licensed 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.
 | |
| 
 | |
| /**
 | |
|  * @fileoverview Number format/parse library with locale support.
 | |
|  */
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * Namespace for locale number format functions
 | |
|  */
 | |
| goog.provide('goog.i18n.NumberFormat');
 | |
| goog.provide('goog.i18n.NumberFormat.CurrencyStyle');
 | |
| goog.provide('goog.i18n.NumberFormat.Format');
 | |
| 
 | |
| goog.require('goog.asserts');
 | |
| goog.require('goog.i18n.CompactNumberFormatSymbols');
 | |
| goog.require('goog.i18n.NumberFormatSymbols');
 | |
| goog.require('goog.i18n.currency');
 | |
| goog.require('goog.math');
 | |
| 
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * Constructor of NumberFormat.
 | |
|  * @param {number|string} pattern The number that indicates a predefined
 | |
|  *     number format pattern.
 | |
|  * @param {string=} opt_currency Optional international currency
 | |
|  *     code. This determines the currency code/symbol used in format/parse. If
 | |
|  *     not given, the currency code for current locale will be used.
 | |
|  * @param {number=} opt_currencyStyle currency style, value defined in
 | |
|  *        goog.i18n.NumberFormat.CurrencyStyle.
 | |
|  * @constructor
 | |
|  */
 | |
| goog.i18n.NumberFormat = function(pattern, opt_currency, opt_currencyStyle) {
 | |
|   /** @private {string} */
 | |
|   this.intlCurrencyCode_ =
 | |
|       opt_currency || goog.i18n.NumberFormatSymbols.DEF_CURRENCY_CODE;
 | |
| 
 | |
|   /** @private {number} */
 | |
|   this.currencyStyle_ =
 | |
|       opt_currencyStyle || goog.i18n.NumberFormat.CurrencyStyle.LOCAL;
 | |
| 
 | |
|   /** @private {number} */
 | |
|   this.maximumIntegerDigits_ = 40;
 | |
|   /** @private {number} */
 | |
|   this.minimumIntegerDigits_ = 1;
 | |
|   /** @private {number} */
 | |
|   this.significantDigits_ = 0;  // invariant, <= maximumFractionDigits
 | |
|   /** @private {number} */
 | |
|   this.maximumFractionDigits_ = 3;  // invariant, >= minFractionDigits
 | |
|   /** @private {number} */
 | |
|   this.minimumFractionDigits_ = 0;
 | |
|   /** @private {number} */
 | |
|   this.minExponentDigits_ = 0;
 | |
|   /** @private {boolean} */
 | |
|   this.useSignForPositiveExponent_ = false;
 | |
| 
 | |
|   /**
 | |
|    * Whether to show trailing zeros in the fraction when significantDigits_ is
 | |
|    * positive.
 | |
|    * @private {boolean}
 | |
|    */
 | |
|   this.showTrailingZeros_ = false;
 | |
| 
 | |
|   /** @private {string} */
 | |
|   this.positivePrefix_ = '';
 | |
|   /** @private {string} */
 | |
|   this.positiveSuffix_ = '';
 | |
|   /** @private {string} */
 | |
|   this.negativePrefix_ = '-';
 | |
|   /** @private {string} */
 | |
|   this.negativeSuffix_ = '';
 | |
| 
 | |
|   // The multiplier for use in percent, per mille, etc.
 | |
|   /** @private {number} */
 | |
|   this.multiplier_ = 1;
 | |
| 
 | |
|   /**
 | |
|    * True if the percent/permill sign of the negative pattern is expected.
 | |
|    * @private {!boolean}
 | |
|    */
 | |
|   this.negativePercentSignExpected_ = false;
 | |
| 
 | |
|   /**
 | |
|    * The grouping array is used to store the values of each number group
 | |
|    * following left of the decimal place. For example, a number group with
 | |
|    * goog.i18n.NumberFormat('#,##,###') should have [3,2] where 2 is the
 | |
|    * repeated number group following a fixed number grouping of size 3.
 | |
|    * @private {!Array<number>}
 | |
|    */
 | |
|   this.groupingArray_ = [];
 | |
| 
 | |
|   /** @private {boolean} */
 | |
|   this.decimalSeparatorAlwaysShown_ = false;
 | |
|   /** @private {boolean} */
 | |
|   this.useExponentialNotation_ = false;
 | |
|   /** @private {goog.i18n.NumberFormat.CompactStyle} */
 | |
|   this.compactStyle_ = goog.i18n.NumberFormat.CompactStyle.NONE;
 | |
| 
 | |
|   /**
 | |
|    * The number to base the formatting on when using compact styles, or null
 | |
|    * if formatting should not be based on another number.
 | |
|    * @type {?number}
 | |
|    * @private
 | |
|    */
 | |
|   this.baseFormattingNumber_ = null;
 | |
| 
 | |
|   /** @private {string} */
 | |
|   this.pattern_;
 | |
| 
 | |
|   if (typeof pattern == 'number') {
 | |
|     this.applyStandardPattern_(pattern);
 | |
|   } else {
 | |
|     this.applyPattern_(pattern);
 | |
|   }
 | |
| };
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * Standard number formatting patterns.
 | |
|  * @enum {number}
 | |
|  */
 | |
| goog.i18n.NumberFormat.Format = {
 | |
|   DECIMAL: 1,
 | |
|   SCIENTIFIC: 2,
 | |
|   PERCENT: 3,
 | |
|   CURRENCY: 4,
 | |
|   COMPACT_SHORT: 5,
 | |
|   COMPACT_LONG: 6
 | |
| };
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * Currency styles.
 | |
|  * @enum {number}
 | |
|  */
 | |
| goog.i18n.NumberFormat.CurrencyStyle = {
 | |
|   LOCAL: 0,     // currency style as it is used in its circulating country.
 | |
|   PORTABLE: 1,  // currency style that differentiate it from other popular ones.
 | |
|   GLOBAL: 2     // currency style that is unique among all currencies.
 | |
| };
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * Compacting styles.
 | |
|  * @enum {number}
 | |
|  */
 | |
| goog.i18n.NumberFormat.CompactStyle = {
 | |
|   NONE: 0,   // Don't compact.
 | |
|   SHORT: 1,  // Short compact form, such as 1.2B.
 | |
|   LONG: 2    // Long compact form, such as 1.2 billion.
 | |
| };
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * If the usage of Ascii digits should be enforced.
 | |
|  * @type {boolean}
 | |
|  * @private
 | |
|  */
 | |
| goog.i18n.NumberFormat.enforceAsciiDigits_ = false;
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * Set if the usage of Ascii digits in formatting should be enforced.
 | |
|  * @param {boolean} doEnforce Boolean value about if Ascii digits should be
 | |
|  *     enforced.
 | |
|  */
 | |
| goog.i18n.NumberFormat.setEnforceAsciiDigits = function(doEnforce) {
 | |
|   goog.i18n.NumberFormat.enforceAsciiDigits_ = doEnforce;
 | |
| };
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * Return if Ascii digits is enforced.
 | |
|  * @return {boolean} If Ascii digits is enforced.
 | |
|  */
 | |
| goog.i18n.NumberFormat.isEnforceAsciiDigits = function() {
 | |
|   return goog.i18n.NumberFormat.enforceAsciiDigits_;
 | |
| };
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * Sets minimum number of fraction digits.
 | |
|  * @param {number} min the minimum.
 | |
|  * @return {!goog.i18n.NumberFormat} Reference to this NumberFormat object.
 | |
|  */
 | |
| goog.i18n.NumberFormat.prototype.setMinimumFractionDigits = function(min) {
 | |
|   if (this.significantDigits_ > 0 && min > 0) {
 | |
|     throw Error(
 | |
|         'Can\'t combine significant digits and minimum fraction digits');
 | |
|   }
 | |
|   this.minimumFractionDigits_ = min;
 | |
|   return this;
 | |
| };
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * Sets maximum number of fraction digits.
 | |
|  * @param {number} max the maximum.
 | |
|  * @return {!goog.i18n.NumberFormat} Reference to this NumberFormat object.
 | |
|  */
 | |
| goog.i18n.NumberFormat.prototype.setMaximumFractionDigits = function(max) {
 | |
|   this.maximumFractionDigits_ = max;
 | |
|   return this;
 | |
| };
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * Sets number of significant digits to show. Only fractions will be rounded.
 | |
|  * @param {number} number The number of significant digits to include.
 | |
|  * @return {!goog.i18n.NumberFormat} Reference to this NumberFormat object.
 | |
|  */
 | |
| goog.i18n.NumberFormat.prototype.setSignificantDigits = function(number) {
 | |
|   if (this.minimumFractionDigits_ > 0 && number >= 0) {
 | |
|     throw Error(
 | |
|         'Can\'t combine significant digits and minimum fraction digits');
 | |
|   }
 | |
|   this.significantDigits_ = number;
 | |
|   return this;
 | |
| };
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * Gets number of significant digits to show. Only fractions will be rounded.
 | |
|  * @return {number} The number of significant digits to include.
 | |
|  */
 | |
| goog.i18n.NumberFormat.prototype.getSignificantDigits = function() {
 | |
|   return this.significantDigits_;
 | |
| };
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * Sets whether trailing fraction zeros should be shown when significantDigits_
 | |
|  * is positive. If this is true and significantDigits_ is 2, 1 will be formatted
 | |
|  * as '1.0'.
 | |
|  * @param {boolean} showTrailingZeros Whether trailing zeros should be shown.
 | |
|  * @return {!goog.i18n.NumberFormat} Reference to this NumberFormat object.
 | |
|  */
 | |
| goog.i18n.NumberFormat.prototype.setShowTrailingZeros = function(
 | |
|     showTrailingZeros) {
 | |
|   this.showTrailingZeros_ = showTrailingZeros;
 | |
|   return this;
 | |
| };
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * Sets a number to base the formatting on when compact style formatting is
 | |
|  * used. If this is null, the formatting should be based only on the number to
 | |
|  * be formatting.
 | |
|  *
 | |
|  * This base formatting number can be used to format the target number as
 | |
|  * another number would be formatted. For example, 100,000 is normally formatted
 | |
|  * as "100K" in the COMPACT_SHORT format. To instead format it as '0.1M', the
 | |
|  * base number could be set to 1,000,000 in order to force all numbers to be
 | |
|  * formatted in millions. Similarly, 1,000,000,000 would normally be formatted
 | |
|  * as '1B' and setting the base formatting number to 1,000,000, would cause it
 | |
|  * to be formatted instead as '1,000M'.
 | |
|  *
 | |
|  * @param {?number} baseFormattingNumber The number to base formatting on, or
 | |
|  * null if formatting should not be based on another number.
 | |
|  * @return {!goog.i18n.NumberFormat} Reference to this NumberFormat object.
 | |
|  */
 | |
| goog.i18n.NumberFormat.prototype.setBaseFormatting = function(
 | |
|     baseFormattingNumber) {
 | |
|   goog.asserts.assert(
 | |
|       goog.isNull(baseFormattingNumber) || isFinite(baseFormattingNumber));
 | |
|   this.baseFormattingNumber_ = baseFormattingNumber;
 | |
|   return this;
 | |
| };
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * Gets the number on which compact formatting is currently based, or null if
 | |
|  * no such number is set. See setBaseFormatting() for more information.
 | |
|  * @return {?number}
 | |
|  */
 | |
| goog.i18n.NumberFormat.prototype.getBaseFormatting = function() {
 | |
|   return this.baseFormattingNumber_;
 | |
| };
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * Apply provided pattern, result are stored in member variables.
 | |
|  *
 | |
|  * @param {string} pattern String pattern being applied.
 | |
|  * @private
 | |
|  */
 | |
| goog.i18n.NumberFormat.prototype.applyPattern_ = function(pattern) {
 | |
|   this.pattern_ = pattern.replace(/ /g, '\u00a0');
 | |
|   var pos = [0];
 | |
| 
 | |
|   this.positivePrefix_ = this.parseAffix_(pattern, pos);
 | |
|   var trunkStart = pos[0];
 | |
|   this.parseTrunk_(pattern, pos);
 | |
|   var trunkLen = pos[0] - trunkStart;
 | |
|   this.positiveSuffix_ = this.parseAffix_(pattern, pos);
 | |
|   if (pos[0] < pattern.length &&
 | |
|       pattern.charAt(pos[0]) == goog.i18n.NumberFormat.PATTERN_SEPARATOR_) {
 | |
|     pos[0]++;
 | |
|     if (this.multiplier_ != 1) this.negativePercentSignExpected_ = true;
 | |
|     this.negativePrefix_ = this.parseAffix_(pattern, pos);
 | |
|     // we assume this part is identical to positive part.
 | |
|     // user must make sure the pattern is correctly constructed.
 | |
|     pos[0] += trunkLen;
 | |
|     this.negativeSuffix_ = this.parseAffix_(pattern, pos);
 | |
|   } else {
 | |
|     // if no negative affix specified, they share the same positive affix
 | |
|     this.negativePrefix_ = this.positivePrefix_ + this.negativePrefix_;
 | |
|     this.negativeSuffix_ += this.positiveSuffix_;
 | |
|   }
 | |
| };
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * Apply a predefined pattern to NumberFormat object.
 | |
|  * @param {number} patternType The number that indicates a predefined number
 | |
|  *     format pattern.
 | |
|  * @private
 | |
|  */
 | |
| goog.i18n.NumberFormat.prototype.applyStandardPattern_ = function(patternType) {
 | |
|   switch (patternType) {
 | |
|     case goog.i18n.NumberFormat.Format.DECIMAL:
 | |
|       this.applyPattern_(goog.i18n.NumberFormatSymbols.DECIMAL_PATTERN);
 | |
|       break;
 | |
|     case goog.i18n.NumberFormat.Format.SCIENTIFIC:
 | |
|       this.applyPattern_(goog.i18n.NumberFormatSymbols.SCIENTIFIC_PATTERN);
 | |
|       break;
 | |
|     case goog.i18n.NumberFormat.Format.PERCENT:
 | |
|       this.applyPattern_(goog.i18n.NumberFormatSymbols.PERCENT_PATTERN);
 | |
|       break;
 | |
|     case goog.i18n.NumberFormat.Format.CURRENCY:
 | |
|       this.applyPattern_(
 | |
|           goog.i18n.currency.adjustPrecision(
 | |
|               goog.i18n.NumberFormatSymbols.CURRENCY_PATTERN,
 | |
|               this.intlCurrencyCode_));
 | |
|       break;
 | |
|     case goog.i18n.NumberFormat.Format.COMPACT_SHORT:
 | |
|       this.applyCompactStyle_(goog.i18n.NumberFormat.CompactStyle.SHORT);
 | |
|       break;
 | |
|     case goog.i18n.NumberFormat.Format.COMPACT_LONG:
 | |
|       this.applyCompactStyle_(goog.i18n.NumberFormat.CompactStyle.LONG);
 | |
|       break;
 | |
|     default:
 | |
|       throw Error('Unsupported pattern type.');
 | |
|   }
 | |
| };
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * Apply a predefined pattern for shorthand formats.
 | |
|  * @param {goog.i18n.NumberFormat.CompactStyle} style the compact style to
 | |
|  *     set defaults for.
 | |
|  * @private
 | |
|  */
 | |
| goog.i18n.NumberFormat.prototype.applyCompactStyle_ = function(style) {
 | |
|   this.compactStyle_ = style;
 | |
|   this.applyPattern_(goog.i18n.NumberFormatSymbols.DECIMAL_PATTERN);
 | |
|   this.setMinimumFractionDigits(0);
 | |
|   this.setMaximumFractionDigits(2);
 | |
|   this.setSignificantDigits(2);
 | |
| };
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * Parses text string to produce a Number.
 | |
|  *
 | |
|  * This method attempts to parse text starting from position "opt_pos" if it
 | |
|  * is given. Otherwise the parse will start from the beginning of the text.
 | |
|  * When opt_pos presents, opt_pos will be updated to the character next to where
 | |
|  * parsing stops after the call. If an error occurs, opt_pos won't be updated.
 | |
|  *
 | |
|  * @param {string} text The string to be parsed.
 | |
|  * @param {Array<number>=} opt_pos Position to pass in and get back.
 | |
|  * @return {number} Parsed number. This throws an error if the text cannot be
 | |
|  *     parsed.
 | |
|  */
 | |
| goog.i18n.NumberFormat.prototype.parse = function(text, opt_pos) {
 | |
|   var pos = opt_pos || [0];
 | |
| 
 | |
|   if (this.compactStyle_ != goog.i18n.NumberFormat.CompactStyle.NONE) {
 | |
|     throw Error('Parsing of compact numbers is unimplemented');
 | |
|   }
 | |
| 
 | |
|   var ret = NaN;
 | |
| 
 | |
|   // we don't want to handle 2 kind of space in parsing, normalize it to nbsp
 | |
|   text = text.replace(/ /g, '\u00a0');
 | |
| 
 | |
|   var gotPositive = text.indexOf(this.positivePrefix_, pos[0]) == pos[0];
 | |
|   var gotNegative = text.indexOf(this.negativePrefix_, pos[0]) == pos[0];
 | |
| 
 | |
|   // check for the longest match
 | |
|   if (gotPositive && gotNegative) {
 | |
|     if (this.positivePrefix_.length > this.negativePrefix_.length) {
 | |
|       gotNegative = false;
 | |
|     } else if (this.positivePrefix_.length < this.negativePrefix_.length) {
 | |
|       gotPositive = false;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (gotPositive) {
 | |
|     pos[0] += this.positivePrefix_.length;
 | |
|   } else if (gotNegative) {
 | |
|     pos[0] += this.negativePrefix_.length;
 | |
|   }
 | |
| 
 | |
|   // process digits or Inf, find decimal position
 | |
|   if (text.indexOf(goog.i18n.NumberFormatSymbols.INFINITY, pos[0]) == pos[0]) {
 | |
|     pos[0] += goog.i18n.NumberFormatSymbols.INFINITY.length;
 | |
|     ret = Infinity;
 | |
|   } else {
 | |
|     ret = this.parseNumber_(text, pos);
 | |
|   }
 | |
| 
 | |
|   // check for suffix
 | |
|   if (gotPositive) {
 | |
|     if (!(text.indexOf(this.positiveSuffix_, pos[0]) == pos[0])) {
 | |
|       return NaN;
 | |
|     }
 | |
|     pos[0] += this.positiveSuffix_.length;
 | |
|   } else if (gotNegative) {
 | |
|     if (!(text.indexOf(this.negativeSuffix_, pos[0]) == pos[0])) {
 | |
|       return NaN;
 | |
|     }
 | |
|     pos[0] += this.negativeSuffix_.length;
 | |
|   }
 | |
| 
 | |
|   return gotNegative ? -ret : ret;
 | |
| };
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * This function will parse a "localized" text into a Number. It needs to
 | |
|  * handle locale specific decimal, grouping, exponent and digits.
 | |
|  *
 | |
|  * @param {string} text The text that need to be parsed.
 | |
|  * @param {Array<number>} pos  In/out parsing position. In case of failure,
 | |
|  *    pos value won't be changed.
 | |
|  * @return {number} Number value, or NaN if nothing can be parsed.
 | |
|  * @private
 | |
|  */
 | |
| goog.i18n.NumberFormat.prototype.parseNumber_ = function(text, pos) {
 | |
|   var sawDecimal = false;
 | |
|   var sawExponent = false;
 | |
|   var sawDigit = false;
 | |
|   var scale = 1;
 | |
|   var decimal = goog.i18n.NumberFormatSymbols.DECIMAL_SEP;
 | |
|   var grouping = goog.i18n.NumberFormatSymbols.GROUP_SEP;
 | |
|   var exponentChar = goog.i18n.NumberFormatSymbols.EXP_SYMBOL;
 | |
| 
 | |
|   if (this.compactStyle_ != goog.i18n.NumberFormat.CompactStyle.NONE) {
 | |
|     throw Error('Parsing of compact style numbers is not implemented');
 | |
|   }
 | |
| 
 | |
|   var normalizedText = '';
 | |
|   for (; pos[0] < text.length; pos[0]++) {
 | |
|     var ch = text.charAt(pos[0]);
 | |
|     var digit = this.getDigit_(ch);
 | |
|     if (digit >= 0 && digit <= 9) {
 | |
|       normalizedText += digit;
 | |
|       sawDigit = true;
 | |
|     } else if (ch == decimal.charAt(0)) {
 | |
|       if (sawDecimal || sawExponent) {
 | |
|         break;
 | |
|       }
 | |
|       normalizedText += '.';
 | |
|       sawDecimal = true;
 | |
|     } else if (
 | |
|         ch == grouping.charAt(0) &&
 | |
|         ('\u00a0' != grouping.charAt(0) ||
 | |
|          pos[0] + 1 < text.length &&
 | |
|              this.getDigit_(text.charAt(pos[0] + 1)) >= 0)) {
 | |
|       // Got a grouping character here. When grouping character is nbsp, need
 | |
|       // to make sure the character following it is a digit.
 | |
|       if (sawDecimal || sawExponent) {
 | |
|         break;
 | |
|       }
 | |
|       continue;
 | |
|     } else if (ch == exponentChar.charAt(0)) {
 | |
|       if (sawExponent) {
 | |
|         break;
 | |
|       }
 | |
|       normalizedText += 'E';
 | |
|       sawExponent = true;
 | |
|     } else if (ch == '+' || ch == '-') {
 | |
|       normalizedText += ch;
 | |
|     } else if (
 | |
|         this.multiplier_ == 1 &&
 | |
|         ch == goog.i18n.NumberFormatSymbols.PERCENT.charAt(0)) {
 | |
|       // Parse the percent character as part of the number only when it's
 | |
|       // not already included in the pattern.
 | |
|       if (scale != 1) {
 | |
|         break;
 | |
|       }
 | |
|       scale = 100;
 | |
|       if (sawDigit) {
 | |
|         pos[0]++;  // eat this character if parse end here
 | |
|         break;
 | |
|       }
 | |
|     } else if (
 | |
|         this.multiplier_ == 1 &&
 | |
|         ch == goog.i18n.NumberFormatSymbols.PERMILL.charAt(0)) {
 | |
|       // Parse the permill character as part of the number only when it's
 | |
|       // not already included in the pattern.
 | |
|       if (scale != 1) {
 | |
|         break;
 | |
|       }
 | |
|       scale = 1000;
 | |
|       if (sawDigit) {
 | |
|         pos[0]++;  // eat this character if parse end here
 | |
|         break;
 | |
|       }
 | |
|     } else {
 | |
|       break;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // Scale the number when the percent/permill character was included in
 | |
|   // the pattern.
 | |
|   if (this.multiplier_ != 1) {
 | |
|     scale = this.multiplier_;
 | |
|   }
 | |
| 
 | |
|   return parseFloat(normalizedText) / scale;
 | |
| };
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * Formats a Number to produce a string.
 | |
|  *
 | |
|  * @param {number} number The Number to be formatted.
 | |
|  * @return {string} The formatted number string.
 | |
|  */
 | |
| goog.i18n.NumberFormat.prototype.format = function(number) {
 | |
|   if (isNaN(number)) {
 | |
|     return goog.i18n.NumberFormatSymbols.NAN;
 | |
|   }
 | |
| 
 | |
|   var parts = [];
 | |
|   var baseFormattingNumber = goog.isNull(this.baseFormattingNumber_) ?
 | |
|       number :
 | |
|       this.baseFormattingNumber_;
 | |
|   var unit = this.getUnitAfterRounding_(baseFormattingNumber, number);
 | |
|   number /= Math.pow(10, unit.divisorBase);
 | |
| 
 | |
|   parts.push(unit.prefix);
 | |
| 
 | |
|   // in icu code, it is commented that certain computation need to keep the
 | |
|   // negative sign for 0.
 | |
|   var isNegative = number < 0.0 || number == 0.0 && 1 / number < 0.0;
 | |
| 
 | |
|   parts.push(isNegative ? this.negativePrefix_ : this.positivePrefix_);
 | |
| 
 | |
|   if (!isFinite(number)) {
 | |
|     parts.push(goog.i18n.NumberFormatSymbols.INFINITY);
 | |
|   } else {
 | |
|     // convert number to non-negative value
 | |
|     number *= isNegative ? -1 : 1;
 | |
| 
 | |
|     number *= this.multiplier_;
 | |
|     this.useExponentialNotation_ ?
 | |
|         this.subformatExponential_(number, parts) :
 | |
|         this.subformatFixed_(number, this.minimumIntegerDigits_, parts);
 | |
|   }
 | |
| 
 | |
|   parts.push(isNegative ? this.negativeSuffix_ : this.positiveSuffix_);
 | |
|   parts.push(unit.suffix);
 | |
| 
 | |
|   return parts.join('');
 | |
| };
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * Round a number into an integer and fractional part
 | |
|  * based on the rounding rules for this NumberFormat.
 | |
|  * @param {number} number The number to round.
 | |
|  * @return {{intValue: number, fracValue: number}} The integer and fractional
 | |
|  *     part after rounding.
 | |
|  * @private
 | |
|  */
 | |
| goog.i18n.NumberFormat.prototype.roundNumber_ = function(number) {
 | |
|   var power = Math.pow(10, this.maximumFractionDigits_);
 | |
|   var shiftedNumber = this.significantDigits_ <= 0 ?
 | |
|       Math.round(number * power) :
 | |
|       Math.round(
 | |
|           this.roundToSignificantDigits_(
 | |
|               number * power, this.significantDigits_,
 | |
|               this.maximumFractionDigits_));
 | |
| 
 | |
|   var intValue, fracValue;
 | |
|   if (isFinite(shiftedNumber)) {
 | |
|     intValue = Math.floor(shiftedNumber / power);
 | |
|     fracValue = Math.floor(shiftedNumber - intValue * power);
 | |
|   } else {
 | |
|     intValue = number;
 | |
|     fracValue = 0;
 | |
|   }
 | |
|   return {intValue: intValue, fracValue: fracValue};
 | |
| };
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * Formats a number with the appropriate groupings when there are repeating
 | |
|  * digits present. Repeating digits exists when the length of the digits left
 | |
|  * of the decimal place exceeds the number of non-repeating digits.
 | |
|  *
 | |
|  * Formats a number by iterating through the integer number (intPart) from the
 | |
|  * most left of the decimal place by inserting the appropriate number grouping
 | |
|  * separator for the repeating digits until all of the repeating digits is
 | |
|  * iterated. Then iterate through the non-repeating digits by inserting the
 | |
|  * appropriate number grouping separator until all the non-repeating digits
 | |
|  * is iterated through.
 | |
|  *
 | |
|  * In the number grouping concept, anything left of the decimal
 | |
|  * place is followed by non-repeating digits and then repeating digits. If the
 | |
|  * pattern is #,##,###, then we first (from the left of the decimal place) have
 | |
|  * a non-repeating digit of size 3 followed by repeating digits of size 2
 | |
|  * separated by a thousand separator. If the length of the digits are six or
 | |
|  * more, there may be repeating digits required. For example, the value of
 | |
|  * 12345678 would format as 1,23,45,678 where the repeating digit is length 2.
 | |
|  *
 | |
|  * @param {!Array<string>} parts An array to build the 'parts' of the formatted
 | |
|  *  number including the values and separators.
 | |
|  * @param {number} zeroCode The value of the zero digit whether or not
 | |
|  *  goog.i18n.NumberFormat.enforceAsciiDigits_ is enforced.
 | |
|  * @param {string} intPart The integer representation of the number to be
 | |
|  *  formatted and referenced.
 | |
|  * @param {!Array<number>} groupingArray The array of numbers to determine the
 | |
|  *  grouping of repeated and non-repeated digits.
 | |
|  * @param {number} repeatedDigitLen The length of the repeated digits left of
 | |
|  *  the non-repeating digits left of the decimal.
 | |
|  * @return {!Array<string>} Returns the resulting parts variable containing
 | |
|  *  how numbers are to be grouped and appear.
 | |
|  * @private
 | |
|  */
 | |
| goog.i18n.NumberFormat.formatNumberGroupingRepeatingDigitsParts_ = function(
 | |
|     parts, zeroCode, intPart, groupingArray, repeatedDigitLen) {
 | |
|   // Keep track of how much has been completed on the non repeated groups
 | |
|   var nonRepeatedGroupCompleteCount = 0;
 | |
|   var currentGroupSizeIndex = 0;
 | |
|   var currentGroupSize = 0;
 | |
| 
 | |
|   var grouping = goog.i18n.NumberFormatSymbols.GROUP_SEP;
 | |
|   var digitLen = intPart.length;
 | |
| 
 | |
|   // There are repeating digits and non-repeating digits
 | |
|   for (var i = 0; i < digitLen; i++) {
 | |
|     parts.push(String.fromCharCode(zeroCode + Number(intPart.charAt(i)) * 1));
 | |
|     if (digitLen - i > 1) {
 | |
|       currentGroupSize = groupingArray[currentGroupSizeIndex];
 | |
|       if (i < repeatedDigitLen) {
 | |
|         // Process the left side (the repeated number groups)
 | |
|         var repeatedDigitIndex = repeatedDigitLen - i;
 | |
|         // Edge case if there's a number grouping asking for "1" group at
 | |
|         // a time; otherwise, if the remainder is 1, there's the separator
 | |
|         if (currentGroupSize === 1 ||
 | |
|             (currentGroupSize > 0 &&
 | |
|              (repeatedDigitIndex % currentGroupSize) === 1)) {
 | |
|           parts.push(grouping);
 | |
|         }
 | |
|       } else if (currentGroupSizeIndex < groupingArray.length) {
 | |
|         // Process the right side (the non-repeated fixed number groups)
 | |
|         if (i === repeatedDigitLen) {
 | |
|           // Increase the group index because a separator
 | |
|           // has previously added in the earlier logic
 | |
|           currentGroupSizeIndex += 1;
 | |
|         } else if (
 | |
|             currentGroupSize ===
 | |
|             i - repeatedDigitLen - nonRepeatedGroupCompleteCount + 1) {
 | |
|           // Otherwise, just iterate to the right side and
 | |
|           // add a separator once the length matches to the expected
 | |
|           parts.push(grouping);
 | |
|           // Keep track of what has been completed on the right
 | |
|           nonRepeatedGroupCompleteCount += currentGroupSize;
 | |
|           currentGroupSizeIndex += 1;  // Get to the next number grouping
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   return parts;
 | |
| };
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * Formats a number with the appropriate groupings when there are no repeating
 | |
|  * digits present. Non-repeating digits exists when the length of the digits
 | |
|  * left of the decimal place is equal or lesser than the length of
 | |
|  * non-repeating digits.
 | |
|  *
 | |
|  * Formats a number by iterating through the integer number (intPart) from the
 | |
|  * right most non-repeating number group of the decimal place. For each group,
 | |
|  * inserting the appropriate number grouping separator for the non-repeating
 | |
|  * digits until the number is completely iterated.
 | |
|  *
 | |
|  * In the number grouping concept, anything left of the decimal
 | |
|  * place is followed by non-repeating digits and then repeating digits. If the
 | |
|  * pattern is #,##,###, then we first (from the left of the decimal place) have
 | |
|  * a non-repeating digit of size 3 followed by repeating digits of size 2
 | |
|  * separated by a thousand separator. If the length of the digits are five or
 | |
|  * less, there won't be any repeating digits required. For example, the value
 | |
|  * of 12345 would be formatted as 12,345 where the non-repeating digit is of
 | |
|  * length 3.
 | |
|  *
 | |
|  * @param {!Array<string>} parts An array to build the 'parts' of the formatted
 | |
|  *  number including the values and separators.
 | |
|  * @param {number} zeroCode The value of the zero digit whether or not
 | |
|  *  goog.i18n.NumberFormat.enforceAsciiDigits_ is enforced.
 | |
|  * @param {string} intPart The integer representation of the number to be
 | |
|  *  formatted and referenced.
 | |
|  * @param {!Array<number>} groupingArray The array of numbers to determine the
 | |
|  *  grouping of repeated and non-repeated digits.
 | |
|  * @return {!Array<string>} Returns the resulting parts variable containing
 | |
|  *  how numbers are to be grouped and appear.
 | |
|  * @private
 | |
|  */
 | |
| goog.i18n.NumberFormat.formatNumberGroupingNonRepeatingDigitsParts_ = function(
 | |
|     parts, zeroCode, intPart, groupingArray) {
 | |
|   // Keep track of how much has been completed on the non repeated groups
 | |
|   var grouping = goog.i18n.NumberFormatSymbols.GROUP_SEP;
 | |
|   var currentGroupSizeIndex;
 | |
|   var currentGroupSize = 0;
 | |
|   var digitLenLeft = intPart.length;
 | |
|   var rightToLeftParts = [];
 | |
| 
 | |
|   // Start from the right most non-repeating group and work inwards
 | |
|   for (currentGroupSizeIndex = groupingArray.length - 1;
 | |
|        currentGroupSizeIndex >= 0 && digitLenLeft > 0;
 | |
|        currentGroupSizeIndex--) {
 | |
|     currentGroupSize = groupingArray[currentGroupSizeIndex];
 | |
|     // Iterate from the right most digit
 | |
|     for (var rightDigitIndex = 0; rightDigitIndex < currentGroupSize &&
 | |
|          ((digitLenLeft - rightDigitIndex - 1) >= 0);
 | |
|          rightDigitIndex++) {
 | |
|       rightToLeftParts.push(
 | |
|           String.fromCharCode(
 | |
|               zeroCode +
 | |
|               Number(intPart.charAt(digitLenLeft - rightDigitIndex - 1)) * 1));
 | |
|     }
 | |
|     // Update the number of digits left
 | |
|     digitLenLeft -= currentGroupSize;
 | |
|     if (digitLenLeft > 0) {
 | |
|       rightToLeftParts.push(grouping);
 | |
|     }
 | |
|   }
 | |
|   // Reverse and push onto the remaining parts
 | |
|   parts.push.apply(parts, rightToLeftParts.reverse());
 | |
| 
 | |
|   return parts;
 | |
| };
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * Formats a Number in fraction format.
 | |
|  *
 | |
|  * @param {number} number
 | |
|  * @param {number} minIntDigits Minimum integer digits.
 | |
|  * @param {Array<string>} parts
 | |
|  *     This array holds the pieces of formatted string.
 | |
|  *     This function will add its formatted pieces to the array.
 | |
|  * @private
 | |
|  */
 | |
| goog.i18n.NumberFormat.prototype.subformatFixed_ = function(
 | |
|     number, minIntDigits, parts) {
 | |
|   if (this.minimumFractionDigits_ > this.maximumFractionDigits_) {
 | |
|     throw Error('Min value must be less than max value');
 | |
|   }
 | |
| 
 | |
|   if (!parts) {
 | |
|     parts = [];
 | |
|   }
 | |
| 
 | |
|   var rounded = this.roundNumber_(number);
 | |
|   var power = Math.pow(10, this.maximumFractionDigits_);
 | |
|   var intValue = rounded.intValue;
 | |
|   var fracValue = rounded.fracValue;
 | |
| 
 | |
|   var numIntDigits = (intValue == 0) ? 0 : this.intLog10_(intValue) + 1;
 | |
|   var fractionPresent = this.minimumFractionDigits_ > 0 || fracValue > 0 ||
 | |
|       (this.showTrailingZeros_ && numIntDigits < this.significantDigits_);
 | |
|   var minimumFractionDigits = this.minimumFractionDigits_;
 | |
|   if (fractionPresent) {
 | |
|     if (this.showTrailingZeros_ && this.significantDigits_ > 0) {
 | |
|       minimumFractionDigits = this.significantDigits_ - numIntDigits;
 | |
|     } else {
 | |
|       minimumFractionDigits = this.minimumFractionDigits_;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   var intPart = '';
 | |
|   var translatableInt = intValue;
 | |
|   while (translatableInt > 1E20) {
 | |
|     // here it goes beyond double precision, add '0' make it look better
 | |
|     intPart = '0' + intPart;
 | |
|     translatableInt = Math.round(translatableInt / 10);
 | |
|   }
 | |
|   intPart = translatableInt + intPart;
 | |
| 
 | |
|   var decimal = goog.i18n.NumberFormatSymbols.DECIMAL_SEP;
 | |
|   var zeroCode = goog.i18n.NumberFormat.enforceAsciiDigits_ ?
 | |
|       48 /* ascii '0' */ :
 | |
|       goog.i18n.NumberFormatSymbols.ZERO_DIGIT.charCodeAt(0);
 | |
|   var digitLen = intPart.length;
 | |
|   var nonRepeatedGroupCount = 0;
 | |
| 
 | |
|   if (intValue > 0 || minIntDigits > 0) {
 | |
|     for (var i = digitLen; i < minIntDigits; i++) {
 | |
|       parts.push(String.fromCharCode(zeroCode));
 | |
|     }
 | |
| 
 | |
|     // If there's more than 1 number grouping,
 | |
|     // figure out the length of the non-repeated groupings (on the right)
 | |
|     if (this.groupingArray_.length >= 2) {
 | |
|       for (var j = 1; j < this.groupingArray_.length; j++) {
 | |
|         nonRepeatedGroupCount += this.groupingArray_[j];
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     // Anything left of the fixed number grouping is repeated,
 | |
|     // figure out the length of repeated groupings (on the left)
 | |
|     var repeatedDigitLen = digitLen - nonRepeatedGroupCount;
 | |
|     if (repeatedDigitLen > 0) {
 | |
|       // There are repeating digits and non-repeating digits
 | |
|       parts = goog.i18n.NumberFormat.formatNumberGroupingRepeatingDigitsParts_(
 | |
|           parts, zeroCode, intPart, this.groupingArray_, repeatedDigitLen);
 | |
|     } else {
 | |
|       // There are no repeating digits and only non-repeating digits
 | |
|       parts =
 | |
|           goog.i18n.NumberFormat.formatNumberGroupingNonRepeatingDigitsParts_(
 | |
|               parts, zeroCode, intPart, this.groupingArray_);
 | |
|     }
 | |
|   } else if (!fractionPresent) {
 | |
|     // If there is no fraction present, and we haven't printed any
 | |
|     // integer digits, then print a zero.
 | |
|     parts.push(String.fromCharCode(zeroCode));
 | |
|   }
 | |
| 
 | |
|   // Output the decimal separator if we always do so.
 | |
|   if (this.decimalSeparatorAlwaysShown_ || fractionPresent) {
 | |
|     parts.push(decimal);
 | |
|   }
 | |
| 
 | |
|   var fracPart = '' + (fracValue + power);
 | |
|   var fracLen = fracPart.length;
 | |
|   while (fracPart.charAt(fracLen - 1) == '0' &&
 | |
|          fracLen > minimumFractionDigits + 1) {
 | |
|     fracLen--;
 | |
|   }
 | |
| 
 | |
|   for (var i = 1; i < fracLen; i++) {
 | |
|     parts.push(String.fromCharCode(zeroCode + Number(fracPart.charAt(i)) * 1));
 | |
|   }
 | |
| };
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * Formats exponent part of a Number.
 | |
|  *
 | |
|  * @param {number} exponent Exponential value.
 | |
|  * @param {Array<string>} parts The array that holds the pieces of formatted
 | |
|  *     string. This function will append more formatted pieces to the array.
 | |
|  * @private
 | |
|  */
 | |
| goog.i18n.NumberFormat.prototype.addExponentPart_ = function(exponent, parts) {
 | |
|   parts.push(goog.i18n.NumberFormatSymbols.EXP_SYMBOL);
 | |
| 
 | |
|   if (exponent < 0) {
 | |
|     exponent = -exponent;
 | |
|     parts.push(goog.i18n.NumberFormatSymbols.MINUS_SIGN);
 | |
|   } else if (this.useSignForPositiveExponent_) {
 | |
|     parts.push(goog.i18n.NumberFormatSymbols.PLUS_SIGN);
 | |
|   }
 | |
| 
 | |
|   var exponentDigits = '' + exponent;
 | |
|   var zeroChar = goog.i18n.NumberFormat.enforceAsciiDigits_ ?
 | |
|       '0' :
 | |
|       goog.i18n.NumberFormatSymbols.ZERO_DIGIT;
 | |
|   for (var i = exponentDigits.length; i < this.minExponentDigits_; i++) {
 | |
|     parts.push(zeroChar);
 | |
|   }
 | |
|   parts.push(exponentDigits);
 | |
| };
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * Formats Number in exponential format.
 | |
|  *
 | |
|  * @param {number} number Value need to be formatted.
 | |
|  * @param {Array<string>} parts The array that holds the pieces of formatted
 | |
|  *     string. This function will append more formatted pieces to the array.
 | |
|  * @private
 | |
|  */
 | |
| goog.i18n.NumberFormat.prototype.subformatExponential_ = function(
 | |
|     number, parts) {
 | |
|   if (number == 0.0) {
 | |
|     this.subformatFixed_(number, this.minimumIntegerDigits_, parts);
 | |
|     this.addExponentPart_(0, parts);
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   var exponent = goog.math.safeFloor(Math.log(number) / Math.log(10));
 | |
|   number /= Math.pow(10, exponent);
 | |
| 
 | |
|   var minIntDigits = this.minimumIntegerDigits_;
 | |
|   if (this.maximumIntegerDigits_ > 1 &&
 | |
|       this.maximumIntegerDigits_ > this.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 % this.maximumIntegerDigits_) != 0) {
 | |
|       number *= 10;
 | |
|       exponent--;
 | |
|     }
 | |
|     minIntDigits = 1;
 | |
|   } else {
 | |
|     // No repeating range is defined; use minimum integer digits.
 | |
|     if (this.minimumIntegerDigits_ < 1) {
 | |
|       exponent++;
 | |
|       number /= 10;
 | |
|     } else {
 | |
|       exponent -= this.minimumIntegerDigits_ - 1;
 | |
|       number *= Math.pow(10, this.minimumIntegerDigits_ - 1);
 | |
|     }
 | |
|   }
 | |
|   this.subformatFixed_(number, minIntDigits, parts);
 | |
|   this.addExponentPart_(exponent, parts);
 | |
| };
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * Returns the digit value of current character. The character could be either
 | |
|  * '0' to '9', or a locale specific digit.
 | |
|  *
 | |
|  * @param {string} ch Character that represents a digit.
 | |
|  * @return {number} The digit value, or -1 on error.
 | |
|  * @private
 | |
|  */
 | |
| goog.i18n.NumberFormat.prototype.getDigit_ = function(ch) {
 | |
|   var code = ch.charCodeAt(0);
 | |
|   // between '0' to '9'
 | |
|   if (48 <= code && code < 58) {
 | |
|     return code - 48;
 | |
|   } else {
 | |
|     var zeroCode = goog.i18n.NumberFormatSymbols.ZERO_DIGIT.charCodeAt(0);
 | |
|     return zeroCode <= code && code < zeroCode + 10 ? code - zeroCode : -1;
 | |
|   }
 | |
| };
 | |
| 
 | |
| 
 | |
| // ----------------------------------------------------------------------
 | |
| // CONSTANTS
 | |
| // ----------------------------------------------------------------------
 | |
| // Constants for characters used in programmatic (unlocalized) patterns.
 | |
| /**
 | |
|  * A zero digit character.
 | |
|  * @type {string}
 | |
|  * @private
 | |
|  */
 | |
| goog.i18n.NumberFormat.PATTERN_ZERO_DIGIT_ = '0';
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * A grouping separator character.
 | |
|  * @type {string}
 | |
|  * @private
 | |
|  */
 | |
| goog.i18n.NumberFormat.PATTERN_GROUPING_SEPARATOR_ = ',';
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * A decimal separator character.
 | |
|  * @type {string}
 | |
|  * @private
 | |
|  */
 | |
| goog.i18n.NumberFormat.PATTERN_DECIMAL_SEPARATOR_ = '.';
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * A per mille character.
 | |
|  * @type {string}
 | |
|  * @private
 | |
|  */
 | |
| goog.i18n.NumberFormat.PATTERN_PER_MILLE_ = '\u2030';
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * A percent character.
 | |
|  * @type {string}
 | |
|  * @private
 | |
|  */
 | |
| goog.i18n.NumberFormat.PATTERN_PERCENT_ = '%';
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * A digit character.
 | |
|  * @type {string}
 | |
|  * @private
 | |
|  */
 | |
| goog.i18n.NumberFormat.PATTERN_DIGIT_ = '#';
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * A separator character.
 | |
|  * @type {string}
 | |
|  * @private
 | |
|  */
 | |
| goog.i18n.NumberFormat.PATTERN_SEPARATOR_ = ';';
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * An exponent character.
 | |
|  * @type {string}
 | |
|  * @private
 | |
|  */
 | |
| goog.i18n.NumberFormat.PATTERN_EXPONENT_ = 'E';
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * An plus character.
 | |
|  * @type {string}
 | |
|  * @private
 | |
|  */
 | |
| goog.i18n.NumberFormat.PATTERN_PLUS_ = '+';
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * A quote character.
 | |
|  * @type {string}
 | |
|  * @private
 | |
|  */
 | |
| goog.i18n.NumberFormat.PATTERN_CURRENCY_SIGN_ = '\u00A4';
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * A quote character.
 | |
|  * @type {string}
 | |
|  * @private
 | |
|  */
 | |
| goog.i18n.NumberFormat.QUOTE_ = '\'';
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * Parses affix part of pattern.
 | |
|  *
 | |
|  * @param {string} pattern Pattern string that need to be parsed.
 | |
|  * @param {Array<number>} pos One element position array to set and receive
 | |
|  *     parsing position.
 | |
|  *
 | |
|  * @return {string} Affix received from parsing.
 | |
|  * @private
 | |
|  */
 | |
| goog.i18n.NumberFormat.prototype.parseAffix_ = function(pattern, pos) {
 | |
|   var affix = '';
 | |
|   var inQuote = false;
 | |
|   var len = pattern.length;
 | |
| 
 | |
|   for (; pos[0] < len; pos[0]++) {
 | |
|     var ch = pattern.charAt(pos[0]);
 | |
|     if (ch == goog.i18n.NumberFormat.QUOTE_) {
 | |
|       if (pos[0] + 1 < len &&
 | |
|           pattern.charAt(pos[0] + 1) == goog.i18n.NumberFormat.QUOTE_) {
 | |
|         pos[0]++;
 | |
|         affix += '\'';  // 'don''t'
 | |
|       } else {
 | |
|         inQuote = !inQuote;
 | |
|       }
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     if (inQuote) {
 | |
|       affix += ch;
 | |
|     } else {
 | |
|       switch (ch) {
 | |
|         case goog.i18n.NumberFormat.PATTERN_DIGIT_:
 | |
|         case goog.i18n.NumberFormat.PATTERN_ZERO_DIGIT_:
 | |
|         case goog.i18n.NumberFormat.PATTERN_GROUPING_SEPARATOR_:
 | |
|         case goog.i18n.NumberFormat.PATTERN_DECIMAL_SEPARATOR_:
 | |
|         case goog.i18n.NumberFormat.PATTERN_SEPARATOR_:
 | |
|           return affix;
 | |
|         case goog.i18n.NumberFormat.PATTERN_CURRENCY_SIGN_:
 | |
|           if ((pos[0] + 1) < len &&
 | |
|               pattern.charAt(pos[0] + 1) ==
 | |
|                   goog.i18n.NumberFormat.PATTERN_CURRENCY_SIGN_) {
 | |
|             pos[0]++;
 | |
|             affix += this.intlCurrencyCode_;
 | |
|           } else {
 | |
|             switch (this.currencyStyle_) {
 | |
|               case goog.i18n.NumberFormat.CurrencyStyle.LOCAL:
 | |
|                 affix += goog.i18n.currency.getLocalCurrencySign(
 | |
|                     this.intlCurrencyCode_);
 | |
|                 break;
 | |
|               case goog.i18n.NumberFormat.CurrencyStyle.GLOBAL:
 | |
|                 affix += goog.i18n.currency.getGlobalCurrencySign(
 | |
|                     this.intlCurrencyCode_);
 | |
|                 break;
 | |
|               case goog.i18n.NumberFormat.CurrencyStyle.PORTABLE:
 | |
|                 affix += goog.i18n.currency.getPortableCurrencySign(
 | |
|                     this.intlCurrencyCode_);
 | |
|                 break;
 | |
|               default:
 | |
|                 break;
 | |
|             }
 | |
|           }
 | |
|           break;
 | |
|         case goog.i18n.NumberFormat.PATTERN_PERCENT_:
 | |
|           if (!this.negativePercentSignExpected_ && this.multiplier_ != 1) {
 | |
|             throw Error('Too many percent/permill');
 | |
|           } else if (
 | |
|               this.negativePercentSignExpected_ && this.multiplier_ != 100) {
 | |
|             throw Error('Inconsistent use of percent/permill characters');
 | |
|           }
 | |
|           this.multiplier_ = 100;
 | |
|           this.negativePercentSignExpected_ = false;
 | |
|           affix += goog.i18n.NumberFormatSymbols.PERCENT;
 | |
|           break;
 | |
|         case goog.i18n.NumberFormat.PATTERN_PER_MILLE_:
 | |
|           if (!this.negativePercentSignExpected_ && this.multiplier_ != 1) {
 | |
|             throw Error('Too many percent/permill');
 | |
|           } else if (
 | |
|               this.negativePercentSignExpected_ && this.multiplier_ != 1000) {
 | |
|             throw Error('Inconsistent use of percent/permill characters');
 | |
|           }
 | |
|           this.multiplier_ = 1000;
 | |
|           this.negativePercentSignExpected_ = false;
 | |
|           affix += goog.i18n.NumberFormatSymbols.PERMILL;
 | |
|           break;
 | |
|         default:
 | |
|           affix += ch;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return affix;
 | |
| };
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * Parses the trunk part of a pattern.
 | |
|  *
 | |
|  * @param {string} pattern Pattern string that need to be parsed.
 | |
|  * @param {Array<number>} pos One element position array to set and receive
 | |
|  *     parsing position.
 | |
|  * @private
 | |
|  */
 | |
| goog.i18n.NumberFormat.prototype.parseTrunk_ = function(pattern, pos) {
 | |
|   var decimalPos = -1;
 | |
|   var digitLeftCount = 0;
 | |
|   var zeroDigitCount = 0;
 | |
|   var digitRightCount = 0;
 | |
|   var groupingCount = -1;
 | |
|   var len = pattern.length;
 | |
|   for (var loop = true; pos[0] < len && loop; pos[0]++) {
 | |
|     var ch = pattern.charAt(pos[0]);
 | |
|     switch (ch) {
 | |
|       case goog.i18n.NumberFormat.PATTERN_DIGIT_:
 | |
|         if (zeroDigitCount > 0) {
 | |
|           digitRightCount++;
 | |
|         } else {
 | |
|           digitLeftCount++;
 | |
|         }
 | |
|         if (groupingCount >= 0 && decimalPos < 0) {
 | |
|           groupingCount++;
 | |
|         }
 | |
|         break;
 | |
|       case goog.i18n.NumberFormat.PATTERN_ZERO_DIGIT_:
 | |
|         if (digitRightCount > 0) {
 | |
|           throw Error('Unexpected "0" in pattern "' + pattern + '"');
 | |
|         }
 | |
|         zeroDigitCount++;
 | |
|         if (groupingCount >= 0 && decimalPos < 0) {
 | |
|           groupingCount++;
 | |
|         }
 | |
|         break;
 | |
|       case goog.i18n.NumberFormat.PATTERN_GROUPING_SEPARATOR_:
 | |
|         if (groupingCount > 0) {
 | |
|           this.groupingArray_.push(groupingCount);
 | |
|         }
 | |
|         groupingCount = 0;
 | |
|         break;
 | |
|       case goog.i18n.NumberFormat.PATTERN_DECIMAL_SEPARATOR_:
 | |
|         if (decimalPos >= 0) {
 | |
|           throw Error(
 | |
|               'Multiple decimal separators in pattern "' + pattern + '"');
 | |
|         }
 | |
|         decimalPos = digitLeftCount + zeroDigitCount + digitRightCount;
 | |
|         break;
 | |
|       case goog.i18n.NumberFormat.PATTERN_EXPONENT_:
 | |
|         if (this.useExponentialNotation_) {
 | |
|           throw Error(
 | |
|               'Multiple exponential symbols in pattern "' + pattern + '"');
 | |
|         }
 | |
|         this.useExponentialNotation_ = true;
 | |
|         this.minExponentDigits_ = 0;
 | |
| 
 | |
|         // exponent pattern can have a optional '+'.
 | |
|         if ((pos[0] + 1) < len &&
 | |
|             pattern.charAt(pos[0] + 1) ==
 | |
|                 goog.i18n.NumberFormat.PATTERN_PLUS_) {
 | |
|           pos[0]++;
 | |
|           this.useSignForPositiveExponent_ = true;
 | |
|         }
 | |
| 
 | |
|         // Use lookahead to parse out the exponential part
 | |
|         // of the pattern, then jump into phase 2.
 | |
|         while ((pos[0] + 1) < len &&
 | |
|                pattern.charAt(pos[0] + 1) ==
 | |
|                    goog.i18n.NumberFormat.PATTERN_ZERO_DIGIT_) {
 | |
|           pos[0]++;
 | |
|           this.minExponentDigits_++;
 | |
|         }
 | |
| 
 | |
|         if ((digitLeftCount + zeroDigitCount) < 1 ||
 | |
|             this.minExponentDigits_ < 1) {
 | |
|           throw Error('Malformed exponential pattern "' + pattern + '"');
 | |
|         }
 | |
|         loop = false;
 | |
|         break;
 | |
|       default:
 | |
|         pos[0]--;
 | |
|         loop = false;
 | |
|         break;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (zeroDigitCount == 0 && digitLeftCount > 0 && decimalPos >= 0) {
 | |
|     // Handle '###.###' and '###.' and '.###'
 | |
|     var n = decimalPos;
 | |
|     if (n == 0) {  // Handle '.###'
 | |
|       n++;
 | |
|     }
 | |
|     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 Error('Malformed pattern "' + pattern + '"');
 | |
|   }
 | |
|   var totalDigits = digitLeftCount + zeroDigitCount + digitRightCount;
 | |
| 
 | |
|   this.maximumFractionDigits_ = decimalPos >= 0 ? totalDigits - decimalPos : 0;
 | |
|   if (decimalPos >= 0) {
 | |
|     this.minimumFractionDigits_ = digitLeftCount + zeroDigitCount - decimalPos;
 | |
|     if (this.minimumFractionDigits_ < 0) {
 | |
|       this.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;
 | |
|   this.minimumIntegerDigits_ = effectiveDecimalPos - digitLeftCount;
 | |
|   if (this.useExponentialNotation_) {
 | |
|     this.maximumIntegerDigits_ = digitLeftCount + this.minimumIntegerDigits_;
 | |
| 
 | |
|     // in exponential display, we need to at least show something.
 | |
|     if (this.maximumFractionDigits_ == 0 && this.minimumIntegerDigits_ == 0) {
 | |
|       this.minimumIntegerDigits_ = 1;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // Add another number grouping at the end
 | |
|   this.groupingArray_.push(Math.max(0, groupingCount));
 | |
|   this.decimalSeparatorAlwaysShown_ =
 | |
|       decimalPos == 0 || decimalPos == totalDigits;
 | |
| };
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * Alias for the compact format 'unit' object.
 | |
|  * @typedef {{
 | |
|  *     prefix: string,
 | |
|  *     suffix: string,
 | |
|  *     divisorBase: number
 | |
|  * }}
 | |
|  */
 | |
| goog.i18n.NumberFormat.CompactNumberUnit;
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * The empty unit, corresponding to a base of 0.
 | |
|  * @private {!goog.i18n.NumberFormat.CompactNumberUnit}
 | |
|  */
 | |
| goog.i18n.NumberFormat.NULL_UNIT_ = {
 | |
|   prefix: '',
 | |
|   suffix: '',
 | |
|   divisorBase: 0
 | |
| };
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * Get compact unit for a certain number of digits
 | |
|  *
 | |
|  * @param {number} base The number of digits to get the unit for.
 | |
|  * @param {string} plurality The plurality of the number.
 | |
|  * @return {!goog.i18n.NumberFormat.CompactNumberUnit} The compact unit.
 | |
|  * @private
 | |
|  */
 | |
| goog.i18n.NumberFormat.prototype.getUnitFor_ = function(base, plurality) {
 | |
|   var table = this.compactStyle_ == goog.i18n.NumberFormat.CompactStyle.SHORT ?
 | |
|       goog.i18n.CompactNumberFormatSymbols.COMPACT_DECIMAL_SHORT_PATTERN :
 | |
|       goog.i18n.CompactNumberFormatSymbols.COMPACT_DECIMAL_LONG_PATTERN;
 | |
| 
 | |
|   if (!goog.isDefAndNotNull(table)) {
 | |
|     table = goog.i18n.CompactNumberFormatSymbols.COMPACT_DECIMAL_SHORT_PATTERN;
 | |
|   }
 | |
| 
 | |
|   if (base < 3) {
 | |
|     return goog.i18n.NumberFormat.NULL_UNIT_;
 | |
|   } else {
 | |
|     base = Math.min(14, base);
 | |
|     var patterns = table[Math.pow(10, base)];
 | |
|     var previousNonNullBase = base - 1;
 | |
|     while (!patterns && previousNonNullBase >= 3) {
 | |
|       patterns = table[Math.pow(10, previousNonNullBase)];
 | |
|       previousNonNullBase--;
 | |
|     }
 | |
|     if (!patterns) {
 | |
|       return goog.i18n.NumberFormat.NULL_UNIT_;
 | |
|     }
 | |
| 
 | |
|     var pattern = patterns[plurality];
 | |
|     if (!pattern || pattern == '0') {
 | |
|       return goog.i18n.NumberFormat.NULL_UNIT_;
 | |
|     }
 | |
| 
 | |
|     var parts = /([^0]*)(0+)(.*)/.exec(pattern);
 | |
|     if (!parts) {
 | |
|       return goog.i18n.NumberFormat.NULL_UNIT_;
 | |
|     }
 | |
| 
 | |
|     return {
 | |
|       prefix: parts[1],
 | |
|       suffix: parts[3],
 | |
|       divisorBase: (previousNonNullBase + 1) - (parts[2].length - 1)
 | |
|     };
 | |
|   }
 | |
| };
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * Get the compact unit divisor, accounting for rounding of the quantity.
 | |
|  *
 | |
|  * @param {number} formattingNumber The number to base the formatting on. The
 | |
|  *     unit will be calculated from this number.
 | |
|  * @param {number} pluralityNumber The number to use for calculating the
 | |
|  *     plurality.
 | |
|  * @return {!goog.i18n.NumberFormat.CompactNumberUnit} The unit after rounding.
 | |
|  * @private
 | |
|  */
 | |
| goog.i18n.NumberFormat.prototype.getUnitAfterRounding_ = function(
 | |
|     formattingNumber, pluralityNumber) {
 | |
|   if (this.compactStyle_ == goog.i18n.NumberFormat.CompactStyle.NONE) {
 | |
|     return goog.i18n.NumberFormat.NULL_UNIT_;
 | |
|   }
 | |
| 
 | |
|   formattingNumber = Math.abs(formattingNumber);
 | |
|   pluralityNumber = Math.abs(pluralityNumber);
 | |
| 
 | |
|   var initialPlurality = this.pluralForm_(formattingNumber);
 | |
|   // Compute the exponent from the formattingNumber, to compute the unit.
 | |
|   var base = formattingNumber <= 1 ? 0 : this.intLog10_(formattingNumber);
 | |
|   var initialDivisor = this.getUnitFor_(base, initialPlurality).divisorBase;
 | |
|   // Round both numbers based on the unit used.
 | |
|   var pluralityAttempt = pluralityNumber / Math.pow(10, initialDivisor);
 | |
|   var pluralityRounded = this.roundNumber_(pluralityAttempt);
 | |
|   var formattingAttempt = formattingNumber / Math.pow(10, initialDivisor);
 | |
|   var formattingRounded = this.roundNumber_(formattingAttempt);
 | |
|   // Compute the plurality of the pluralityNumber when formatted using the name
 | |
|   // units as the formattingNumber.
 | |
|   var finalPlurality =
 | |
|       this.pluralForm_(pluralityRounded.intValue + pluralityRounded.fracValue);
 | |
|   // Get the final unit, using the rounded formatting number to get the correct
 | |
|   // unit, and the plurality computed from the pluralityNumber.
 | |
|   return this.getUnitFor_(
 | |
|       initialDivisor + this.intLog10_(formattingRounded.intValue),
 | |
|       finalPlurality);
 | |
| };
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * Get the integer base 10 logarithm of a number.
 | |
|  *
 | |
|  * @param {number} number The number to log.
 | |
|  * @return {number} The lowest integer n such that 10^n >= number.
 | |
|  * @private
 | |
|  */
 | |
| goog.i18n.NumberFormat.prototype.intLog10_ = function(number) {
 | |
|   // Turns out Math.log(1000000)/Math.LN10 is strictly less than 6.
 | |
|   var i = 0;
 | |
|   while ((number /= 10) >= 1) i++;
 | |
|   return i;
 | |
| };
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * Round to a certain number of significant digits.
 | |
|  *
 | |
|  * @param {number} number The number to round.
 | |
|  * @param {number} significantDigits The number of significant digits
 | |
|  *     to round to.
 | |
|  * @param {number} scale Treat number as fixed point times 10^scale.
 | |
|  * @return {number} The rounded number.
 | |
|  * @private
 | |
|  */
 | |
| goog.i18n.NumberFormat.prototype.roundToSignificantDigits_ = function(
 | |
|     number, significantDigits, scale) {
 | |
|   if (!number) return number;
 | |
| 
 | |
|   var digits = this.intLog10_(number);
 | |
|   var magnitude = significantDigits - digits - 1;
 | |
| 
 | |
|   // Only round fraction, not (potentially shifted) integers.
 | |
|   if (magnitude < -scale) {
 | |
|     var point = Math.pow(10, scale);
 | |
|     return Math.round(number / point) * point;
 | |
|   }
 | |
| 
 | |
|   var power = Math.pow(10, magnitude);
 | |
|   var shifted = Math.round(number * power);
 | |
|   return shifted / power;
 | |
| };
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * Get the plural form of a number.
 | |
|  * @param {number} quantity The quantity to find plurality of.
 | |
|  * @return {string} One of 'zero', 'one', 'two', 'few', 'many', 'other'.
 | |
|  * @private
 | |
|  */
 | |
| goog.i18n.NumberFormat.prototype.pluralForm_ = function(quantity) {
 | |
|   /* TODO: Implement */
 | |
|   return 'other';
 | |
| };
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * Checks if the currency symbol comes before the value ($12) or after (12$)
 | |
|  * Handy for applications that need to have separate UI fields for the currency
 | |
|  * value and symbol, especially for input: Price: [USD] [123.45]
 | |
|  * The currency symbol might be a combo box, or a label.
 | |
|  *
 | |
|  * @return {boolean} true if currency is before value.
 | |
|  */
 | |
| goog.i18n.NumberFormat.prototype.isCurrencyCodeBeforeValue = function() {
 | |
|   var posCurrSymbol = this.pattern_.indexOf('\u00A4');  // '¤' Currency sign
 | |
|   var posPound = this.pattern_.indexOf('#');
 | |
|   var posZero = this.pattern_.indexOf('0');
 | |
| 
 | |
|   // posCurrValue is the first '#' or '0' found.
 | |
|   // If none of them is found (not possible, but still),
 | |
|   // the result is true (postCurrSymbol < MAX_VALUE)
 | |
|   // That is OK, matches the en_US and ROOT locales.
 | |
|   var posCurrValue = Number.MAX_VALUE;
 | |
|   if (posPound >= 0 && posPound < posCurrValue) {
 | |
|     posCurrValue = posPound;
 | |
|   }
 | |
|   if (posZero >= 0 && posZero < posCurrValue) {
 | |
|     posCurrValue = posZero;
 | |
|   }
 | |
| 
 | |
|   // No need to test, it is guaranteed that both these symbols exist.
 | |
|   // If not, we have bigger problems than this.
 | |
|   return posCurrSymbol < posCurrValue;
 | |
| };
 |