summaryrefslogtreecommitdiff
path: root/frontend/gamma/js/MochiKit/Text.js
Unidiff
Diffstat (limited to 'frontend/gamma/js/MochiKit/Text.js') (more/less context) (show whitespace changes)
-rw-r--r--frontend/gamma/js/MochiKit/Text.js577
1 files changed, 577 insertions, 0 deletions
diff --git a/frontend/gamma/js/MochiKit/Text.js b/frontend/gamma/js/MochiKit/Text.js
new file mode 100644
index 0000000..a44f7e4
--- a/dev/null
+++ b/frontend/gamma/js/MochiKit/Text.js
@@ -0,0 +1,577 @@
1/***
2
3MochiKit.Text 1.5
4
5See <http://mochikit.com/> for documentation, downloads, license, etc.
6
7(c) 2008 Per Cederberg. All rights Reserved.
8
9***/
10
11MochiKit.Base._module('Text', '1.5', ['Base', 'Format']);
12
13/**
14 * Checks if a text string starts with the specified substring. If
15 * either of the two strings is null, false will be returned.
16 *
17 * @param {String} substr the substring to search for
18 * @param {String} str the string to search in
19 *
20 * @return {Boolean} true if the string starts with the substring, or
21 * false otherwise
22 */
23MochiKit.Text.startsWith = function (substr, str) {
24 return str != null && substr != null && str.indexOf(substr) == 0;
25}
26
27/**
28 * Checks if a text string ends with the specified substring. If
29 * either of the two strings is null, false will be returned.
30 *
31 * @param {String} substr the substring to search for
32 * @param {String} str the string to search in
33 *
34 * @return {Boolean} true if the string ends with the substring, or
35 * false otherwise
36 */
37MochiKit.Text.endsWith = function (substr, str) {
38 return str != null && substr != null &&
39 str.lastIndexOf(substr) == Math.max(str.length - substr.length, 0);
40}
41
42/**
43 * Checks if a text string contains the specified substring. If
44 * either of the two strings is null, false will be returned.
45 *
46 * @param {String} substr the substring to search for
47 * @param {String} str the string to search in
48 *
49 * @return {Boolean} true if the string contains the substring, or
50 * false otherwise
51 */
52MochiKit.Text.contains = function (substr, str) {
53 return str != null && substr != null && str.indexOf(substr) >= 0;
54}
55
56/**
57 * Adds a character to the left-hand side of a string until it
58 * reaches the specified minimum length.
59 *
60 * @param {String} str the string to process
61 * @param {Number} minLength the requested minimum length
62 * @param {String} fillChar the padding character to add, defaults
63 * to a space
64 *
65 * @return {String} the padded string
66 */
67MochiKit.Text.padLeft = function (str, minLength, fillChar) {
68 str = str || "";
69 fillChar = fillChar || " ";
70 while (str.length < minLength) {
71 str = fillChar + str;
72 }
73 return str;
74}
75
76/**
77 * Adds a character to the right-hand side of a string until it
78 * reaches the specified minimum length.
79 *
80 * @param {String} str the string to process
81 * @param {Number} minLength the requested minimum length
82 * @param {String} fillChar the padding character to add, defaults
83 * to a space
84 *
85 * @return {String} the padded string
86 */
87MochiKit.Text.padRight = function (str, minLength, fillChar) {
88 str = str || "";
89 fillChar = fillChar || " ";
90 while (str.length < minLength) {
91 str += fillChar;
92 }
93 return str;
94}
95
96/**
97 * Returns a truncated copy of a string. If the string is shorter
98 * than the specified maximum length, the object will be returned
99 * unmodified. If an optional tail string is specified, additional
100 * elements will be removed in order to accomodate the tail (that
101 * will be appended). This function also works on arrays.
102 *
103 * @param {String} str the string to truncate
104 * @param {Number} maxLength the maximum length
105 * @param {String} [tail] the tail to append on truncation
106 *
107 * @return {String} the truncated string
108 */
109MochiKit.Text.truncate = function (str, maxLength, tail) {
110 if (str == null || str.length <= maxLength || maxLength < 0) {
111 return str;
112 } else if (tail != null) {
113 str = str.slice(0, Math.max(0, maxLength - tail.length));
114 if (typeof(str) == "string") {
115 return str + tail;
116 } else {
117 return MochiKit.Base.extend(str, tail);
118 }
119 } else {
120 return str.slice(0, maxLength);
121 }
122}
123
124/**
125 * Splits a text string, applies a function and joins the results
126 * back together again. This is a convenience function for calling
127 * split(), map() and join() separately. It can be used to easily
128 * trim each line in a text string (using the strip function), or to
129 * translate a text word-by-word.
130 *
131 * @param {Function} func the function to apply
132 * @param {String} str the string to split
133 * @param {String} [separator] the separator character to use,
134 * defaults to newline
135 *
136 * @return {String} a string with the joined up results
137 */
138MochiKit.Text.splitJoin = function (func, str, separator) {
139 if (str == null || str.length == 0) {
140 return str;
141 }
142 separator = separator || '\n'
143 return MochiKit.Base.map(func, str.split(separator)).join(separator);
144}
145
146/**
147 * Creates a formatter function for the specified formatter pattern
148 * and locale. The returned function takes as many arguments as the
149 * formatter pattern requires. See separate documentation for
150 * information about the formatter pattern syntax.
151 *
152 * @param {String} pattern the formatter pattern string
153 * @param {Object} [locale] the locale to use, defaults to
154 * LOCALE.en_US
155 *
156 * @return {Function} the formatter function created
157 *
158 * @throws FormatPatternError if the format pattern was invalid
159 */
160MochiKit.Text.formatter = function (pattern, locale) {
161 if (typeof(locale) == "undefined") {
162 locale = MochiKit.Format.formatLocale();
163 } else if (typeof(locale) == "string") {
164 locale = MochiKit.Format.formatLocale(locale);
165 }
166 var parts = MochiKit.Text._parsePattern(pattern);
167 return function() {
168 var values = MochiKit.Base.extend([], arguments);
169 var res = [];
170 for (var i = 0; i < parts.length; i++) {
171 if (typeof(parts[i]) == "string") {
172 res.push(parts[i]);
173 } else {
174 res.push(MochiKit.Text.formatValue(parts[i], values, locale));
175 }
176 }
177 return res.join("");
178 }
179}
180
181/**
182 * Formats the specified arguments according to a formatter pattern.
183 * See separate documentation for information about the formatter
184 * pattern syntax.
185 *
186 * @param {String} pattern the formatter pattern string
187 * @param {Object} [...] the optional values to format
188 *
189 * @return {String} the formatted output string
190 *
191 * @throws FormatPatternError if the format pattern was invalid
192 */
193MochiKit.Text.format = function (pattern/*, ...*/) {
194 var func = MochiKit.Text.formatter(pattern);
195 return func.apply(this, MochiKit.Base.extend([], arguments, 1));
196}
197
198/**
199 * Format a value with the specified format specifier.
200 *
201 * @param {String/Object} spec the format specifier string or parsed
202 * format specifier object
203 * @param {Object} value the value to format
204 * @param {Object} [locale] the locale to use, defaults to
205 * LOCALE.en_US
206 *
207 * @return {String} the formatted output string
208 */
209MochiKit.Text.formatValue = function (spec, value, locale) {
210 var self = MochiKit.Text;
211 if (typeof(spec) === "string") {
212 spec = self._parseFormatFlags(spec, 0, spec.length - 1);
213 }
214 for (var i = 0; spec.path != null && i < spec.path.length; i++) {
215 if (value != null) {
216 value = value[spec.path[i]];
217 }
218 }
219 if (typeof(locale) == "undefined") {
220 locale = MochiKit.Format.formatLocale();
221 } else if (typeof(locale) == "string") {
222 locale = MochiKit.Format.formatLocale(locale);
223 }
224 var str = "";
225 if (spec.numeric) {
226 if (typeof(value) != "number" || isNaN(value)) {
227 str = "";
228 } else if (value === Number.POSITIVE_INFINITY) {
229 str = "\u221e";
230 } else if (value === Number.NEGATIVE_INFINITY) {
231 str = "-\u221e";
232 } else {
233 var sign = (spec.sign === "-") ? "" : spec.sign;
234 sign = (value < 0) ? "-" : sign;
235 value = Math.abs(value);
236 if (spec.format === "%") {
237 str = self._truncToPercent(value, spec.precision);
238 } else if (spec.format === "d") {
239 str = MochiKit.Format.roundToFixed(value, 0);
240 } else if (spec.radix != 10) {
241 str = Math.floor(value).toString(spec.radix);
242 if (spec.format === "x") {
243 str = str.toLowerCase();
244 } else if (spec.format === "X") {
245 str = str.toUpperCase();
246 }
247 } else if (spec.precision >= 0) {
248 str = MochiKit.Format.roundToFixed(value, spec.precision);
249 } else {
250 str = value.toString();
251 }
252 if (spec.padding === "0" && spec.format === "%") {
253 str = self.padLeft(str, spec.width - sign.length - 1, "0");
254 } else if (spec.padding == "0") {
255 str = self.padLeft(str, spec.width - sign.length, "0");
256 }
257 str = self._localizeNumber(str, locale, spec.grouping);
258 str = sign + str;
259 }
260 if (str !== "" && spec.format === "%") {
261 str = str + locale.percent;
262 }
263 } else {
264 if (spec.format == "r") {
265 str = MochiKit.Base.repr(value);
266 } else {
267 str = (value == null) ? "null" : value.toString();
268 }
269 str = self.truncate(str, spec.precision);
270 }
271 if (spec.align == "<") {
272 str = self.padRight(str, spec.width);
273 } else {
274 str = self.padLeft(str, spec.width);
275 }
276 return str;
277}
278
279/**
280 * Adjust an already formatted numeric string for locale-specific
281 * grouping and decimal separators. The grouping is optional and
282 * will attempt to keep the number string length intact by removing
283 * padded zeros (if possible).
284 *
285 * @param {String} num the formatted number string
286 * @param {Object} locale the formatting locale to use
287 * @param {Boolean} grouping the grouping flag
288 *
289 * @return {String} the localized number string
290 */
291MochiKit.Text._localizeNumber = function (num, locale, grouping) {
292 var parts = num.split(/\./);
293 var whole = parts[0];
294 var frac = (parts.length == 1) ? "" : parts[1];
295 var res = (frac.length > 0) ? locale.decimal : "";
296 while (grouping && frac.length > 3) {
297 res = res + frac.substring(0, 3) + locale.separator;
298 frac = frac.substring(3);
299 if (whole.charAt(0) == "0") {
300 whole = whole.substring(1);
301 }
302 }
303 if (frac.length > 0) {
304 res += frac;
305 }
306 while (grouping && whole.length > 3) {
307 var pos = whole.length - 3;
308 res = locale.separator + whole.substring(pos) + res;
309 whole = whole.substring((whole.charAt(0) == "0") ? 1 : 0, pos);
310 }
311 return whole + res;
312}
313
314/**
315 * Parses a format pattern and returns an array of constant strings
316 * and format info objects.
317 *
318 * @param {String} pattern the format pattern to analyze
319 *
320 * @return {Array} an array of strings and format info objects
321 *
322 * @throws FormatPatternError if the format pattern was invalid
323 */
324MochiKit.Text._parsePattern = function (pattern) {
325 var self = MochiKit.Text;
326 var parts = [];
327 var start = 0;
328 var pos = 0;
329 for (pos = 0; pos < pattern.length; pos++) {
330 if (pattern.charAt(pos) == "{") {
331 if (pos + 1 >= pattern.length) {
332 var msg = "unescaped { char, should be escaped as {{";
333 throw new self.FormatPatternError(pattern, pos, msg);
334 } else if (pattern.charAt(pos + 1) == "{") {
335 parts.push(pattern.substring(start, pos + 1));
336 start = pos + 2;
337 pos++;
338 } else {
339 if (start < pos) {
340 parts.push(pattern.substring(start, pos));
341 }
342 start = pattern.indexOf("}", pos) + 1;
343 if (start <= 0) {
344 var msg = "unmatched { char, not followed by a } char";
345 throw new self.FormatPatternError(pattern, pos, msg);
346 }
347 parts.push(self._parseFormat(pattern, pos + 1, start - 1));
348 pos = start - 1;
349 }
350 } else if (pattern.charAt(pos) == "}") {
351 if (pos + 1 >= pattern.length || pattern.charAt(pos + 1) != "}") {
352 var msg = "unescaped } char, should be escaped as }}";
353 throw new self.FormatPatternError(pattern, pos, msg);
354 }
355 parts.push(pattern.substring(start, pos + 1));
356 start = pos + 2;
357 pos++;
358 }
359 }
360 if (start < pos) {
361 parts.push(pattern.substring(start, pos));
362 }
363 return parts;
364}
365
366/**
367 * Parses a format instruction and returns a format info object.
368 *
369 * @param {String} pattern the format pattern string
370 * @param {Number} startPos the first index of the format instruction
371 * @param {Number} endPos the last index of the format instruction
372 *
373 * @return {Object} the format info object
374 *
375 * @throws FormatPatternError if the format pattern was invalid
376 */
377MochiKit.Text._parseFormat = function (pattern, startPos, endPos) {
378 var self = MochiKit.Text;
379 var text = pattern.substring(startPos, endPos);
380 var info;
381 var pos = text.indexOf(":");
382 if (pos == 0) {
383 info = self._parseFormatFlags(pattern, startPos + 1, endPos);
384 info.path = [0];
385 } else if (pos > 0) {
386 info = self._parseFormatFlags(pattern, startPos + pos + 1, endPos);
387 info.path = text.substring(0, pos).split(".");
388 } else {
389 info = self._parseFormatFlags(pattern, endPos, endPos);
390 info.path = text.split(".");
391 }
392 var DIGITS = /^\d+$/;
393 for (var i = 0; i < info.path.length; i++) {
394 var e = info.path[i];
395 if (typeof(e) == "string") {
396 // TODO: replace with MochiKit.Format.strip?
397 e = e.replace(/^\s+/, "").replace(/\s+$/, "");
398 if (e == "" && info.path.length == 1) {
399 e = 0;
400 } else if (e == "") {
401 var msg = "format value path contains blanks";
402 throw new self.FormatPatternError(pattern, startPos, msg);
403 } else if (DIGITS.test(e)) {
404 e = parseInt(e);
405 }
406 }
407 info.path[i] = e;
408 }
409 if (info.path.length < 0 || typeof(info.path[0]) != "number") {
410 info.path.unshift(0);
411 }
412 return info;
413}
414
415/**
416 * Parses a string with format flags and returns a format info object.
417 *
418 * @param {String} pattern the format pattern string
419 * @param {Number} startPos the first index of the format instruction
420 * @param {Number} endPos the last index of the format instruction
421 *
422 * @return {Object} the format info object
423 *
424 * @throws FormatPatternError if the format pattern was invalid
425 */
426MochiKit.Text._parseFormatFlags = function (pattern, startPos, endPos) {
427 var self = MochiKit.Text;
428 var info = { numeric: false, format: "s", width: 0, precision: -1,
429 align: ">", sign: "-", padding: " ", grouping: false };
430 // TODO: replace with MochiKit.Format.rstrip?
431 var flags = pattern.substring(startPos, endPos).replace(/\s+$/, "");
432 while (flags.length > 0) {
433 switch (flags.charAt(0)) {
434 case ">":
435 case "<":
436 info.align = flags.charAt(0);
437 flags = flags.substring(1);
438 break;
439 case "+":
440 case "-":
441 case " ":
442 info.sign = flags.charAt(0);
443 flags = flags.substring(1);
444 break;
445 case ",":
446 info.grouping = true;
447 flags = flags.substring(1);
448 break;
449 case ".":
450 var chars = /^\d*/.exec(flags.substring(1))[0];
451 info.precision = parseInt(chars);
452 flags = flags.substring(1 + chars.length);
453 break;
454 case "0":
455 info.padding = flags.charAt(0);
456 flags = flags.substring(1);
457 break;
458 case "1":
459 case "2":
460 case "3":
461 case "4":
462 case "5":
463 case "6":
464 case "7":
465 case "8":
466 case "9":
467 var chars = /^\d*/.exec(flags)[0];
468 info.width = parseInt(chars);
469 flags = flags.substring(chars.length);
470 break;
471 case "s":
472 case "r":
473 info.format = flags.charAt(0);
474 flags = flags.substring(1);
475 break;
476 case "b":
477 case "d":
478 case "o":
479 case "x":
480 case "X":
481 case "f":
482 case "%":
483 info.numeric = true;
484 info.format = flags.charAt(0);
485 info.radix = 10;
486 if (info.format === "b") {
487 info.radix = 2;
488 } else if (info.format === "o") {
489 info.radix = 8;
490 } else if (info.format === "x" || info.format === "X") {
491 info.radix = 16;
492 }
493 flags = flags.substring(1);
494 break;
495 default:
496 var msg = "unsupported format flag: " + flags.charAt(0);
497 throw new self.FormatPatternError(pattern, startPos, msg);
498 }
499 }
500 return info;
501}
502
503/**
504 * Formats a value as a percentage. This method avoids multiplication
505 * by 100 since it leads to weird numeric rounding errors. Instead it
506 * just move the decimal separator in the text string. It is ugly,
507 * but works...
508 *
509 * @param {Number} value the value to format
510 * @param {Number} precision the number of precision digits
511 */
512MochiKit.Text._truncToPercent = function (value, precision) {
513 // TODO: This can be simplified by using the same helper function
514 // as roundToFixed now does.
515 var str;
516 if (precision >= 0) {
517 str = MochiKit.Format.roundToFixed(value, precision + 2);
518 } else {
519 str = (value == null) ? "0" : value.toString();
520 }
521 var fracPos = str.indexOf(".");
522 if (fracPos < 0) {
523 str = str + "00";
524 } else if (fracPos + 3 >= str.length) {
525 var fraction = str.substring(fracPos + 1);
526 while (fraction.length < 2) {
527 fraction = fraction + "0";
528 }
529 str = str.substring(0, fracPos) + fraction;
530 } else {
531 var fraction = str.substring(fracPos + 1);
532 str = str.substring(0, fracPos) + fraction.substring(0, 2) +
533 "." + fraction.substring(2);
534 }
535 while (str.length > 1 && str.charAt(0) == "0" && str.charAt(1) != ".") {
536 str = str.substring(1);
537 }
538 return str;
539}
540
541/**
542 * Creates a new format pattern error.
543 *
544 * @param {String} pattern the format pattern string
545 * @param {Number} pos the position of the error
546 * @param {String} message the error message text
547 *
548 * @return {Error} the format pattern error
549 *
550 * @class The format pattern error class. This error is thrown when
551 * a syntax error is encountered inside a format string.
552 * @property {String} pattern The format pattern string.
553 * @property {Number} pos The position of the error.
554 * @property {String} message The error message text.
555 * @extends MochiKit.Base.NamedError
556 */
557MochiKit.Text.FormatPatternError = function (pattern, pos, message) {
558 this.pattern = pattern;
559 this.pos = pos;
560 this.message = message;
561}
562MochiKit.Text.FormatPatternError.prototype =
563 new MochiKit.Base.NamedError("MochiKit.Text.FormatPatternError");
564
565
566//
567//XXX: Internet Explorer exception handling blows
568//
569if (MochiKit.__export__) {
570 formatter = MochiKit.Text.formatter;
571 format = MochiKit.Text.format;
572 formatValue = MochiKit.Text.formatValue;
573}
574
575
576MochiKit.Base.nameFunctions(MochiKit.Text);
577MochiKit.Base._exportSymbols(this, MochiKit.Text);