/** * Globalize v1.0.0 * * http://github.com/jquery/globalize * * Copyright jQuery Foundation and other contributors * Released under the MIT license * http://jquery.org/license * * Date: 2015-04-23T12:02Z */ /*! * Globalize v1.0.0 2015-04-23T12:02Z Released under the MIT license * http://git.io/TrdQbw */ (function( root, factory ) { // UMD returnExports if ( typeof define === "function" && define.amd ) { // AMD define([ "cldr", "../globalize", "cldr/event", "cldr/supplemental" ], factory ); } else if ( typeof exports === "object" ) { // Node, CommonJS module.exports = factory( require( "cldrjs" ), require( "globalize" ) ); } else { // Global factory( root.Cldr, root.Globalize ); } }(this, function( Cldr, Globalize ) { var createError = Globalize._createError, objectExtend = Globalize._objectExtend, regexpEscape = Globalize._regexpEscape, stringPad = Globalize._stringPad, validateCldr = Globalize._validateCldr, validateDefaultLocale = Globalize._validateDefaultLocale, validateParameterPresence = Globalize._validateParameterPresence, validateParameterRange = Globalize._validateParameterRange, validateParameterType = Globalize._validateParameterType, validateParameterTypePlainObject = Globalize._validateParameterTypePlainObject; var createErrorUnsupportedFeature = function( feature ) { return createError( "E_UNSUPPORTED", "Unsupported {feature}.", { feature: feature }); }; var validateParameterTypeNumber = function( value, name ) { validateParameterType( value, name, value === undefined || typeof value === "number", "Number" ); }; var validateParameterTypeString = function( value, name ) { validateParameterType( value, name, value === undefined || typeof value === "string", "a string" ); }; /** * goupingSeparator( number, primaryGroupingSize, secondaryGroupingSize ) * * @number [Number]. * * @primaryGroupingSize [Number] * * @secondaryGroupingSize [Number] * * Return the formatted number with group separator. */ var numberFormatGroupingSeparator = function( number, primaryGroupingSize, secondaryGroupingSize ) { var index, currentGroupingSize = primaryGroupingSize, ret = "", sep = ",", switchToSecondary = secondaryGroupingSize ? true : false; number = String( number ).split( "." ); index = number[ 0 ].length; while ( index > currentGroupingSize ) { ret = number[ 0 ].slice( index - currentGroupingSize, index ) + ( ret.length ? sep : "" ) + ret; index -= currentGroupingSize; if ( switchToSecondary ) { currentGroupingSize = secondaryGroupingSize; switchToSecondary = false; } } number[ 0 ] = number[ 0 ].slice( 0, index ) + ( ret.length ? sep : "" ) + ret; return number.join( "." ); }; /** * integerFractionDigits( number, minimumIntegerDigits, minimumFractionDigits, * maximumFractionDigits, round, roundIncrement ) * * @number [Number] * * @minimumIntegerDigits [Number] * * @minimumFractionDigits [Number] * * @maximumFractionDigits [Number] * * @round [Function] * * @roundIncrement [Function] * * Return the formatted integer and fraction digits. */ var numberFormatIntegerFractionDigits = function( number, minimumIntegerDigits, minimumFractionDigits, maximumFractionDigits, round, roundIncrement ) { // Fraction if ( maximumFractionDigits ) { // Rounding if ( roundIncrement ) { number = round( number, roundIncrement ); // Maximum fraction digits } else { number = round( number, { exponent: -maximumFractionDigits } ); } // Minimum fraction digits if ( minimumFractionDigits ) { number = String( number ).split( "." ); number[ 1 ] = stringPad( number[ 1 ] || "", minimumFractionDigits, true ); number = number.join( "." ); } } else { number = round( number ); } number = String( number ); // Minimum integer digits if ( minimumIntegerDigits ) { number = number.split( "." ); number[ 0 ] = stringPad( number[ 0 ], minimumIntegerDigits ); number = number.join( "." ); } return number; }; /** * toPrecision( number, precision, round ) * * @number (Number) * * @precision (Number) significant figures precision (not decimal precision). * * @round (Function) * * Return number.toPrecision( precision ) using the given round function. */ var numberToPrecision = function( number, precision, round ) { var roundOrder; // Get number at two extra significant figure precision. number = number.toPrecision( precision + 2 ); // Then, round it to the required significant figure precision. roundOrder = Math.ceil( Math.log( Math.abs( number ) ) / Math.log( 10 ) ); roundOrder -= precision; return round( number, { exponent: roundOrder } ); }; /** * toPrecision( number, minimumSignificantDigits, maximumSignificantDigits, round ) * * @number [Number] * * @minimumSignificantDigits [Number] * * @maximumSignificantDigits [Number] * * @round [Function] * * Return the formatted significant digits number. */ var numberFormatSignificantDigits = function( number, minimumSignificantDigits, maximumSignificantDigits, round ) { var atMinimum, atMaximum; // Sanity check. if ( minimumSignificantDigits > maximumSignificantDigits ) { maximumSignificantDigits = minimumSignificantDigits; } atMinimum = numberToPrecision( number, minimumSignificantDigits, round ); atMaximum = numberToPrecision( number, maximumSignificantDigits, round ); // Use atMaximum only if it has more significant digits than atMinimum. number = +atMinimum === +atMaximum ? atMinimum : atMaximum; // Expand integer numbers, eg. 123e5 to 12300. number = ( +number ).toString( 10 ); if ( (/e/).test( number ) ) { throw createErrorUnsupportedFeature({ feature: "integers out of (1e21, 1e-7)" }); } // Add trailing zeros if necessary. if ( minimumSignificantDigits - number.replace( /^0+|\./g, "" ).length > 0 ) { number = number.split( "." ); number[ 1 ] = stringPad( number[ 1 ] || "", minimumSignificantDigits - number[ 0 ].replace( /^0+/, "" ).length, true ); number = number.join( "." ); } return number; }; /** * format( number, properties ) * * @number [Number]. * * @properties [Object] Output of number/format-properties. * * Return the formatted number. * ref: http://www.unicode.org/reports/tr35/tr35-numbers.html */ var numberFormat = function( number, properties ) { var infinitySymbol, maximumFractionDigits, maximumSignificantDigits, minimumFractionDigits, minimumIntegerDigits, minimumSignificantDigits, nanSymbol, nuDigitsMap, padding, prefix, primaryGroupingSize, pattern, ret, round, roundIncrement, secondaryGroupingSize, suffix, symbolMap; padding = properties[ 1 ]; minimumIntegerDigits = properties[ 2 ]; minimumFractionDigits = properties[ 3 ]; maximumFractionDigits = properties[ 4 ]; minimumSignificantDigits = properties[ 5 ]; maximumSignificantDigits = properties[ 6 ]; roundIncrement = properties[ 7 ]; primaryGroupingSize = properties[ 8 ]; secondaryGroupingSize = properties[ 9 ]; round = properties[ 15 ]; infinitySymbol = properties[ 16 ]; nanSymbol = properties[ 17 ]; symbolMap = properties[ 18 ]; nuDigitsMap = properties[ 19 ]; // NaN if ( isNaN( number ) ) { return nanSymbol; } if ( number < 0 ) { pattern = properties[ 12 ]; prefix = properties[ 13 ]; suffix = properties[ 14 ]; } else { pattern = properties[ 11 ]; prefix = properties[ 0 ]; suffix = properties[ 10 ]; } // Infinity if ( !isFinite( number ) ) { return prefix + infinitySymbol + suffix; } ret = prefix; // Percent if ( pattern.indexOf( "%" ) !== -1 ) { number *= 100; // Per mille } else if ( pattern.indexOf( "\u2030" ) !== -1 ) { number *= 1000; } // Significant digit format if ( !isNaN( minimumSignificantDigits * maximumSignificantDigits ) ) { number = numberFormatSignificantDigits( number, minimumSignificantDigits, maximumSignificantDigits, round ); // Integer and fractional format } else { number = numberFormatIntegerFractionDigits( number, minimumIntegerDigits, minimumFractionDigits, maximumFractionDigits, round, roundIncrement ); } // Remove the possible number minus sign number = number.replace( /^-/, "" ); // Grouping separators if ( primaryGroupingSize ) { number = numberFormatGroupingSeparator( number, primaryGroupingSize, secondaryGroupingSize ); } ret += number; // Scientific notation // TODO implement here // Padding/'([^']|'')+'|''|[.,\-+E%\u2030]/g // TODO implement here ret += suffix; return ret.replace( /('([^']|'')+'|'')|./g, function( character, literal ) { // Literals if ( literal ) { literal = literal.replace( /''/, "'" ); if ( literal.length > 2 ) { literal = literal.slice( 1, -1 ); } return literal; } // Symbols character = character.replace( /[.,\-+E%\u2030]/, function( symbol ) { return symbolMap[ symbol ]; }); // Numbering system if ( nuDigitsMap ) { character = character.replace( /[0-9]/, function( digit ) { return nuDigitsMap[ +digit ]; }); } return character; }); }; /** * NumberingSystem( cldr ) * * - http://www.unicode.org/reports/tr35/tr35-numbers.html#otherNumberingSystems * - http://cldr.unicode.org/index/bcp47-extension * - http://www.unicode.org/reports/tr35/#u_Extension */ var numberNumberingSystem = function( cldr ) { var nu = cldr.attributes[ "u-nu" ]; if ( nu ) { if ( nu === "traditio" ) { nu = "traditional"; } if ( [ "native", "traditional", "finance" ].indexOf( nu ) !== -1 ) { // Unicode locale extension `u-nu` is set using either (native, traditional or // finance). So, lookup the respective locale's numberingSystem and return it. return cldr.main([ "numbers/otherNumberingSystems", nu ]); } // Unicode locale extension `u-nu` is set with an explicit numberingSystem. Return it. return nu; } // Return the default numberingSystem. return cldr.main( "numbers/defaultNumberingSystem" ); }; /** * nuMap( cldr ) * * @cldr [Cldr instance]. * * Return digits map if numbering system is different than `latn`. */ var numberNumberingSystemDigitsMap = function( cldr ) { var aux, nu = numberNumberingSystem( cldr ); if ( nu === "latn" ) { return; } aux = cldr.supplemental([ "numberingSystems", nu ]); if ( aux._type !== "numeric" ) { throw createErrorUnsupportedFeature( "`" + aux._type + "` numbering system" ); } return aux._digits; }; /** * EBNF representation: * * number_pattern_re = prefix? * padding? * (integer_fraction_pattern | significant_pattern) * scientific_notation? * suffix? * * prefix = non_number_stuff * * padding = "*" regexp(.) * * integer_fraction_pattern = integer_pattern * fraction_pattern? * * integer_pattern = regexp([#,]*[0,]*0+) * * fraction_pattern = "." regexp(0*[0-9]*#*) * * significant_pattern = regexp([#,]*@+#*) * * scientific_notation = regexp(E\+?0+) * * suffix = non_number_stuff * * non_number_stuff = regexp(('[^']+'|''|[^*#@0,.E])*) * * * Regexp groups: * * 0: number_pattern_re * 1: prefix * 2: - * 3: padding * 4: (integer_fraction_pattern | significant_pattern) * 5: integer_fraction_pattern * 6: integer_pattern * 7: fraction_pattern * 8: significant_pattern * 9: scientific_notation * 10: suffix * 11: - */ var numberPatternRe = (/^(('[^']+'|''|[^*#@0,.E])*)(\*.)?((([#,]*[0,]*0+)(\.0*[0-9]*#*)?)|([#,]*@+#*))(E\+?0+)?(('[^']+'|''|[^*#@0,.E])*)$/); /** * format( number, pattern ) * * @number [Number]. * * @pattern [String] raw pattern for numbers. * * Return the formatted number. * ref: http://www.unicode.org/reports/tr35/tr35-numbers.html */ var numberPatternProperties = function( pattern ) { var aux1, aux2, fractionPattern, integerFractionOrSignificantPattern, integerPattern, maximumFractionDigits, maximumSignificantDigits, minimumFractionDigits, minimumIntegerDigits, minimumSignificantDigits, padding, prefix, primaryGroupingSize, roundIncrement, scientificNotation, secondaryGroupingSize, significantPattern, suffix; pattern = pattern.match( numberPatternRe ); if ( !pattern ) { throw new Error( "Invalid pattern: " + pattern ); } prefix = pattern[ 1 ]; padding = pattern[ 3 ]; integerFractionOrSignificantPattern = pattern[ 4 ]; significantPattern = pattern[ 8 ]; scientificNotation = pattern[ 9 ]; suffix = pattern[ 10 ]; // Significant digit format if ( significantPattern ) { significantPattern.replace( /(@+)(#*)/, function( match, minimumSignificantDigitsMatch, maximumSignificantDigitsMatch ) { minimumSignificantDigits = minimumSignificantDigitsMatch.length; maximumSignificantDigits = minimumSignificantDigits + maximumSignificantDigitsMatch.length; }); // Integer and fractional format } else { fractionPattern = pattern[ 7 ]; integerPattern = pattern[ 6 ]; if ( fractionPattern ) { // Minimum fraction digits, and rounding. fractionPattern.replace( /[0-9]+/, function( match ) { minimumFractionDigits = match; }); if ( minimumFractionDigits ) { roundIncrement = +( "0." + minimumFractionDigits ); minimumFractionDigits = minimumFractionDigits.length; } else { minimumFractionDigits = 0; } // Maximum fraction digits // 1: ignore decimal character maximumFractionDigits = fractionPattern.length - 1 /* 1 */; } // Minimum integer digits integerPattern.replace( /0+$/, function( match ) { minimumIntegerDigits = match.length; }); } // Scientific notation if ( scientificNotation ) { throw createErrorUnsupportedFeature({ feature: "scientific notation (not implemented)" }); } // Padding if ( padding ) { throw createErrorUnsupportedFeature({ feature: "padding (not implemented)" }); } // Grouping if ( ( aux1 = integerFractionOrSignificantPattern.lastIndexOf( "," ) ) !== -1 ) { // Primary grouping size is the interval between the last group separator and the end of // the integer (or the end of the significant pattern). aux2 = integerFractionOrSignificantPattern.split( "." )[ 0 ]; primaryGroupingSize = aux2.length - aux1 - 1; // Secondary grouping size is the interval between the last two group separators. if ( ( aux2 = integerFractionOrSignificantPattern.lastIndexOf( ",", aux1 - 1 ) ) !== -1 ) { secondaryGroupingSize = aux1 - 1 - aux2; } } // Return: // 0: @prefix String // 1: @padding Array [ , ] TODO // 2: @minimumIntegerDigits non-negative integer Number value indicating the minimum integer // digits to be used. Numbers will be padded with leading zeroes if necessary. // 3: @minimumFractionDigits and // 4: @maximumFractionDigits are non-negative integer Number values indicating the minimum and // maximum fraction digits to be used. Numbers will be rounded or padded with trailing // zeroes if necessary. // 5: @minimumSignificantDigits and // 6: @maximumSignificantDigits are positive integer Number values indicating the minimum and // maximum fraction digits to be shown. Either none or both of these properties are // present; if they are, they override minimum and maximum integer and fraction digits // – the formatter uses however many integer and fraction digits are required to display // the specified number of significant digits. // 7: @roundIncrement Decimal round increment or null // 8: @primaryGroupingSize // 9: @secondaryGroupingSize // 10: @suffix String return [ prefix, padding, minimumIntegerDigits, minimumFractionDigits, maximumFractionDigits, minimumSignificantDigits, maximumSignificantDigits, roundIncrement, primaryGroupingSize, secondaryGroupingSize, suffix ]; }; /** * Symbol( name, cldr ) * * @name [String] Symbol name. * * @cldr [Cldr instance]. * * Return the localized symbol given its name. */ var numberSymbol = function( name, cldr ) { return cldr.main([ "numbers/symbols-numberSystem-" + numberNumberingSystem( cldr ), name ]); }; var numberSymbolName = { ".": "decimal", ",": "group", "%": "percentSign", "+": "plusSign", "-": "minusSign", "E": "exponential", "\u2030": "perMille" }; /** * symbolMap( cldr ) * * @cldr [Cldr instance]. * * Return the (localized symbol, pattern symbol) key value pair, eg. { * ".": "٫", * ",": "٬", * "%": "٪", * ... * }; */ var numberSymbolMap = function( cldr ) { var symbol, symbolMap = {}; for ( symbol in numberSymbolName ) { symbolMap[ symbol ] = numberSymbol( numberSymbolName[ symbol ], cldr ); } return symbolMap; }; var numberTruncate = function( value ) { if ( isNaN( value ) ) { return NaN; } return Math[ value < 0 ? "ceil" : "floor" ]( value ); }; /** * round( method ) * * @method [String] with either "round", "ceil", "floor", or "truncate". * * Return function( value, incrementOrExp ): * * @value [Number] eg. 123.45. * * @incrementOrExp [Number] optional, eg. 0.1; or * [Object] Either { increment: } or { exponent: } * * Return the rounded number, eg: * - round( "round" )( 123.45 ): 123; * - round( "ceil" )( 123.45 ): 124; * - round( "floor" )( 123.45 ): 123; * - round( "truncate" )( 123.45 ): 123; * - round( "round" )( 123.45, 0.1 ): 123.5; * - round( "round" )( 123.45, 10 ): 120; * * Based on https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/round * Ref: #376 */ var numberRound = function( method ) { method = method || "round"; method = method === "truncate" ? numberTruncate : Math[ method ]; return function( value, incrementOrExp ) { var exp, increment; value = +value; // If the value is not a number, return NaN. if ( isNaN( value ) ) { return NaN; } // Exponent given. if ( typeof incrementOrExp === "object" && incrementOrExp.exponent ) { exp = +incrementOrExp.exponent; increment = 1; if ( exp === 0 ) { return method( value ); } // If the exp is not an integer, return NaN. if ( !( typeof exp === "number" && exp % 1 === 0 ) ) { return NaN; } // Increment given. } else { increment = +incrementOrExp || 1; if ( increment === 1 ) { return method( value ); } // If the increment is not a number, return NaN. if ( isNaN( increment ) ) { return NaN; } increment = increment.toExponential().split( "e" ); exp = +increment[ 1 ]; increment = +increment[ 0 ]; } // Shift & Round value = value.toString().split( "e" ); value[ 0 ] = +value[ 0 ] / increment; value[ 1 ] = value[ 1 ] ? ( +value[ 1 ] - exp ) : -exp; value = method( +(value[ 0 ] + "e" + value[ 1 ] ) ); // Shift back value = value.toString().split( "e" ); value[ 0 ] = +value[ 0 ] * increment; value[ 1 ] = value[ 1 ] ? ( +value[ 1 ] + exp ) : exp; return +( value[ 0 ] + "e" + value[ 1 ] ); }; }; /** * formatProperties( pattern, cldr [, options] ) * * @pattern [String] raw pattern for numbers. * * @cldr [Cldr instance]. * * @options [Object]: * - minimumIntegerDigits [Number] * - minimumFractionDigits, maximumFractionDigits [Number] * - minimumSignificantDigits, maximumSignificantDigits [Number] * - round [String] "ceil", "floor", "round" (default), or "truncate". * - useGrouping [Boolean] default true. * * Return the processed properties that will be used in number/format. * ref: http://www.unicode.org/reports/tr35/tr35-numbers.html */ var numberFormatProperties = function( pattern, cldr, options ) { var negativePattern, negativePrefix, negativeProperties, negativeSuffix, positivePattern, properties; function getOptions( attribute, propertyIndex ) { if ( attribute in options ) { properties[ propertyIndex ] = options[ attribute ]; } } options = options || {}; pattern = pattern.split( ";" ); positivePattern = pattern[ 0 ]; negativePattern = pattern[ 1 ] || "-" + positivePattern; negativeProperties = numberPatternProperties( negativePattern ); negativePrefix = negativeProperties[ 0 ]; negativeSuffix = negativeProperties[ 10 ]; properties = numberPatternProperties( positivePattern ).concat([ positivePattern, negativePrefix + positivePattern + negativeSuffix, negativePrefix, negativeSuffix, numberRound( options.round ), numberSymbol( "infinity", cldr ), numberSymbol( "nan", cldr ), numberSymbolMap( cldr ), numberNumberingSystemDigitsMap( cldr ) ]); getOptions( "minimumIntegerDigits", 2 ); getOptions( "minimumFractionDigits", 3 ); getOptions( "maximumFractionDigits", 4 ); getOptions( "minimumSignificantDigits", 5 ); getOptions( "maximumSignificantDigits", 6 ); // Grouping separators if ( options.useGrouping === false ) { properties[ 8 ] = null; } // Normalize number of digits if only one of either minimumFractionDigits or // maximumFractionDigits is passed in as an option if ( "minimumFractionDigits" in options && !( "maximumFractionDigits" in options ) ) { // maximumFractionDigits = Math.max( minimumFractionDigits, maximumFractionDigits ); properties[ 4 ] = Math.max( properties[ 3 ], properties[ 4 ] ); } else if ( !( "minimumFractionDigits" in options ) && "maximumFractionDigits" in options ) { // minimumFractionDigits = Math.min( minimumFractionDigits, maximumFractionDigits ); properties[ 3 ] = Math.min( properties[ 3 ], properties[ 4 ] ); } // Return: // 0-10: see number/pattern-properties. // 11: @positivePattern [String] Positive pattern. // 12: @negativePattern [String] Negative pattern. // 13: @negativePrefix [String] Negative prefix. // 14: @negativeSuffix [String] Negative suffix. // 15: @round [Function] Round function. // 16: @infinitySymbol [String] Infinity symbol. // 17: @nanSymbol [String] NaN symbol. // 18: @symbolMap [Object] A bunch of other symbols. // 19: @nuDigitsMap [Array] Digits map if numbering system is different than `latn`. return properties; }; /** * EBNF representation: * * number_pattern_re = prefix_including_padding? * number * scientific_notation? * suffix? * * number = integer_including_group_separator fraction_including_decimal_separator * * integer_including_group_separator = * regexp([0-9,]*[0-9]+) * * fraction_including_decimal_separator = * regexp((\.[0-9]+)?) * prefix_including_padding = non_number_stuff * * scientific_notation = regexp(E[+-]?[0-9]+) * * suffix = non_number_stuff * * non_number_stuff = regexp([^0-9]*) * * * Regexp groups: * * 0: number_pattern_re * 1: prefix * 2: integer_including_group_separator fraction_including_decimal_separator * 3: integer_including_group_separator * 4: fraction_including_decimal_separator * 5: scientific_notation * 6: suffix */ var numberNumberRe = (/^([^0-9]*)(([0-9,]*[0-9]+)(\.[0-9]+)?)(E[+-]?[0-9]+)?([^0-9]*)$/); /** * parse( value, properties ) * * @value [String]. * * @properties [Object] Parser properties is a reduced pre-processed cldr * data set returned by numberParserProperties(). * * Return the parsed Number (including Infinity) or NaN when value is invalid. * ref: http://www.unicode.org/reports/tr35/tr35-numbers.html */ var numberParse = function( value, properties ) { var aux, infinitySymbol, invertedNuDigitsMap, invertedSymbolMap, localizedDigitRe, localizedSymbolsRe, negativePrefix, negativeSuffix, number, prefix, suffix; infinitySymbol = properties[ 0 ]; invertedSymbolMap = properties[ 1 ]; negativePrefix = properties[ 2 ]; negativeSuffix = properties[ 3 ]; invertedNuDigitsMap = properties[ 4 ]; // Infinite number. if ( aux = value.match( infinitySymbol ) ) { number = Infinity; prefix = value.slice( 0, aux.length ); suffix = value.slice( aux.length + 1 ); // Finite number. } else { // TODO: Create it during setup, i.e., make it a property. localizedSymbolsRe = new RegExp( Object.keys( invertedSymbolMap ).map(function( localizedSymbol ) { return regexpEscape( localizedSymbol ); }).join( "|" ), "g" ); // Reverse localized symbols. value = value.replace( localizedSymbolsRe, function( localizedSymbol ) { return invertedSymbolMap[ localizedSymbol ]; }); // Reverse localized numbering system. if ( invertedNuDigitsMap ) { // TODO: Create it during setup, i.e., make it a property. localizedDigitRe = new RegExp( Object.keys( invertedNuDigitsMap ).map(function( localizedDigit ) { return regexpEscape( localizedDigit ); }).join( "|" ), "g" ); value = value.replace( localizedDigitRe, function( localizedDigit ) { return invertedNuDigitsMap[ localizedDigit ]; }); } // Is it a valid number? value = value.match( numberNumberRe ); if ( !value ) { // Invalid number. return NaN; } prefix = value[ 1 ]; suffix = value[ 6 ]; // Remove grouping separators. number = value[ 2 ].replace( /,/g, "" ); // Scientific notation if ( value[ 5 ] ) { number += value[ 5 ]; } number = +number; // Is it a valid number? if ( isNaN( number ) ) { // Invalid number. return NaN; } // Percent if ( value[ 0 ].indexOf( "%" ) !== -1 ) { number /= 100; suffix = suffix.replace( "%", "" ); // Per mille } else if ( value[ 0 ].indexOf( "\u2030" ) !== -1 ) { number /= 1000; suffix = suffix.replace( "\u2030", "" ); } } // Negative number // "If there is an explicit negative subpattern, it serves only to specify the negative prefix // and suffix. If there is no explicit negative subpattern, the negative subpattern is the // localized minus sign prefixed to the positive subpattern" UTS#35 if ( prefix === negativePrefix && suffix === negativeSuffix ) { number *= -1; } return number; }; /** * symbolMap( cldr ) * * @cldr [Cldr instance]. * * Return the (localized symbol, pattern symbol) key value pair, eg. { * "٫": ".", * "٬": ",", * "٪": "%", * ... * }; */ var numberSymbolInvertedMap = function( cldr ) { var symbol, symbolMap = {}; for ( symbol in numberSymbolName ) { symbolMap[ numberSymbol( numberSymbolName[ symbol ], cldr ) ] = symbol; } return symbolMap; }; /** * parseProperties( pattern, cldr ) * * @pattern [String] raw pattern for numbers. * * @cldr [Cldr instance]. * * Return parser properties, used to feed parser function. */ var numberParseProperties = function( pattern, cldr ) { var invertedNuDigitsMap, invertedNuDigitsMapSanityCheck, negativePattern, negativeProperties, nuDigitsMap = numberNumberingSystemDigitsMap( cldr ); pattern = pattern.split( ";" ); negativePattern = pattern[ 1 ] || "-" + pattern[ 0 ]; negativeProperties = numberPatternProperties( negativePattern ); if ( nuDigitsMap ) { invertedNuDigitsMap = nuDigitsMap.split( "" ).reduce(function( object, localizedDigit, i ) { object[ localizedDigit ] = String( i ); return object; }, {} ); invertedNuDigitsMapSanityCheck = "0123456789".split( "" ).reduce(function( object, digit ) { object[ digit ] = "invalid"; return object; }, {} ); invertedNuDigitsMap = objectExtend( invertedNuDigitsMapSanityCheck, invertedNuDigitsMap ); } // 0: @infinitySymbol [String] Infinity symbol. // 1: @invertedSymbolMap [Object] Inverted symbol map augmented with sanity check. // The sanity check prevents permissive parsing, i.e., it prevents symbols that doesn't // belong to the localized set to pass through. This is obtained with the result of the // inverted map object overloading symbol name map object (the remaining symbol name // mappings will invalidate parsing, working as the sanity check). // 2: @negativePrefix [String] Negative prefix. // 3: @negativeSuffix [String] Negative suffix with percent or per mille stripped out. // 4: @invertedNuDigitsMap [Object] Inverted digits map if numbering system is different than // `latn` augmented with sanity check (similar to invertedSymbolMap). return [ numberSymbol( "infinity", cldr ), objectExtend( {}, numberSymbolName, numberSymbolInvertedMap( cldr ) ), negativeProperties[ 0 ], negativeProperties[ 10 ].replace( "%", "" ).replace( "\u2030", "" ), invertedNuDigitsMap ]; }; /** * Pattern( style ) * * @style [String] "decimal" (default) or "percent". * * @cldr [Cldr instance]. */ var numberPattern = function( style, cldr ) { if ( style !== "decimal" && style !== "percent" ) { throw new Error( "Invalid style" ); } return cldr.main([ "numbers", style + "Formats-numberSystem-" + numberNumberingSystem( cldr ), "standard" ]); }; /** * .numberFormatter( [options] ) * * @options [Object]: * - style: [String] "decimal" (default) or "percent". * - see also number/format options. * * Return a function that formats a number according to the given options and default/instance * locale. */ Globalize.numberFormatter = Globalize.prototype.numberFormatter = function( options ) { var cldr, maximumFractionDigits, maximumSignificantDigits, minimumFractionDigits, minimumIntegerDigits, minimumSignificantDigits, pattern, properties; validateParameterTypePlainObject( options, "options" ); options = options || {}; cldr = this.cldr; validateDefaultLocale( cldr ); cldr.on( "get", validateCldr ); if ( options.raw ) { pattern = options.raw; } else { pattern = numberPattern( options.style || "decimal", cldr ); } properties = numberFormatProperties( pattern, cldr, options ); cldr.off( "get", validateCldr ); minimumIntegerDigits = properties[ 2 ]; minimumFractionDigits = properties[ 3 ]; maximumFractionDigits = properties[ 4 ]; minimumSignificantDigits = properties[ 5 ]; maximumSignificantDigits = properties[ 6 ]; // Validate significant digit format properties if ( !isNaN( minimumSignificantDigits * maximumSignificantDigits ) ) { validateParameterRange( minimumSignificantDigits, "minimumSignificantDigits", 1, 21 ); validateParameterRange( maximumSignificantDigits, "maximumSignificantDigits", minimumSignificantDigits, 21 ); } else if ( !isNaN( minimumSignificantDigits ) || !isNaN( maximumSignificantDigits ) ) { throw new Error( "Neither or both the minimum and maximum significant digits must be " + "present" ); // Validate integer and fractional format } else { validateParameterRange( minimumIntegerDigits, "minimumIntegerDigits", 1, 21 ); validateParameterRange( minimumFractionDigits, "minimumFractionDigits", 0, 20 ); validateParameterRange( maximumFractionDigits, "maximumFractionDigits", minimumFractionDigits, 20 ); } return function( value ) { validateParameterPresence( value, "value" ); validateParameterTypeNumber( value, "value" ); return numberFormat( value, properties ); }; }; /** * .numberParser( [options] ) * * @options [Object]: * - style: [String] "decimal" (default) or "percent". * * Return the number parser according to the default/instance locale. */ Globalize.numberParser = Globalize.prototype.numberParser = function( options ) { var cldr, pattern, properties; validateParameterTypePlainObject( options, "options" ); options = options || {}; cldr = this.cldr; validateDefaultLocale( cldr ); cldr.on( "get", validateCldr ); if ( options.raw ) { pattern = options.raw; } else { pattern = numberPattern( options.style || "decimal", cldr ); } properties = numberParseProperties( pattern, cldr ); cldr.off( "get", validateCldr ); return function( value ) { validateParameterPresence( value, "value" ); validateParameterTypeString( value, "value" ); return numberParse( value, properties ); }; }; /** * .formatNumber( value [, options] ) * * @value [Number] number to be formatted. * * @options [Object]: see number/format-properties. * * Format a number according to the given options and default/instance locale. */ Globalize.formatNumber = Globalize.prototype.formatNumber = function( value, options ) { validateParameterPresence( value, "value" ); validateParameterTypeNumber( value, "value" ); return this.numberFormatter( options )( value ); }; /** * .parseNumber( value [, options] ) * * @value [String] * * @options [Object]: See numberParser(). * * Return the parsed Number (including Infinity) or NaN when value is invalid. */ Globalize.parseNumber = Globalize.prototype.parseNumber = function( value, options ) { validateParameterPresence( value, "value" ); validateParameterTypeString( value, "value" ); return this.numberParser( options )( value ); }; /** * Optimization to avoid duplicating some internal functions across modules. */ Globalize._createErrorUnsupportedFeature = createErrorUnsupportedFeature; Globalize._numberNumberingSystem = numberNumberingSystem; Globalize._numberPattern = numberPattern; Globalize._numberSymbol = numberSymbol; Globalize._stringPad = stringPad; Globalize._validateParameterTypeNumber = validateParameterTypeNumber; Globalize._validateParameterTypeString = validateParameterTypeString; return Globalize; }));