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