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