]> git.wh0rd.org - tt-rss.git/blame - lib/dojo/number.js
upgrade Dojo to 1.6.1
[tt-rss.git] / lib / dojo / number.js
CommitLineData
2f01fe57 1/*
81bea17a 2 Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved.
2f01fe57
AD
3 Available via Academic Free License >= 2.1 OR the modified BSD license.
4 see: http://dojotoolkit.org/license for details
5*/
6
7
a089699c
AD
8if(!dojo._hasResource["dojo.number"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
9dojo._hasResource["dojo.number"] = true;
2f01fe57
AD
10dojo.provide("dojo.number");
11dojo.require("dojo.i18n");
81bea17a 12dojo.requireLocalization("dojo.cldr", "number", null, "ROOT,ar,ca,cs,da,de,el,en,en-au,en-gb,es,fi,fr,fr-ch,he,hu,it,ja,ko,nb,nl,pl,pt,pt-pt,ro,ru,sk,sl,sv,th,tr,zh,zh-hant,zh-hk");
2f01fe57
AD
13dojo.require("dojo.string");
14dojo.require("dojo.regexp");
a089699c 15
81bea17a 16dojo.getObject("number", true, dojo);
a089699c
AD
17
18/*=====
19dojo.number = {
20 // summary: localized formatting and parsing routines for Number
2f01fe57 21}
a089699c
AD
22
23dojo.number.__FormatOptions = function(){
24 // pattern: String?
25 // override [formatting pattern](http://www.unicode.org/reports/tr35/#Number_Format_Patterns)
26 // with this string. Default value is based on locale. Overriding this property will defeat
27 // localization. Literal characters in patterns are not supported.
28 // type: String?
29 // choose a format type based on the locale from the following:
30 // decimal, scientific (not yet supported), percent, currency. decimal by default.
31 // places: Number?
32 // fixed number of decimal places to show. This overrides any
33 // information in the provided pattern.
34 // round: Number?
35 // 5 rounds to nearest .5; 0 rounds to nearest whole (default). -1
36 // means do not round.
37 // locale: String?
38 // override the locale used to determine formatting rules
39 // fractional: Boolean?
40 // If false, show no decimal places, overriding places and pattern settings.
41 this.pattern = pattern;
42 this.type = type;
43 this.places = places;
44 this.round = round;
45 this.locale = locale;
46 this.fractional = fractional;
47}
48=====*/
49
50dojo.number.format = function(/*Number*/value, /*dojo.number.__FormatOptions?*/options){
51 // summary:
52 // Format a Number as a String, using locale-specific settings
53 // description:
54 // Create a string from a Number using a known localized pattern.
55 // Formatting patterns appropriate to the locale are chosen from the
56 // [Common Locale Data Repository](http://unicode.org/cldr) as well as the appropriate symbols and
57 // delimiters.
58 // If value is Infinity, -Infinity, or is not a valid JavaScript number, return null.
59 // value:
60 // the number to be formatted
61
62 options = dojo.mixin({}, options || {});
63 var locale = dojo.i18n.normalizeLocale(options.locale),
64 bundle = dojo.i18n.getLocalization("dojo.cldr", "number", locale);
65 options.customs = bundle;
66 var pattern = options.pattern || bundle[(options.type || "decimal") + "Format"];
67 if(isNaN(value) || Math.abs(value) == Infinity){ return null; } // null
68 return dojo.number._applyPattern(value, pattern, options); // String
2f01fe57 69};
a089699c
AD
70
71//dojo.number._numberPatternRE = /(?:[#0]*,?)*[#0](?:\.0*#*)?/; // not precise, but good enough
72dojo.number._numberPatternRE = /[#0,]*[#0](?:\.0*#*)?/; // not precise, but good enough
73
74dojo.number._applyPattern = function(/*Number*/value, /*String*/pattern, /*dojo.number.__FormatOptions?*/options){
75 // summary:
76 // Apply pattern to format value as a string using options. Gives no
77 // consideration to local customs.
78 // value:
79 // the number to be formatted.
80 // pattern:
81 // a pattern string as described by
82 // [unicode.org TR35](http://www.unicode.org/reports/tr35/#Number_Format_Patterns)
83 // options: dojo.number.__FormatOptions?
84 // _applyPattern is usually called via `dojo.number.format()` which
85 // populates an extra property in the options parameter, "customs".
86 // The customs object specifies group and decimal parameters if set.
87
88 //TODO: support escapes
89 options = options || {};
90 var group = options.customs.group,
91 decimal = options.customs.decimal,
92 patternList = pattern.split(';'),
93 positivePattern = patternList[0];
94 pattern = patternList[(value < 0) ? 1 : 0] || ("-" + positivePattern);
95
96 //TODO: only test against unescaped
97 if(pattern.indexOf('%') != -1){
98 value *= 100;
99 }else if(pattern.indexOf('\u2030') != -1){
100 value *= 1000; // per mille
101 }else if(pattern.indexOf('\u00a4') != -1){
102 group = options.customs.currencyGroup || group;//mixins instead?
103 decimal = options.customs.currencyDecimal || decimal;// Should these be mixins instead?
104 pattern = pattern.replace(/\u00a4{1,3}/, function(match){
105 var prop = ["symbol", "currency", "displayName"][match.length-1];
106 return options[prop] || options.currency || "";
107 });
108 }else if(pattern.indexOf('E') != -1){
109 throw new Error("exponential notation not supported");
110 }
111
112 //TODO: support @ sig figs?
113 var numberPatternRE = dojo.number._numberPatternRE;
114 var numberPattern = positivePattern.match(numberPatternRE);
115 if(!numberPattern){
116 throw new Error("unable to find a number expression in pattern: "+pattern);
117 }
118 if(options.fractional === false){ options.places = 0; }
119 return pattern.replace(numberPatternRE,
120 dojo.number._formatAbsolute(value, numberPattern[0], {decimal: decimal, group: group, places: options.places, round: options.round}));
81bea17a 121};
a089699c
AD
122
123dojo.number.round = function(/*Number*/value, /*Number?*/places, /*Number?*/increment){
124 // summary:
125 // Rounds to the nearest value with the given number of decimal places, away from zero
126 // description:
127 // Rounds to the nearest value with the given number of decimal places, away from zero if equal.
128 // Similar to Number.toFixed(), but compensates for browser quirks. Rounding can be done by
129 // fractional increments also, such as the nearest quarter.
130 // NOTE: Subject to floating point errors. See dojox.math.round for experimental workaround.
131 // value:
132 // The number to round
133 // places:
134 // The number of decimal places where rounding takes place. Defaults to 0 for whole rounding.
135 // Must be non-negative.
136 // increment:
137 // Rounds next place to nearest value of increment/10. 10 by default.
138 // example:
139 // >>> dojo.number.round(-0.5)
140 // -1
141 // >>> dojo.number.round(162.295, 2)
142 // 162.29 // note floating point error. Should be 162.3
143 // >>> dojo.number.round(10.71, 0, 2.5)
144 // 10.75
145 var factor = 10 / (increment || 10);
146 return (factor * +value).toFixed(places) / factor; // Number
81bea17a 147};
a089699c
AD
148
149if((0.9).toFixed() == 0){
150 // (isIE) toFixed() bug workaround: Rounding fails on IE when most significant digit
151 // is just after the rounding place and is >=5
152 (function(){
153 var round = dojo.number.round;
154 dojo.number.round = function(v, p, m){
155 var d = Math.pow(10, -p || 0), a = Math.abs(v);
156 if(!v || a >= d || a * Math.pow(10, p + 1) < 5){
157 d = 0;
158 }
159 return round(v, p, m) + (v > 0 ? d : -d);
81bea17a 160 };
a089699c 161 })();
2f01fe57 162}
a089699c
AD
163
164/*=====
165dojo.number.__FormatAbsoluteOptions = function(){
166 // decimal: String?
167 // the decimal separator
168 // group: String?
169 // the group separator
170 // places: Number?|String?
171 // number of decimal places. the range "n,m" will format to m places.
172 // round: Number?
173 // 5 rounds to nearest .5; 0 rounds to nearest whole (default). -1
174 // means don't round.
175 this.decimal = decimal;
176 this.group = group;
177 this.places = places;
178 this.round = round;
179}
180=====*/
181
182dojo.number._formatAbsolute = function(/*Number*/value, /*String*/pattern, /*dojo.number.__FormatAbsoluteOptions?*/options){
81bea17a 183 // summary:
a089699c
AD
184 // Apply numeric pattern to absolute value using options. Gives no
185 // consideration to local customs.
186 // value:
187 // the number to be formatted, ignores sign
188 // pattern:
189 // the number portion of a pattern (e.g. `#,##0.00`)
190 options = options || {};
191 if(options.places === true){options.places=0;}
192 if(options.places === Infinity){options.places=6;} // avoid a loop; pick a limit
193
194 var patternParts = pattern.split("."),
195 comma = typeof options.places == "string" && options.places.indexOf(","),
196 maxPlaces = options.places;
197 if(comma){
198 maxPlaces = options.places.substring(comma + 1);
199 }else if(!(maxPlaces >= 0)){
200 maxPlaces = (patternParts[1] || []).length;
201 }
202 if(!(options.round < 0)){
203 value = dojo.number.round(value, maxPlaces, options.round);
204 }
205
206 var valueParts = String(Math.abs(value)).split("."),
207 fractional = valueParts[1] || "";
208 if(patternParts[1] || options.places){
209 if(comma){
210 options.places = options.places.substring(0, comma);
211 }
212 // Pad fractional with trailing zeros
213 var pad = options.places !== undefined ? options.places : (patternParts[1] && patternParts[1].lastIndexOf("0") + 1);
214 if(pad > fractional.length){
215 valueParts[1] = dojo.string.pad(fractional, pad, '0', true);
216 }
217
218 // Truncate fractional
219 if(maxPlaces < fractional.length){
220 valueParts[1] = fractional.substr(0, maxPlaces);
221 }
222 }else{
223 if(valueParts[1]){ valueParts.pop(); }
224 }
225
226 // Pad whole with leading zeros
227 var patternDigits = patternParts[0].replace(',', '');
228 pad = patternDigits.indexOf("0");
229 if(pad != -1){
230 pad = patternDigits.length - pad;
231 if(pad > valueParts[0].length){
232 valueParts[0] = dojo.string.pad(valueParts[0], pad);
233 }
234
235 // Truncate whole
236 if(patternDigits.indexOf("#") == -1){
237 valueParts[0] = valueParts[0].substr(valueParts[0].length - pad);
238 }
239 }
240
241 // Add group separators
242 var index = patternParts[0].lastIndexOf(','),
243 groupSize, groupSize2;
244 if(index != -1){
245 groupSize = patternParts[0].length - index - 1;
246 var remainder = patternParts[0].substr(0, index);
247 index = remainder.lastIndexOf(',');
248 if(index != -1){
249 groupSize2 = remainder.length - index - 1;
250 }
251 }
252 var pieces = [];
253 for(var whole = valueParts[0]; whole;){
254 var off = whole.length - groupSize;
255 pieces.push((off > 0) ? whole.substr(off) : whole);
256 whole = (off > 0) ? whole.slice(0, off) : "";
257 if(groupSize2){
258 groupSize = groupSize2;
259 delete groupSize2;
260 }
261 }
262 valueParts[0] = pieces.reverse().join(options.group || ",");
263
264 return valueParts.join(options.decimal || ".");
2f01fe57 265};
a089699c
AD
266
267/*=====
268dojo.number.__RegexpOptions = function(){
269 // pattern: String?
270 // override [formatting pattern](http://www.unicode.org/reports/tr35/#Number_Format_Patterns)
271 // with this string. Default value is based on locale. Overriding this property will defeat
272 // localization.
273 // type: String?
274 // choose a format type based on the locale from the following:
275 // decimal, scientific (not yet supported), percent, currency. decimal by default.
276 // locale: String?
277 // override the locale used to determine formatting rules
278 // strict: Boolean?
279 // strict parsing, false by default. Strict parsing requires input as produced by the format() method.
280 // Non-strict is more permissive, e.g. flexible on white space, omitting thousands separators
281 // places: Number|String?
282 // number of decimal places to accept: Infinity, a positive number, or
283 // a range "n,m". Defined by pattern or Infinity if pattern not provided.
284 this.pattern = pattern;
285 this.type = type;
286 this.locale = locale;
287 this.strict = strict;
288 this.places = places;
289}
290=====*/
291dojo.number.regexp = function(/*dojo.number.__RegexpOptions?*/options){
292 // summary:
293 // Builds the regular needed to parse a number
294 // description:
295 // Returns regular expression with positive and negative match, group
296 // and decimal separators
297 return dojo.number._parseInfo(options).regexp; // String
81bea17a 298};
a089699c
AD
299
300dojo.number._parseInfo = function(/*Object?*/options){
301 options = options || {};
302 var locale = dojo.i18n.normalizeLocale(options.locale),
303 bundle = dojo.i18n.getLocalization("dojo.cldr", "number", locale),
304 pattern = options.pattern || bundle[(options.type || "decimal") + "Format"],
305//TODO: memoize?
306 group = bundle.group,
307 decimal = bundle.decimal,
308 factor = 1;
309
310 if(pattern.indexOf('%') != -1){
311 factor /= 100;
312 }else if(pattern.indexOf('\u2030') != -1){
313 factor /= 1000; // per mille
314 }else{
315 var isCurrency = pattern.indexOf('\u00a4') != -1;
316 if(isCurrency){
317 group = bundle.currencyGroup || group;
318 decimal = bundle.currencyDecimal || decimal;
319 }
320 }
321
322 //TODO: handle quoted escapes
323 var patternList = pattern.split(';');
324 if(patternList.length == 1){
325 patternList.push("-" + patternList[0]);
326 }
327
328 var re = dojo.regexp.buildGroupRE(patternList, function(pattern){
329 pattern = "(?:"+dojo.regexp.escapeString(pattern, '.')+")";
330 return pattern.replace(dojo.number._numberPatternRE, function(format){
331 var flags = {
332 signed: false,
333 separator: options.strict ? group : [group,""],
334 fractional: options.fractional,
335 decimal: decimal,
336 exponent: false
337 },
338
339 parts = format.split('.'),
340 places = options.places;
341
342 // special condition for percent (factor != 1)
343 // allow decimal places even if not specified in pattern
344 if(parts.length == 1 && factor != 1){
345 parts[1] = "###";
346 }
347 if(parts.length == 1 || places === 0){
348 flags.fractional = false;
349 }else{
350 if(places === undefined){ places = options.pattern ? parts[1].lastIndexOf('0') + 1 : Infinity; }
351 if(places && options.fractional == undefined){flags.fractional = true;} // required fractional, unless otherwise specified
352 if(!options.places && (places < parts[1].length)){ places += "," + parts[1].length; }
353 flags.places = places;
354 }
355 var groups = parts[0].split(',');
356 if(groups.length > 1){
357 flags.groupSize = groups.pop().length;
358 if(groups.length > 1){
359 flags.groupSize2 = groups.pop().length;
360 }
361 }
362 return "("+dojo.number._realNumberRegexp(flags)+")";
363 });
364 }, true);
365
366 if(isCurrency){
367 // substitute the currency symbol for the placeholder in the pattern
368 re = re.replace(/([\s\xa0]*)(\u00a4{1,3})([\s\xa0]*)/g, function(match, before, target, after){
369 var prop = ["symbol", "currency", "displayName"][target.length-1],
370 symbol = dojo.regexp.escapeString(options[prop] || options.currency || "");
371 before = before ? "[\\s\\xa0]" : "";
372 after = after ? "[\\s\\xa0]" : "";
373 if(!options.strict){
374 if(before){before += "*";}
375 if(after){after += "*";}
376 return "(?:"+before+symbol+after+")?";
377 }
378 return before+symbol+after;
379 });
380 }
381
382//TODO: substitute localized sign/percent/permille/etc.?
383
384 // normalize whitespace and return
385 return {regexp: re.replace(/[\xa0 ]/g, "[\\s\\xa0]"), group: group, decimal: decimal, factor: factor}; // Object
81bea17a 386};
a089699c
AD
387
388/*=====
389dojo.number.__ParseOptions = function(){
390 // pattern: String?
391 // override [formatting pattern](http://www.unicode.org/reports/tr35/#Number_Format_Patterns)
392 // with this string. Default value is based on locale. Overriding this property will defeat
393 // localization. Literal characters in patterns are not supported.
394 // type: String?
395 // choose a format type based on the locale from the following:
396 // decimal, scientific (not yet supported), percent, currency. decimal by default.
397 // locale: String?
398 // override the locale used to determine formatting rules
399 // strict: Boolean?
400 // strict parsing, false by default. Strict parsing requires input as produced by the format() method.
401 // Non-strict is more permissive, e.g. flexible on white space, omitting thousands separators
402 // fractional: Boolean?|Array?
403 // Whether to include the fractional portion, where the number of decimal places are implied by pattern
404 // or explicit 'places' parameter. The value [true,false] makes the fractional portion optional.
405 this.pattern = pattern;
406 this.type = type;
407 this.locale = locale;
408 this.strict = strict;
409 this.fractional = fractional;
410}
411=====*/
412dojo.number.parse = function(/*String*/expression, /*dojo.number.__ParseOptions?*/options){
413 // summary:
414 // Convert a properly formatted string to a primitive Number, using
415 // locale-specific settings.
416 // description:
417 // Create a Number from a string using a known localized pattern.
418 // Formatting patterns are chosen appropriate to the locale
419 // and follow the syntax described by
420 // [unicode.org TR35](http://www.unicode.org/reports/tr35/#Number_Format_Patterns)
421 // Note that literal characters in patterns are not supported.
422 // expression:
423 // A string representation of a Number
424 var info = dojo.number._parseInfo(options),
425 results = (new RegExp("^"+info.regexp+"$")).exec(expression);
426 if(!results){
427 return NaN; //NaN
428 }
429 var absoluteMatch = results[1]; // match for the positive expression
430 if(!results[1]){
431 if(!results[2]){
432 return NaN; //NaN
433 }
434 // matched the negative pattern
435 absoluteMatch =results[2];
436 info.factor *= -1;
437 }
438
439 // Transform it to something Javascript can parse as a number. Normalize
440 // decimal point and strip out group separators or alternate forms of whitespace
441 absoluteMatch = absoluteMatch.
442 replace(new RegExp("["+info.group + "\\s\\xa0"+"]", "g"), "").
443 replace(info.decimal, ".");
444 // Adjust for negative sign, percent, etc. as necessary
445 return absoluteMatch * info.factor; //Number
2f01fe57 446};
a089699c
AD
447
448/*=====
449dojo.number.__RealNumberRegexpFlags = function(){
450 // places: Number?
451 // The integer number of decimal places or a range given as "n,m". If
452 // not given, the decimal part is optional and the number of places is
453 // unlimited.
454 // decimal: String?
455 // A string for the character used as the decimal point. Default
456 // is ".".
457 // fractional: Boolean?|Array?
458 // Whether decimal places are used. Can be true, false, or [true,
459 // false]. Default is [true, false] which means optional.
460 // exponent: Boolean?|Array?
461 // Express in exponential notation. Can be true, false, or [true,
462 // false]. Default is [true, false], (i.e. will match if the
463 // exponential part is present are not).
464 // eSigned: Boolean?|Array?
465 // The leading plus-or-minus sign on the exponent. Can be true,
466 // false, or [true, false]. Default is [true, false], (i.e. will
467 // match if it is signed or unsigned). flags in regexp.integer can be
468 // applied.
469 this.places = places;
470 this.decimal = decimal;
471 this.fractional = fractional;
472 this.exponent = exponent;
473 this.eSigned = eSigned;
474}
475=====*/
476
477dojo.number._realNumberRegexp = function(/*dojo.number.__RealNumberRegexpFlags?*/flags){
478 // summary:
479 // Builds a regular expression to match a real number in exponential
480 // notation
481
482 // assign default values to missing parameters
483 flags = flags || {};
484 //TODO: use mixin instead?
485 if(!("places" in flags)){ flags.places = Infinity; }
486 if(typeof flags.decimal != "string"){ flags.decimal = "."; }
487 if(!("fractional" in flags) || /^0/.test(flags.places)){ flags.fractional = [true, false]; }
488 if(!("exponent" in flags)){ flags.exponent = [true, false]; }
489 if(!("eSigned" in flags)){ flags.eSigned = [true, false]; }
490
491 var integerRE = dojo.number._integerRegexp(flags),
492 decimalRE = dojo.regexp.buildGroupRE(flags.fractional,
493 function(q){
494 var re = "";
495 if(q && (flags.places!==0)){
496 re = "\\" + flags.decimal;
81bea17a
AD
497 if(flags.places == Infinity){
498 re = "(?:" + re + "\\d+)?";
a089699c 499 }else{
81bea17a 500 re += "\\d{" + flags.places + "}";
a089699c
AD
501 }
502 }
503 return re;
504 },
505 true
506 );
507
508 var exponentRE = dojo.regexp.buildGroupRE(flags.exponent,
81bea17a 509 function(q){
a089699c 510 if(q){ return "([eE]" + dojo.number._integerRegexp({ signed: flags.eSigned}) + ")"; }
81bea17a 511 return "";
a089699c
AD
512 }
513 );
514
515 var realRE = integerRE + decimalRE;
516 // allow for decimals without integers, e.g. .25
517 if(decimalRE){realRE = "(?:(?:"+ realRE + ")|(?:" + decimalRE + "))";}
518 return realRE + exponentRE; // String
2f01fe57 519};
a089699c
AD
520
521/*=====
522dojo.number.__IntegerRegexpFlags = function(){
523 // signed: Boolean?
524 // The leading plus-or-minus sign. Can be true, false, or `[true,false]`.
525 // Default is `[true, false]`, (i.e. will match if it is signed
526 // or unsigned).
527 // separator: String?
528 // The character used as the thousands separator. Default is no
529 // separator. For more than one symbol use an array, e.g. `[",", ""]`,
530 // makes ',' optional.
531 // groupSize: Number?
532 // group size between separators
533 // groupSize2: Number?
534 // second grouping, where separators 2..n have a different interval than the first separator (for India)
535 this.signed = signed;
536 this.separator = separator;
537 this.groupSize = groupSize;
538 this.groupSize2 = groupSize2;
539}
540=====*/
541
542dojo.number._integerRegexp = function(/*dojo.number.__IntegerRegexpFlags?*/flags){
81bea17a 543 // summary:
a089699c
AD
544 // Builds a regular expression that matches an integer
545
546 // assign default values to missing parameters
547 flags = flags || {};
548 if(!("signed" in flags)){ flags.signed = [true, false]; }
549 if(!("separator" in flags)){
550 flags.separator = "";
551 }else if(!("groupSize" in flags)){
552 flags.groupSize = 3;
553 }
554
555 var signRE = dojo.regexp.buildGroupRE(flags.signed,
556 function(q){ return q ? "[-+]" : ""; },
557 true
558 );
559
560 var numberRE = dojo.regexp.buildGroupRE(flags.separator,
561 function(sep){
562 if(!sep){
563 return "(?:\\d+)";
564 }
565
566 sep = dojo.regexp.escapeString(sep);
567 if(sep == " "){ sep = "\\s"; }
568 else if(sep == "\xa0"){ sep = "\\s\\xa0"; }
569
570 var grp = flags.groupSize, grp2 = flags.groupSize2;
571 //TODO: should we continue to enforce that numbers with separators begin with 1-9? See #6933
572 if(grp2){
573 var grp2RE = "(?:0|[1-9]\\d{0," + (grp2-1) + "}(?:[" + sep + "]\\d{" + grp2 + "})*[" + sep + "]\\d{" + grp + "})";
574 return ((grp-grp2) > 0) ? "(?:" + grp2RE + "|(?:0|[1-9]\\d{0," + (grp-1) + "}))" : grp2RE;
575 }
576 return "(?:0|[1-9]\\d{0," + (grp-1) + "}(?:[" + sep + "]\\d{" + grp + "})*)";
577 },
578 true
579 );
580
581 return signRE + numberRE; // String
81bea17a 582};
a089699c 583
2f01fe57 584}