string.js 52 KB

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