Subversion Repositories Applications.papyrus

Rev

Go to most recent revision | Details | Last modification | View Log | RSS feed

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