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