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