lib/goog/string/string.js

1// Copyright 2006 The Closure Library Authors. All Rights Reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS-IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15/**
16 * @fileoverview Utilities for string manipulation.
17 */
18
19
20/**
21 * Namespace for string utilities
22 */
23goog.provide('goog.string');
24goog.provide('goog.string.Unicode');
25
26
27/**
28 * @define {boolean} Enables HTML escaping of lowercase letter "e" which helps
29 * with detection of double-escaping as this letter is frequently used.
30 */
31goog.define('goog.string.DETECT_DOUBLE_ESCAPING', false);
32
33
34/**
35 * Common Unicode string characters.
36 * @enum {string}
37 */
38goog.string.Unicode = {
39 NBSP: '\xa0'
40};
41
42
43/**
44 * Fast prefix-checker.
45 * @param {string} str The string to check.
46 * @param {string} prefix A string to look for at the start of {@code str}.
47 * @return {boolean} True if {@code str} begins with {@code prefix}.
48 */
49goog.string.startsWith = function(str, prefix) {
50 return str.lastIndexOf(prefix, 0) == 0;
51};
52
53
54/**
55 * Fast suffix-checker.
56 * @param {string} str The string to check.
57 * @param {string} suffix A string to look for at the end of {@code str}.
58 * @return {boolean} True if {@code str} ends with {@code suffix}.
59 */
60goog.string.endsWith = function(str, suffix) {
61 var l = str.length - suffix.length;
62 return l >= 0 && str.indexOf(suffix, l) == l;
63};
64
65
66/**
67 * Case-insensitive prefix-checker.
68 * @param {string} str The string to check.
69 * @param {string} prefix A string to look for at the end of {@code str}.
70 * @return {boolean} True if {@code str} begins with {@code prefix} (ignoring
71 * case).
72 */
73goog.string.caseInsensitiveStartsWith = function(str, prefix) {
74 return goog.string.caseInsensitiveCompare(
75 prefix, str.substr(0, prefix.length)) == 0;
76};
77
78
79/**
80 * Case-insensitive suffix-checker.
81 * @param {string} str The string to check.
82 * @param {string} suffix A string to look for at the end of {@code str}.
83 * @return {boolean} True if {@code str} ends with {@code suffix} (ignoring
84 * case).
85 */
86goog.string.caseInsensitiveEndsWith = function(str, suffix) {
87 return goog.string.caseInsensitiveCompare(
88 suffix, str.substr(str.length - suffix.length, suffix.length)) == 0;
89};
90
91
92/**
93 * Case-insensitive equality checker.
94 * @param {string} str1 First string to check.
95 * @param {string} str2 Second string to check.
96 * @return {boolean} True if {@code str1} and {@code str2} are the same string,
97 * ignoring case.
98 */
99goog.string.caseInsensitiveEquals = function(str1, str2) {
100 return str1.toLowerCase() == str2.toLowerCase();
101};
102
103
104/**
105 * Does simple python-style string substitution.
106 * subs("foo%s hot%s", "bar", "dog") becomes "foobar hotdog".
107 * @param {string} str The string containing the pattern.
108 * @param {...*} var_args The items to substitute into the pattern.
109 * @return {string} A copy of {@code str} in which each occurrence of
110 * {@code %s} has been replaced an argument from {@code var_args}.
111 */
112goog.string.subs = function(str, var_args) {
113 var splitParts = str.split('%s');
114 var returnString = '';
115
116 var subsArguments = Array.prototype.slice.call(arguments, 1);
117 while (subsArguments.length &&
118 // Replace up to the last split part. We are inserting in the
119 // positions between split parts.
120 splitParts.length > 1) {
121 returnString += splitParts.shift() + subsArguments.shift();
122 }
123
124 return returnString + splitParts.join('%s'); // Join unused '%s'
125};
126
127
128/**
129 * Converts multiple whitespace chars (spaces, non-breaking-spaces, new lines
130 * and tabs) to a single space, and strips leading and trailing whitespace.
131 * @param {string} str Input string.
132 * @return {string} A copy of {@code str} with collapsed whitespace.
133 */
134goog.string.collapseWhitespace = function(str) {
135 // Since IE doesn't include non-breaking-space (0xa0) in their \s character
136 // class (as required by section 7.2 of the ECMAScript spec), we explicitly
137 // include it in the regexp to enforce consistent cross-browser behavior.
138 return str.replace(/[\s\xa0]+/g, ' ').replace(/^\s+|\s+$/g, '');
139};
140
141
142/**
143 * Checks if a string is empty or contains only whitespaces.
144 * @param {string} str The string to check.
145 * @return {boolean} True if {@code str} is empty or whitespace only.
146 */
147goog.string.isEmpty = function(str) {
148 // testing length == 0 first is actually slower in all browsers (about the
149 // same in Opera).
150 // Since IE doesn't include non-breaking-space (0xa0) in their \s character
151 // class (as required by section 7.2 of the ECMAScript spec), we explicitly
152 // include it in the regexp to enforce consistent cross-browser behavior.
153 return /^[\s\xa0]*$/.test(str);
154};
155
156
157/**
158 * Checks if a string is null, undefined, empty or contains only whitespaces.
159 * @param {*} str The string to check.
160 * @return {boolean} True if{@code str} is null, undefined, empty, or
161 * whitespace only.
162 */
163goog.string.isEmptySafe = function(str) {
164 return goog.string.isEmpty(goog.string.makeSafe(str));
165};
166
167
168/**
169 * Checks if a string is all breaking whitespace.
170 * @param {string} str The string to check.
171 * @return {boolean} Whether the string is all breaking whitespace.
172 */
173goog.string.isBreakingWhitespace = function(str) {
174 return !/[^\t\n\r ]/.test(str);
175};
176
177
178/**
179 * Checks if a string contains all letters.
180 * @param {string} str string to check.
181 * @return {boolean} True if {@code str} consists entirely of letters.
182 */
183goog.string.isAlpha = function(str) {
184 return !/[^a-zA-Z]/.test(str);
185};
186
187
188/**
189 * Checks if a string contains only numbers.
190 * @param {*} str string to check. If not a string, it will be
191 * casted to one.
192 * @return {boolean} True if {@code str} is numeric.
193 */
194goog.string.isNumeric = function(str) {
195 return !/[^0-9]/.test(str);
196};
197
198
199/**
200 * Checks if a string contains only numbers or letters.
201 * @param {string} str string to check.
202 * @return {boolean} True if {@code str} is alphanumeric.
203 */
204goog.string.isAlphaNumeric = function(str) {
205 return !/[^a-zA-Z0-9]/.test(str);
206};
207
208
209/**
210 * Checks if a character is a space character.
211 * @param {string} ch Character to check.
212 * @return {boolean} True if {code ch} is a space.
213 */
214goog.string.isSpace = function(ch) {
215 return ch == ' ';
216};
217
218
219/**
220 * Checks if a character is a valid unicode character.
221 * @param {string} ch Character to check.
222 * @return {boolean} True if {code ch} is a valid unicode character.
223 */
224goog.string.isUnicodeChar = function(ch) {
225 return ch.length == 1 && ch >= ' ' && ch <= '~' ||
226 ch >= '\u0080' && ch <= '\uFFFD';
227};
228
229
230/**
231 * Takes a string and replaces newlines with a space. Multiple lines are
232 * replaced with a single space.
233 * @param {string} str The string from which to strip newlines.
234 * @return {string} A copy of {@code str} stripped of newlines.
235 */
236goog.string.stripNewlines = function(str) {
237 return str.replace(/(\r\n|\r|\n)+/g, ' ');
238};
239
240
241/**
242 * Replaces Windows and Mac new lines with unix style: \r or \r\n with \n.
243 * @param {string} str The string to in which to canonicalize newlines.
244 * @return {string} {@code str} A copy of {@code} with canonicalized newlines.
245 */
246goog.string.canonicalizeNewlines = function(str) {
247 return str.replace(/(\r\n|\r|\n)/g, '\n');
248};
249
250
251/**
252 * Normalizes whitespace in a string, replacing all whitespace chars with
253 * a space.
254 * @param {string} str The string in which to normalize whitespace.
255 * @return {string} A copy of {@code str} with all whitespace normalized.
256 */
257goog.string.normalizeWhitespace = function(str) {
258 return str.replace(/\xa0|\s/g, ' ');
259};
260
261
262/**
263 * Normalizes spaces in a string, replacing all consecutive spaces and tabs
264 * with a single space. Replaces non-breaking space with a space.
265 * @param {string} str The string in which to normalize spaces.
266 * @return {string} A copy of {@code str} with all consecutive spaces and tabs
267 * replaced with a single space.
268 */
269goog.string.normalizeSpaces = function(str) {
270 return str.replace(/\xa0|[ \t]+/g, ' ');
271};
272
273
274/**
275 * Removes the breaking spaces from the left and right of the string and
276 * collapses the sequences of breaking spaces in the middle into single spaces.
277 * The original and the result strings render the same way in HTML.
278 * @param {string} str A string in which to collapse spaces.
279 * @return {string} Copy of the string with normalized breaking spaces.
280 */
281goog.string.collapseBreakingSpaces = function(str) {
282 return str.replace(/[\t\r\n ]+/g, ' ').replace(
283 /^[\t\r\n ]+|[\t\r\n ]+$/g, '');
284};
285
286
287/**
288 * Trims white spaces to the left and right of a string.
289 * @param {string} str The string to trim.
290 * @return {string} A trimmed copy of {@code str}.
291 */
292goog.string.trim = function(str) {
293 // Since IE doesn't include non-breaking-space (0xa0) in their \s character
294 // class (as required by section 7.2 of the ECMAScript spec), we explicitly
295 // include it in the regexp to enforce consistent cross-browser behavior.
296 return str.replace(/^[\s\xa0]+|[\s\xa0]+$/g, '');
297};
298
299
300/**
301 * Trims whitespaces at the left end of a string.
302 * @param {string} str The string to left trim.
303 * @return {string} A trimmed copy of {@code str}.
304 */
305goog.string.trimLeft = function(str) {
306 // Since IE doesn't include non-breaking-space (0xa0) in their \s character
307 // class (as required by section 7.2 of the ECMAScript spec), we explicitly
308 // include it in the regexp to enforce consistent cross-browser behavior.
309 return str.replace(/^[\s\xa0]+/, '');
310};
311
312
313/**
314 * Trims whitespaces at the right end of a string.
315 * @param {string} str The string to right trim.
316 * @return {string} A trimmed copy of {@code str}.
317 */
318goog.string.trimRight = function(str) {
319 // Since IE doesn't include non-breaking-space (0xa0) in their \s character
320 // class (as required by section 7.2 of the ECMAScript spec), we explicitly
321 // include it in the regexp to enforce consistent cross-browser behavior.
322 return str.replace(/[\s\xa0]+$/, '');
323};
324
325
326/**
327 * A string comparator that ignores case.
328 * -1 = str1 less than str2
329 * 0 = str1 equals str2
330 * 1 = str1 greater than str2
331 *
332 * @param {string} str1 The string to compare.
333 * @param {string} str2 The string to compare {@code str1} to.
334 * @return {number} The comparator result, as described above.
335 */
336goog.string.caseInsensitiveCompare = function(str1, str2) {
337 var test1 = String(str1).toLowerCase();
338 var test2 = String(str2).toLowerCase();
339
340 if (test1 < test2) {
341 return -1;
342 } else if (test1 == test2) {
343 return 0;
344 } else {
345 return 1;
346 }
347};
348
349
350/**
351 * Regular expression used for splitting a string into substrings of fractional
352 * numbers, integers, and non-numeric characters.
353 * @type {RegExp}
354 * @private
355 */
356goog.string.numerateCompareRegExp_ = /(\.\d+)|(\d+)|(\D+)/g;
357
358
359/**
360 * String comparison function that handles numbers in a way humans might expect.
361 * Using this function, the string "File 2.jpg" sorts before "File 10.jpg". The
362 * comparison is mostly case-insensitive, though strings that are identical
363 * except for case are sorted with the upper-case strings before lower-case.
364 *
365 * This comparison function is significantly slower (about 500x) than either
366 * the default or the case-insensitive compare. It should not be used in
367 * time-critical code, but should be fast enough to sort several hundred short
368 * strings (like filenames) with a reasonable delay.
369 *
370 * @param {string} str1 The string to compare in a numerically sensitive way.
371 * @param {string} str2 The string to compare {@code str1} to.
372 * @return {number} less than 0 if str1 < str2, 0 if str1 == str2, greater than
373 * 0 if str1 > str2.
374 */
375goog.string.numerateCompare = function(str1, str2) {
376 if (str1 == str2) {
377 return 0;
378 }
379 if (!str1) {
380 return -1;
381 }
382 if (!str2) {
383 return 1;
384 }
385
386 // Using match to split the entire string ahead of time turns out to be faster
387 // for most inputs than using RegExp.exec or iterating over each character.
388 var tokens1 = str1.toLowerCase().match(goog.string.numerateCompareRegExp_);
389 var tokens2 = str2.toLowerCase().match(goog.string.numerateCompareRegExp_);
390
391 var count = Math.min(tokens1.length, tokens2.length);
392
393 for (var i = 0; i < count; i++) {
394 var a = tokens1[i];
395 var b = tokens2[i];
396
397 // Compare pairs of tokens, returning if one token sorts before the other.
398 if (a != b) {
399
400 // Only if both tokens are integers is a special comparison required.
401 // Decimal numbers are sorted as strings (e.g., '.09' < '.1').
402 var num1 = parseInt(a, 10);
403 if (!isNaN(num1)) {
404 var num2 = parseInt(b, 10);
405 if (!isNaN(num2) && num1 - num2) {
406 return num1 - num2;
407 }
408 }
409 return a < b ? -1 : 1;
410 }
411 }
412
413 // If one string is a substring of the other, the shorter string sorts first.
414 if (tokens1.length != tokens2.length) {
415 return tokens1.length - tokens2.length;
416 }
417
418 // The two strings must be equivalent except for case (perfect equality is
419 // tested at the head of the function.) Revert to default ASCII-betical string
420 // comparison to stablize the sort.
421 return str1 < str2 ? -1 : 1;
422};
423
424
425/**
426 * URL-encodes a string
427 * @param {*} str The string to url-encode.
428 * @return {string} An encoded copy of {@code str} that is safe for urls.
429 * Note that '#', ':', and other characters used to delimit portions
430 * of URLs *will* be encoded.
431 */
432goog.string.urlEncode = function(str) {
433 return encodeURIComponent(String(str));
434};
435
436
437/**
438 * URL-decodes the string. We need to specially handle '+'s because
439 * the javascript library doesn't convert them to spaces.
440 * @param {string} str The string to url decode.
441 * @return {string} The decoded {@code str}.
442 */
443goog.string.urlDecode = function(str) {
444 return decodeURIComponent(str.replace(/\+/g, ' '));
445};
446
447
448/**
449 * Converts \n to <br>s or <br />s.
450 * @param {string} str The string in which to convert newlines.
451 * @param {boolean=} opt_xml Whether to use XML compatible tags.
452 * @return {string} A copy of {@code str} with converted newlines.
453 */
454goog.string.newLineToBr = function(str, opt_xml) {
455 return str.replace(/(\r\n|\r|\n)/g, opt_xml ? '<br />' : '<br>');
456};
457
458
459/**
460 * Escapes double quote '"' and single quote '\'' characters in addition to
461 * '&', '<', and '>' so that a string can be included in an HTML tag attribute
462 * value within double or single quotes.
463 *
464 * It should be noted that > doesn't need to be escaped for the HTML or XML to
465 * be valid, but it has been decided to escape it for consistency with other
466 * implementations.
467 *
468 * With goog.string.DETECT_DOUBLE_ESCAPING, this function escapes also the
469 * lowercase letter "e".
470 *
471 * NOTE(user):
472 * HtmlEscape is often called during the generation of large blocks of HTML.
473 * Using statics for the regular expressions and strings is an optimization
474 * that can more than half the amount of time IE spends in this function for
475 * large apps, since strings and regexes both contribute to GC allocations.
476 *
477 * Testing for the presence of a character before escaping increases the number
478 * of function calls, but actually provides a speed increase for the average
479 * case -- since the average case often doesn't require the escaping of all 4
480 * characters and indexOf() is much cheaper than replace().
481 * The worst case does suffer slightly from the additional calls, therefore the
482 * opt_isLikelyToContainHtmlChars option has been included for situations
483 * where all 4 HTML entities are very likely to be present and need escaping.
484 *
485 * Some benchmarks (times tended to fluctuate +-0.05ms):
486 * FireFox IE6
487 * (no chars / average (mix of cases) / all 4 chars)
488 * no checks 0.13 / 0.22 / 0.22 0.23 / 0.53 / 0.80
489 * indexOf 0.08 / 0.17 / 0.26 0.22 / 0.54 / 0.84
490 * indexOf + re test 0.07 / 0.17 / 0.28 0.19 / 0.50 / 0.85
491 *
492 * An additional advantage of checking if replace actually needs to be called
493 * is a reduction in the number of object allocations, so as the size of the
494 * application grows the difference between the various methods would increase.
495 *
496 * @param {string} str string to be escaped.
497 * @param {boolean=} opt_isLikelyToContainHtmlChars Don't perform a check to see
498 * if the character needs replacing - use this option if you expect each of
499 * the characters to appear often. Leave false if you expect few html
500 * characters to occur in your strings, such as if you are escaping HTML.
501 * @return {string} An escaped copy of {@code str}.
502 */
503goog.string.htmlEscape = function(str, opt_isLikelyToContainHtmlChars) {
504
505 if (opt_isLikelyToContainHtmlChars) {
506 str = str.replace(goog.string.AMP_RE_, '&amp;')
507 .replace(goog.string.LT_RE_, '&lt;')
508 .replace(goog.string.GT_RE_, '&gt;')
509 .replace(goog.string.QUOT_RE_, '&quot;')
510 .replace(goog.string.SINGLE_QUOTE_RE_, '&#39;')
511 .replace(goog.string.NULL_RE_, '&#0;');
512 if (goog.string.DETECT_DOUBLE_ESCAPING) {
513 str = str.replace(goog.string.E_RE_, '&#101;');
514 }
515 return str;
516
517 } else {
518 // quick test helps in the case when there are no chars to replace, in
519 // worst case this makes barely a difference to the time taken
520 if (!goog.string.ALL_RE_.test(str)) return str;
521
522 // str.indexOf is faster than regex.test in this case
523 if (str.indexOf('&') != -1) {
524 str = str.replace(goog.string.AMP_RE_, '&amp;');
525 }
526 if (str.indexOf('<') != -1) {
527 str = str.replace(goog.string.LT_RE_, '&lt;');
528 }
529 if (str.indexOf('>') != -1) {
530 str = str.replace(goog.string.GT_RE_, '&gt;');
531 }
532 if (str.indexOf('"') != -1) {
533 str = str.replace(goog.string.QUOT_RE_, '&quot;');
534 }
535 if (str.indexOf('\'') != -1) {
536 str = str.replace(goog.string.SINGLE_QUOTE_RE_, '&#39;');
537 }
538 if (str.indexOf('\x00') != -1) {
539 str = str.replace(goog.string.NULL_RE_, '&#0;');
540 }
541 if (goog.string.DETECT_DOUBLE_ESCAPING && str.indexOf('e') != -1) {
542 str = str.replace(goog.string.E_RE_, '&#101;');
543 }
544 return str;
545 }
546};
547
548
549/**
550 * Regular expression that matches an ampersand, for use in escaping.
551 * @const {!RegExp}
552 * @private
553 */
554goog.string.AMP_RE_ = /&/g;
555
556
557/**
558 * Regular expression that matches a less than sign, for use in escaping.
559 * @const {!RegExp}
560 * @private
561 */
562goog.string.LT_RE_ = /</g;
563
564
565/**
566 * Regular expression that matches a greater than sign, for use in escaping.
567 * @const {!RegExp}
568 * @private
569 */
570goog.string.GT_RE_ = />/g;
571
572
573/**
574 * Regular expression that matches a double quote, for use in escaping.
575 * @const {!RegExp}
576 * @private
577 */
578goog.string.QUOT_RE_ = /"/g;
579
580
581/**
582 * Regular expression that matches a single quote, for use in escaping.
583 * @const {!RegExp}
584 * @private
585 */
586goog.string.SINGLE_QUOTE_RE_ = /'/g;
587
588
589/**
590 * Regular expression that matches null character, for use in escaping.
591 * @const {!RegExp}
592 * @private
593 */
594goog.string.NULL_RE_ = /\x00/g;
595
596
597/**
598 * Regular expression that matches a lowercase letter "e", for use in escaping.
599 * @const {!RegExp}
600 * @private
601 */
602goog.string.E_RE_ = /e/g;
603
604
605/**
606 * Regular expression that matches any character that needs to be escaped.
607 * @const {!RegExp}
608 * @private
609 */
610goog.string.ALL_RE_ = (goog.string.DETECT_DOUBLE_ESCAPING ?
611 /[\x00&<>"'e]/ :
612 /[\x00&<>"']/);
613
614
615/**
616 * Unescapes an HTML string.
617 *
618 * @param {string} str The string to unescape.
619 * @return {string} An unescaped copy of {@code str}.
620 */
621goog.string.unescapeEntities = function(str) {
622 if (goog.string.contains(str, '&')) {
623 // We are careful not to use a DOM if we do not have one. We use the []
624 // notation so that the JSCompiler will not complain about these objects and
625 // fields in the case where we have no DOM.
626 if ('document' in goog.global) {
627 return goog.string.unescapeEntitiesUsingDom_(str);
628 } else {
629 // Fall back on pure XML entities
630 return goog.string.unescapePureXmlEntities_(str);
631 }
632 }
633 return str;
634};
635
636
637/**
638 * Unescapes a HTML string using the provided document.
639 *
640 * @param {string} str The string to unescape.
641 * @param {!Document} document A document to use in escaping the string.
642 * @return {string} An unescaped copy of {@code str}.
643 */
644goog.string.unescapeEntitiesWithDocument = function(str, document) {
645 if (goog.string.contains(str, '&')) {
646 return goog.string.unescapeEntitiesUsingDom_(str, document);
647 }
648 return str;
649};
650
651
652/**
653 * Unescapes an HTML string using a DOM to resolve non-XML, non-numeric
654 * entities. This function is XSS-safe and whitespace-preserving.
655 * @private
656 * @param {string} str The string to unescape.
657 * @param {Document=} opt_document An optional document to use for creating
658 * elements. If this is not specified then the default window.document
659 * will be used.
660 * @return {string} The unescaped {@code str} string.
661 */
662goog.string.unescapeEntitiesUsingDom_ = function(str, opt_document) {
663 var seen = {'&amp;': '&', '&lt;': '<', '&gt;': '>', '&quot;': '"'};
664 var div;
665 if (opt_document) {
666 div = opt_document.createElement('div');
667 } else {
668 div = goog.global.document.createElement('div');
669 }
670 // Match as many valid entity characters as possible. If the actual entity
671 // happens to be shorter, it will still work as innerHTML will return the
672 // trailing characters unchanged. Since the entity characters do not include
673 // open angle bracket, there is no chance of XSS from the innerHTML use.
674 // Since no whitespace is passed to innerHTML, whitespace is preserved.
675 return str.replace(goog.string.HTML_ENTITY_PATTERN_, function(s, entity) {
676 // Check for cached entity.
677 var value = seen[s];
678 if (value) {
679 return value;
680 }
681 // Check for numeric entity.
682 if (entity.charAt(0) == '#') {
683 // Prefix with 0 so that hex entities (e.g. &#x10) parse as hex numbers.
684 var n = Number('0' + entity.substr(1));
685 if (!isNaN(n)) {
686 value = String.fromCharCode(n);
687 }
688 }
689 // Fall back to innerHTML otherwise.
690 if (!value) {
691 // Append a non-entity character to avoid a bug in Webkit that parses
692 // an invalid entity at the end of innerHTML text as the empty string.
693 div.innerHTML = s + ' ';
694 // Then remove the trailing character from the result.
695 value = div.firstChild.nodeValue.slice(0, -1);
696 }
697 // Cache and return.
698 return seen[s] = value;
699 });
700};
701
702
703/**
704 * Unescapes XML entities.
705 * @private
706 * @param {string} str The string to unescape.
707 * @return {string} An unescaped copy of {@code str}.
708 */
709goog.string.unescapePureXmlEntities_ = function(str) {
710 return str.replace(/&([^;]+);/g, function(s, entity) {
711 switch (entity) {
712 case 'amp':
713 return '&';
714 case 'lt':
715 return '<';
716 case 'gt':
717 return '>';
718 case 'quot':
719 return '"';
720 default:
721 if (entity.charAt(0) == '#') {
722 // Prefix with 0 so that hex entities (e.g. &#x10) parse as hex.
723 var n = Number('0' + entity.substr(1));
724 if (!isNaN(n)) {
725 return String.fromCharCode(n);
726 }
727 }
728 // For invalid entities we just return the entity
729 return s;
730 }
731 });
732};
733
734
735/**
736 * Regular expression that matches an HTML entity.
737 * See also HTML5: Tokenization / Tokenizing character references.
738 * @private
739 * @type {!RegExp}
740 */
741goog.string.HTML_ENTITY_PATTERN_ = /&([^;\s<&]+);?/g;
742
743
744/**
745 * Do escaping of whitespace to preserve spatial formatting. We use character
746 * entity #160 to make it safer for xml.
747 * @param {string} str The string in which to escape whitespace.
748 * @param {boolean=} opt_xml Whether to use XML compatible tags.
749 * @return {string} An escaped copy of {@code str}.
750 */
751goog.string.whitespaceEscape = function(str, opt_xml) {
752 // This doesn't use goog.string.preserveSpaces for backwards compatibility.
753 return goog.string.newLineToBr(str.replace(/ /g, ' &#160;'), opt_xml);
754};
755
756
757/**
758 * Preserve spaces that would be otherwise collapsed in HTML by replacing them
759 * with non-breaking space Unicode characters.
760 * @param {string} str The string in which to preserve whitespace.
761 * @return {string} A copy of {@code str} with preserved whitespace.
762 */
763goog.string.preserveSpaces = function(str) {
764 return str.replace(/(^|[\n ]) /g, '$1' + goog.string.Unicode.NBSP);
765};
766
767
768/**
769 * Strip quote characters around a string. The second argument is a string of
770 * characters to treat as quotes. This can be a single character or a string of
771 * multiple character and in that case each of those are treated as possible
772 * quote characters. For example:
773 *
774 * <pre>
775 * goog.string.stripQuotes('"abc"', '"`') --> 'abc'
776 * goog.string.stripQuotes('`abc`', '"`') --> 'abc'
777 * </pre>
778 *
779 * @param {string} str The string to strip.
780 * @param {string} quoteChars The quote characters to strip.
781 * @return {string} A copy of {@code str} without the quotes.
782 */
783goog.string.stripQuotes = function(str, quoteChars) {
784 var length = quoteChars.length;
785 for (var i = 0; i < length; i++) {
786 var quoteChar = length == 1 ? quoteChars : quoteChars.charAt(i);
787 if (str.charAt(0) == quoteChar && str.charAt(str.length - 1) == quoteChar) {
788 return str.substring(1, str.length - 1);
789 }
790 }
791 return str;
792};
793
794
795/**
796 * Truncates a string to a certain length and adds '...' if necessary. The
797 * length also accounts for the ellipsis, so a maximum length of 10 and a string
798 * 'Hello World!' produces 'Hello W...'.
799 * @param {string} str The string to truncate.
800 * @param {number} chars Max number of characters.
801 * @param {boolean=} opt_protectEscapedCharacters Whether to protect escaped
802 * characters from being cut off in the middle.
803 * @return {string} The truncated {@code str} string.
804 */
805goog.string.truncate = function(str, chars, opt_protectEscapedCharacters) {
806 if (opt_protectEscapedCharacters) {
807 str = goog.string.unescapeEntities(str);
808 }
809
810 if (str.length > chars) {
811 str = str.substring(0, chars - 3) + '...';
812 }
813
814 if (opt_protectEscapedCharacters) {
815 str = goog.string.htmlEscape(str);
816 }
817
818 return str;
819};
820
821
822/**
823 * Truncate a string in the middle, adding "..." if necessary,
824 * and favoring the beginning of the string.
825 * @param {string} str The string to truncate the middle of.
826 * @param {number} chars Max number of characters.
827 * @param {boolean=} opt_protectEscapedCharacters Whether to protect escaped
828 * characters from being cutoff in the middle.
829 * @param {number=} opt_trailingChars Optional number of trailing characters to
830 * leave at the end of the string, instead of truncating as close to the
831 * middle as possible.
832 * @return {string} A truncated copy of {@code str}.
833 */
834goog.string.truncateMiddle = function(str, chars,
835 opt_protectEscapedCharacters, opt_trailingChars) {
836 if (opt_protectEscapedCharacters) {
837 str = goog.string.unescapeEntities(str);
838 }
839
840 if (opt_trailingChars && str.length > chars) {
841 if (opt_trailingChars > chars) {
842 opt_trailingChars = chars;
843 }
844 var endPoint = str.length - opt_trailingChars;
845 var startPoint = chars - opt_trailingChars;
846 str = str.substring(0, startPoint) + '...' + str.substring(endPoint);
847 } else if (str.length > chars) {
848 // Favor the beginning of the string:
849 var half = Math.floor(chars / 2);
850 var endPos = str.length - half;
851 half += chars % 2;
852 str = str.substring(0, half) + '...' + str.substring(endPos);
853 }
854
855 if (opt_protectEscapedCharacters) {
856 str = goog.string.htmlEscape(str);
857 }
858
859 return str;
860};
861
862
863/**
864 * Special chars that need to be escaped for goog.string.quote.
865 * @private
866 * @type {Object}
867 */
868goog.string.specialEscapeChars_ = {
869 '\0': '\\0',
870 '\b': '\\b',
871 '\f': '\\f',
872 '\n': '\\n',
873 '\r': '\\r',
874 '\t': '\\t',
875 '\x0B': '\\x0B', // '\v' is not supported in JScript
876 '"': '\\"',
877 '\\': '\\\\'
878};
879
880
881/**
882 * Character mappings used internally for goog.string.escapeChar.
883 * @private
884 * @type {Object}
885 */
886goog.string.jsEscapeCache_ = {
887 '\'': '\\\''
888};
889
890
891/**
892 * Encloses a string in double quotes and escapes characters so that the
893 * string is a valid JS string.
894 * @param {string} s The string to quote.
895 * @return {string} A copy of {@code s} surrounded by double quotes.
896 */
897goog.string.quote = function(s) {
898 s = String(s);
899 if (s.quote) {
900 return s.quote();
901 } else {
902 var sb = ['"'];
903 for (var i = 0; i < s.length; i++) {
904 var ch = s.charAt(i);
905 var cc = ch.charCodeAt(0);
906 sb[i + 1] = goog.string.specialEscapeChars_[ch] ||
907 ((cc > 31 && cc < 127) ? ch : goog.string.escapeChar(ch));
908 }
909 sb.push('"');
910 return sb.join('');
911 }
912};
913
914
915/**
916 * Takes a string and returns the escaped string for that character.
917 * @param {string} str The string to escape.
918 * @return {string} An escaped string representing {@code str}.
919 */
920goog.string.escapeString = function(str) {
921 var sb = [];
922 for (var i = 0; i < str.length; i++) {
923 sb[i] = goog.string.escapeChar(str.charAt(i));
924 }
925 return sb.join('');
926};
927
928
929/**
930 * Takes a character and returns the escaped string for that character. For
931 * example escapeChar(String.fromCharCode(15)) -> "\\x0E".
932 * @param {string} c The character to escape.
933 * @return {string} An escaped string representing {@code c}.
934 */
935goog.string.escapeChar = function(c) {
936 if (c in goog.string.jsEscapeCache_) {
937 return goog.string.jsEscapeCache_[c];
938 }
939
940 if (c in goog.string.specialEscapeChars_) {
941 return goog.string.jsEscapeCache_[c] = goog.string.specialEscapeChars_[c];
942 }
943
944 var rv = c;
945 var cc = c.charCodeAt(0);
946 if (cc > 31 && cc < 127) {
947 rv = c;
948 } else {
949 // tab is 9 but handled above
950 if (cc < 256) {
951 rv = '\\x';
952 if (cc < 16 || cc > 256) {
953 rv += '0';
954 }
955 } else {
956 rv = '\\u';
957 if (cc < 4096) { // \u1000
958 rv += '0';
959 }
960 }
961 rv += cc.toString(16).toUpperCase();
962 }
963
964 return goog.string.jsEscapeCache_[c] = rv;
965};
966
967
968/**
969 * Determines whether a string contains a substring.
970 * @param {string} str The string to search.
971 * @param {string} subString The substring to search for.
972 * @return {boolean} Whether {@code str} contains {@code subString}.
973 */
974goog.string.contains = function(str, subString) {
975 return str.indexOf(subString) != -1;
976};
977
978
979/**
980 * Determines whether a string contains a substring, ignoring case.
981 * @param {string} str The string to search.
982 * @param {string} subString The substring to search for.
983 * @return {boolean} Whether {@code str} contains {@code subString}.
984 */
985goog.string.caseInsensitiveContains = function(str, subString) {
986 return goog.string.contains(str.toLowerCase(), subString.toLowerCase());
987};
988
989
990/**
991 * Returns the non-overlapping occurrences of ss in s.
992 * If either s or ss evalutes to false, then returns zero.
993 * @param {string} s The string to look in.
994 * @param {string} ss The string to look for.
995 * @return {number} Number of occurrences of ss in s.
996 */
997goog.string.countOf = function(s, ss) {
998 return s && ss ? s.split(ss).length - 1 : 0;
999};
1000
1001
1002/**
1003 * Removes a substring of a specified length at a specific
1004 * index in a string.
1005 * @param {string} s The base string from which to remove.
1006 * @param {number} index The index at which to remove the substring.
1007 * @param {number} stringLength The length of the substring to remove.
1008 * @return {string} A copy of {@code s} with the substring removed or the full
1009 * string if nothing is removed or the input is invalid.
1010 */
1011goog.string.removeAt = function(s, index, stringLength) {
1012 var resultStr = s;
1013 // If the index is greater or equal to 0 then remove substring
1014 if (index >= 0 && index < s.length && stringLength > 0) {
1015 resultStr = s.substr(0, index) +
1016 s.substr(index + stringLength, s.length - index - stringLength);
1017 }
1018 return resultStr;
1019};
1020
1021
1022/**
1023 * Removes the first occurrence of a substring from a string.
1024 * @param {string} s The base string from which to remove.
1025 * @param {string} ss The string to remove.
1026 * @return {string} A copy of {@code s} with {@code ss} removed or the full
1027 * string if nothing is removed.
1028 */
1029goog.string.remove = function(s, ss) {
1030 var re = new RegExp(goog.string.regExpEscape(ss), '');
1031 return s.replace(re, '');
1032};
1033
1034
1035/**
1036 * Removes all occurrences of a substring from a string.
1037 * @param {string} s The base string from which to remove.
1038 * @param {string} ss The string to remove.
1039 * @return {string} A copy of {@code s} with {@code ss} removed or the full
1040 * string if nothing is removed.
1041 */
1042goog.string.removeAll = function(s, ss) {
1043 var re = new RegExp(goog.string.regExpEscape(ss), 'g');
1044 return s.replace(re, '');
1045};
1046
1047
1048/**
1049 * Escapes characters in the string that are not safe to use in a RegExp.
1050 * @param {*} s The string to escape. If not a string, it will be casted
1051 * to one.
1052 * @return {string} A RegExp safe, escaped copy of {@code s}.
1053 */
1054goog.string.regExpEscape = function(s) {
1055 return String(s).replace(/([-()\[\]{}+?*.$\^|,:#<!\\])/g, '\\$1').
1056 replace(/\x08/g, '\\x08');
1057};
1058
1059
1060/**
1061 * Repeats a string n times.
1062 * @param {string} string The string to repeat.
1063 * @param {number} length The number of times to repeat.
1064 * @return {string} A string containing {@code length} repetitions of
1065 * {@code string}.
1066 */
1067goog.string.repeat = function(string, length) {
1068 return new Array(length + 1).join(string);
1069};
1070
1071
1072/**
1073 * Pads number to given length and optionally rounds it to a given precision.
1074 * For example:
1075 * <pre>padNumber(1.25, 2, 3) -> '01.250'
1076 * padNumber(1.25, 2) -> '01.25'
1077 * padNumber(1.25, 2, 1) -> '01.3'
1078 * padNumber(1.25, 0) -> '1.25'</pre>
1079 *
1080 * @param {number} num The number to pad.
1081 * @param {number} length The desired length.
1082 * @param {number=} opt_precision The desired precision.
1083 * @return {string} {@code num} as a string with the given options.
1084 */
1085goog.string.padNumber = function(num, length, opt_precision) {
1086 var s = goog.isDef(opt_precision) ? num.toFixed(opt_precision) : String(num);
1087 var index = s.indexOf('.');
1088 if (index == -1) {
1089 index = s.length;
1090 }
1091 return goog.string.repeat('0', Math.max(0, length - index)) + s;
1092};
1093
1094
1095/**
1096 * Returns a string representation of the given object, with
1097 * null and undefined being returned as the empty string.
1098 *
1099 * @param {*} obj The object to convert.
1100 * @return {string} A string representation of the {@code obj}.
1101 */
1102goog.string.makeSafe = function(obj) {
1103 return obj == null ? '' : String(obj);
1104};
1105
1106
1107/**
1108 * Concatenates string expressions. This is useful
1109 * since some browsers are very inefficient when it comes to using plus to
1110 * concat strings. Be careful when using null and undefined here since
1111 * these will not be included in the result. If you need to represent these
1112 * be sure to cast the argument to a String first.
1113 * For example:
1114 * <pre>buildString('a', 'b', 'c', 'd') -> 'abcd'
1115 * buildString(null, undefined) -> ''
1116 * </pre>
1117 * @param {...*} var_args A list of strings to concatenate. If not a string,
1118 * it will be casted to one.
1119 * @return {string} The concatenation of {@code var_args}.
1120 */
1121goog.string.buildString = function(var_args) {
1122 return Array.prototype.join.call(arguments, '');
1123};
1124
1125
1126/**
1127 * Returns a string with at least 64-bits of randomness.
1128 *
1129 * Doesn't trust Javascript's random function entirely. Uses a combination of
1130 * random and current timestamp, and then encodes the string in base-36 to
1131 * make it shorter.
1132 *
1133 * @return {string} A random string, e.g. sn1s7vb4gcic.
1134 */
1135goog.string.getRandomString = function() {
1136 var x = 2147483648;
1137 return Math.floor(Math.random() * x).toString(36) +
1138 Math.abs(Math.floor(Math.random() * x) ^ goog.now()).toString(36);
1139};
1140
1141
1142/**
1143 * Compares two version numbers.
1144 *
1145 * @param {string|number} version1 Version of first item.
1146 * @param {string|number} version2 Version of second item.
1147 *
1148 * @return {number} 1 if {@code version1} is higher.
1149 * 0 if arguments are equal.
1150 * -1 if {@code version2} is higher.
1151 */
1152goog.string.compareVersions = function(version1, version2) {
1153 var order = 0;
1154 // Trim leading and trailing whitespace and split the versions into
1155 // subversions.
1156 var v1Subs = goog.string.trim(String(version1)).split('.');
1157 var v2Subs = goog.string.trim(String(version2)).split('.');
1158 var subCount = Math.max(v1Subs.length, v2Subs.length);
1159
1160 // Iterate over the subversions, as long as they appear to be equivalent.
1161 for (var subIdx = 0; order == 0 && subIdx < subCount; subIdx++) {
1162 var v1Sub = v1Subs[subIdx] || '';
1163 var v2Sub = v2Subs[subIdx] || '';
1164
1165 // Split the subversions into pairs of numbers and qualifiers (like 'b').
1166 // Two different RegExp objects are needed because they are both using
1167 // the 'g' flag.
1168 var v1CompParser = new RegExp('(\\d*)(\\D*)', 'g');
1169 var v2CompParser = new RegExp('(\\d*)(\\D*)', 'g');
1170 do {
1171 var v1Comp = v1CompParser.exec(v1Sub) || ['', '', ''];
1172 var v2Comp = v2CompParser.exec(v2Sub) || ['', '', ''];
1173 // Break if there are no more matches.
1174 if (v1Comp[0].length == 0 && v2Comp[0].length == 0) {
1175 break;
1176 }
1177
1178 // Parse the numeric part of the subversion. A missing number is
1179 // equivalent to 0.
1180 var v1CompNum = v1Comp[1].length == 0 ? 0 : parseInt(v1Comp[1], 10);
1181 var v2CompNum = v2Comp[1].length == 0 ? 0 : parseInt(v2Comp[1], 10);
1182
1183 // Compare the subversion components. The number has the highest
1184 // precedence. Next, if the numbers are equal, a subversion without any
1185 // qualifier is always higher than a subversion with any qualifier. Next,
1186 // the qualifiers are compared as strings.
1187 order = goog.string.compareElements_(v1CompNum, v2CompNum) ||
1188 goog.string.compareElements_(v1Comp[2].length == 0,
1189 v2Comp[2].length == 0) ||
1190 goog.string.compareElements_(v1Comp[2], v2Comp[2]);
1191 // Stop as soon as an inequality is discovered.
1192 } while (order == 0);
1193 }
1194
1195 return order;
1196};
1197
1198
1199/**
1200 * Compares elements of a version number.
1201 *
1202 * @param {string|number|boolean} left An element from a version number.
1203 * @param {string|number|boolean} right An element from a version number.
1204 *
1205 * @return {number} 1 if {@code left} is higher.
1206 * 0 if arguments are equal.
1207 * -1 if {@code right} is higher.
1208 * @private
1209 */
1210goog.string.compareElements_ = function(left, right) {
1211 if (left < right) {
1212 return -1;
1213 } else if (left > right) {
1214 return 1;
1215 }
1216 return 0;
1217};
1218
1219
1220/**
1221 * Maximum value of #goog.string.hashCode, exclusive. 2^32.
1222 * @type {number}
1223 * @private
1224 */
1225goog.string.HASHCODE_MAX_ = 0x100000000;
1226
1227
1228/**
1229 * String hash function similar to java.lang.String.hashCode().
1230 * The hash code for a string is computed as
1231 * s[0] * 31 ^ (n - 1) + s[1] * 31 ^ (n - 2) + ... + s[n - 1],
1232 * where s[i] is the ith character of the string and n is the length of
1233 * the string. We mod the result to make it between 0 (inclusive) and 2^32
1234 * (exclusive).
1235 * @param {string} str A string.
1236 * @return {number} Hash value for {@code str}, between 0 (inclusive) and 2^32
1237 * (exclusive). The empty string returns 0.
1238 */
1239goog.string.hashCode = function(str) {
1240 var result = 0;
1241 for (var i = 0; i < str.length; ++i) {
1242 result = 31 * result + str.charCodeAt(i);
1243 // Normalize to 4 byte range, 0 ... 2^32.
1244 result %= goog.string.HASHCODE_MAX_;
1245 }
1246 return result;
1247};
1248
1249
1250/**
1251 * The most recent unique ID. |0 is equivalent to Math.floor in this case.
1252 * @type {number}
1253 * @private
1254 */
1255goog.string.uniqueStringCounter_ = Math.random() * 0x80000000 | 0;
1256
1257
1258/**
1259 * Generates and returns a string which is unique in the current document.
1260 * This is useful, for example, to create unique IDs for DOM elements.
1261 * @return {string} A unique id.
1262 */
1263goog.string.createUniqueString = function() {
1264 return 'goog_' + goog.string.uniqueStringCounter_++;
1265};
1266
1267
1268/**
1269 * Converts the supplied string to a number, which may be Infinity or NaN.
1270 * This function strips whitespace: (toNumber(' 123') === 123)
1271 * This function accepts scientific notation: (toNumber('1e1') === 10)
1272 *
1273 * This is better than Javascript's built-in conversions because, sadly:
1274 * (Number(' ') === 0) and (parseFloat('123a') === 123)
1275 *
1276 * @param {string} str The string to convert.
1277 * @return {number} The number the supplied string represents, or NaN.
1278 */
1279goog.string.toNumber = function(str) {
1280 var num = Number(str);
1281 if (num == 0 && goog.string.isEmpty(str)) {
1282 return NaN;
1283 }
1284 return num;
1285};
1286
1287
1288/**
1289 * Returns whether the given string is lower camel case (e.g. "isFooBar").
1290 *
1291 * Note that this assumes the string is entirely letters.
1292 * @see http://en.wikipedia.org/wiki/CamelCase#Variations_and_synonyms
1293 *
1294 * @param {string} str String to test.
1295 * @return {boolean} Whether the string is lower camel case.
1296 */
1297goog.string.isLowerCamelCase = function(str) {
1298 return /^[a-z]+([A-Z][a-z]*)*$/.test(str);
1299};
1300
1301
1302/**
1303 * Returns whether the given string is upper camel case (e.g. "FooBarBaz").
1304 *
1305 * Note that this assumes the string is entirely letters.
1306 * @see http://en.wikipedia.org/wiki/CamelCase#Variations_and_synonyms
1307 *
1308 * @param {string} str String to test.
1309 * @return {boolean} Whether the string is upper camel case.
1310 */
1311goog.string.isUpperCamelCase = function(str) {
1312 return /^([A-Z][a-z]*)+$/.test(str);
1313};
1314
1315
1316/**
1317 * Converts a string from selector-case to camelCase (e.g. from
1318 * "multi-part-string" to "multiPartString"), useful for converting
1319 * CSS selectors and HTML dataset keys to their equivalent JS properties.
1320 * @param {string} str The string in selector-case form.
1321 * @return {string} The string in camelCase form.
1322 */
1323goog.string.toCamelCase = function(str) {
1324 return String(str).replace(/\-([a-z])/g, function(all, match) {
1325 return match.toUpperCase();
1326 });
1327};
1328
1329
1330/**
1331 * Converts a string from camelCase to selector-case (e.g. from
1332 * "multiPartString" to "multi-part-string"), useful for converting JS
1333 * style and dataset properties to equivalent CSS selectors and HTML keys.
1334 * @param {string} str The string in camelCase form.
1335 * @return {string} The string in selector-case form.
1336 */
1337goog.string.toSelectorCase = function(str) {
1338 return String(str).replace(/([A-Z])/g, '-$1').toLowerCase();
1339};
1340
1341
1342/**
1343 * Converts a string into TitleCase. First character of the string is always
1344 * capitalized in addition to the first letter of every subsequent word.
1345 * Words are delimited by one or more whitespaces by default. Custom delimiters
1346 * can optionally be specified to replace the default, which doesn't preserve
1347 * whitespace delimiters and instead must be explicitly included if needed.
1348 *
1349 * Default delimiter => " ":
1350 * goog.string.toTitleCase('oneTwoThree') => 'OneTwoThree'
1351 * goog.string.toTitleCase('one two three') => 'One Two Three'
1352 * goog.string.toTitleCase(' one two ') => ' One Two '
1353 * goog.string.toTitleCase('one_two_three') => 'One_two_three'
1354 * goog.string.toTitleCase('one-two-three') => 'One-two-three'
1355 *
1356 * Custom delimiter => "_-.":
1357 * goog.string.toTitleCase('oneTwoThree', '_-.') => 'OneTwoThree'
1358 * goog.string.toTitleCase('one two three', '_-.') => 'One two three'
1359 * goog.string.toTitleCase(' one two ', '_-.') => ' one two '
1360 * goog.string.toTitleCase('one_two_three', '_-.') => 'One_Two_Three'
1361 * goog.string.toTitleCase('one-two-three', '_-.') => 'One-Two-Three'
1362 * goog.string.toTitleCase('one...two...three', '_-.') => 'One...Two...Three'
1363 * goog.string.toTitleCase('one. two. three', '_-.') => 'One. two. three'
1364 * goog.string.toTitleCase('one-two.three', '_-.') => 'One-Two.Three'
1365 *
1366 * @param {string} str String value in camelCase form.
1367 * @param {string=} opt_delimiters Custom delimiter character set used to
1368 * distinguish words in the string value. Each character represents a
1369 * single delimiter. When provided, default whitespace delimiter is
1370 * overridden and must be explicitly included if needed.
1371 * @return {string} String value in TitleCase form.
1372 */
1373goog.string.toTitleCase = function(str, opt_delimiters) {
1374 var delimiters = goog.isString(opt_delimiters) ?
1375 goog.string.regExpEscape(opt_delimiters) : '\\s';
1376
1377 // For IE8, we need to prevent using an empty character set. Otherwise,
1378 // incorrect matching will occur.
1379 delimiters = delimiters ? '|[' + delimiters + ']+' : '';
1380
1381 var regexp = new RegExp('(^' + delimiters + ')([a-z])', 'g');
1382 return str.replace(regexp, function(all, p1, p2) {
1383 return p1 + p2.toUpperCase();
1384 });
1385};
1386
1387
1388/**
1389 * Parse a string in decimal or hexidecimal ('0xFFFF') form.
1390 *
1391 * To parse a particular radix, please use parseInt(string, radix) directly. See
1392 * https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/parseInt
1393 *
1394 * This is a wrapper for the built-in parseInt function that will only parse
1395 * numbers as base 10 or base 16. Some JS implementations assume strings
1396 * starting with "0" are intended to be octal. ES3 allowed but discouraged
1397 * this behavior. ES5 forbids it. This function emulates the ES5 behavior.
1398 *
1399 * For more information, see Mozilla JS Reference: http://goo.gl/8RiFj
1400 *
1401 * @param {string|number|null|undefined} value The value to be parsed.
1402 * @return {number} The number, parsed. If the string failed to parse, this
1403 * will be NaN.
1404 */
1405goog.string.parseInt = function(value) {
1406 // Force finite numbers to strings.
1407 if (isFinite(value)) {
1408 value = String(value);
1409 }
1410
1411 if (goog.isString(value)) {
1412 // If the string starts with '0x' or '-0x', parse as hex.
1413 return /^\s*-?0x/i.test(value) ?
1414 parseInt(value, 16) : parseInt(value, 10);
1415 }
1416
1417 return NaN;
1418};
1419
1420
1421/**
1422 * Splits a string on a separator a limited number of times.
1423 *
1424 * This implementation is more similar to Python or Java, where the limit
1425 * parameter specifies the maximum number of splits rather than truncating
1426 * the number of results.
1427 *
1428 * See http://docs.python.org/2/library/stdtypes.html#str.split
1429 * See JavaDoc: http://goo.gl/F2AsY
1430 * See Mozilla reference: http://goo.gl/dZdZs
1431 *
1432 * @param {string} str String to split.
1433 * @param {string} separator The separator.
1434 * @param {number} limit The limit to the number of splits. The resulting array
1435 * will have a maximum length of limit+1. Negative numbers are the same
1436 * as zero.
1437 * @return {!Array.<string>} The string, split.
1438 */
1439
1440goog.string.splitLimit = function(str, separator, limit) {
1441 var parts = str.split(separator);
1442 var returnVal = [];
1443
1444 // Only continue doing this while we haven't hit the limit and we have
1445 // parts left.
1446 while (limit > 0 && parts.length) {
1447 returnVal.push(parts.shift());
1448 limit--;
1449 }
1450
1451 // If there are remaining parts, append them to the end.
1452 if (parts.length) {
1453 returnVal.push(parts.join(separator));
1454 }
1455
1456 return returnVal;
1457};