yavsc/web/Scripts/globalize/number.js

1267 lines
33 KiB
JavaScript

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

/**
* 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 [ <character>, <count> ] 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: <value> } or { exponent: <value> }
*
* 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;
}));