/** section: Language * class String * * Extensions to the built-in `String` class. * * Prototype enhances the `String` object with a series of useful methods for * ranging from the trivial to the complex. Tired of stripping trailing * whitespace? Try [[String#strip]]. Want to replace `replace`? Have a look at * [[String#sub]] and [[String#gsub]]. Need to parse a query string? We have * [[String#toQueryParams]]. **/ Object.extend(String, { /** * String.interpret(value) -> String * * Coerces `value` into a string. Returns an empty string for `null`. **/ interpret: function(value) { return value == null ? '' : String(value); }, specialChar: { '\b': '\\b', '\t': '\\t', '\n': '\\n', '\f': '\\f', '\r': '\\r', '\\': '\\\\' } }); Object.extend(String.prototype, (function() { function prepareReplacement(replacement) { if (Object.isFunction(replacement)) return replacement; var template = new Template(replacement); return function(match) { return template.evaluate(match) }; } /** * String#gsub(pattern, replacement) -> String * * Returns the string with every occurence of a given pattern replaced by either * a regular string, the returned value of a function or a [[Template]] string. * The pattern can be a string or a regular expression. **/ function gsub(pattern, replacement) { var result = '', source = this, match; replacement = prepareReplacement(replacement); if (Object.isString(pattern)) pattern = RegExp.escape(pattern); if (!(pattern.length || pattern.source)) { replacement = replacement(''); return replacement + source.split('').join(replacement) + replacement; } while (source.length > 0) { if (match = source.match(pattern)) { result += source.slice(0, match.index); result += String.interpret(replacement(match)); source = source.slice(match.index + match[0].length); } else { result += source, source = ''; } } return result; } /** * String#sub(pattern, replacement[, count = 1]) -> String * * Returns a string with the first count occurrences of pattern replaced by either * a regular string, the returned value of a function or a [[Template]] string. * The pattern can be a string or a regular expression. **/ function sub(pattern, replacement, count) { replacement = prepareReplacement(replacement); count = Object.isUndefined(count) ? 1 : count; return this.gsub(pattern, function(match) { if (--count < 0) return match[0]; return replacement(match); }); } /** related to: String#gsub * String#scan(pattern, iterator) -> String * * Allows iterating over every occurrence of the given pattern (which can be a * string or a regular expression). * Returns the original string. **/ function scan(pattern, iterator) { this.gsub(pattern, iterator); return String(this); } /** * String#truncate([length = 30[, suffix = '...']]) -> String * * Truncates a string to given `length` and appends `suffix` to it (indicating * that it is only an excerpt). **/ function truncate(length, truncation) { length = length || 30; truncation = Object.isUndefined(truncation) ? '...' : truncation; return this.length > length ? this.slice(0, length - truncation.length) + truncation : String(this); } /** * String#strip() -> String * * Strips all leading and trailing whitespace from a string. **/ function strip() { return this.replace(/^\s+/, '').replace(/\s+$/, ''); } /** * String#stripTags() -> String * * Strips a string of any HTML tags. * * Note that `stripTags` will only strip HTML 4.01 tags — like `div`, * `span`, and `abbr`. It _will not_ strip namespace-prefixed tags such * as `h:table` or `xsl:template`. **/ function stripTags() { return this.replace(/<\w+(\s+("[^"]*"|'[^']*'|[^>])+)?>|<\/\w+>/gi, ''); } /** * String#stripScripts() -> String * * Strips a string of anything that looks like an HTML script block. **/ function stripScripts() { return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), ''); } /** * String#extractScripts() -> Array * * Extracts the content of any script blocks present in the string and * returns them as an array of strings. **/ function extractScripts() { var matchAll = new RegExp(Prototype.ScriptFragment, 'img'); var matchOne = new RegExp(Prototype.ScriptFragment, 'im'); return (this.match(matchAll) || []).map(function(scriptTag) { return (scriptTag.match(matchOne) || ['', ''])[1]; }); } /** * String#evalScripts() -> Array * * Evaluates the content of any script block present in the string. * Returns an array containing the value returned by each script. **/ function evalScripts() { return this.extractScripts().map(function(script) { return eval(script) }); } /** * String#escapeHTML() -> String * * Converts HTML special characters to their entity equivalents. **/ function escapeHTML() { escapeHTML.text.data = this; return escapeHTML.div.innerHTML; } /** related to: String#escapeHTML * String#unescapeHTML() -> String * * Strips tags and converts the entity forms of special HTML characters * to their normal form. **/ function unescapeHTML() { var div = document.createElement('div'); div.innerHTML = this.stripTags(); return div.childNodes[0] ? (div.childNodes.length > 1 ? $A(div.childNodes).inject('', function(memo, node) { return memo+node.nodeValue }) : div.childNodes[0].nodeValue) : ''; } /** * String#parseQuery([separator = '&']) -> Object **/ /** alias of: String#parseQuery, related to: Hash#toQueryString * String#toQueryParams([separator = '&']) -> Object * * Parses a URI-like query string and returns an object composed of * parameter/value pairs. **/ function toQueryParams(separator) { var match = this.strip().match(/([^?#]*)(#.*)?$/); if (!match) return { }; return match[1].split(separator || '&').inject({ }, function(hash, pair) { if ((pair = pair.split('='))[0]) { var key = decodeURIComponent(pair.shift()); var value = pair.length > 1 ? pair.join('=') : pair[0]; if (value != undefined) value = decodeURIComponent(value); if (key in hash) { if (!Object.isArray(hash[key])) hash[key] = [hash[key]]; hash[key].push(value); } else hash[key] = value; } return hash; }); } /** * String#toArray() -> Array * * Splits the string character-by-character and returns an array with * the result. **/ function toArray() { return this.split(''); } /** * String#succ() -> String * * Used internally by ObjectRange. * Converts the last character of the string to the following character in * the Unicode alphabet. **/ function succ() { return this.slice(0, this.length - 1) + String.fromCharCode(this.charCodeAt(this.length - 1) + 1); } /** * String#times(count) -> String * * Concatenates the string `count` times. **/ function times(count) { return count < 1 ? '' : new Array(count + 1).join(this); } /** * String#camelize() -> String * * Converts a string separated by dashes into a camelCase equivalent. * For instance, 'foo-bar' would be converted to 'fooBar'. * *