prototype/src/lang/string.js

468 lines
13 KiB
JavaScript
Raw Normal View History

2009-03-05 19:56:01 +00:00
/** 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]].
2008-12-14 04:36:59 +00:00
**/
Object.extend(String, {
2008-12-14 04:36:59 +00:00
/**
* String.interpret(value) -> String
*
2009-02-24 02:35:49 +00:00
* Coerces `value` into a string. Returns an empty string for `null`.
2008-12-14 04:36:59 +00:00
**/
interpret: function(value) {
return value == null ? '' : String(value);
},
specialChar: {
'\b': '\\b',
'\t': '\\t',
'\n': '\\n',
'\f': '\\f',
'\r': '\\r',
'\\': '\\\\'
}
});
2007-01-18 22:24:27 +00:00
2008-12-11 10:50:51 +00:00
Object.extend(String.prototype, (function() {
2008-12-11 10:50:51 +00:00
function prepareReplacement(replacement) {
if (Object.isFunction(replacement)) return replacement;
var template = new Template(replacement);
return function(match) { return template.evaluate(match) };
}
2008-12-14 04:36:59 +00:00
/**
* 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.
**/
2008-12-11 10:50:51 +00:00
function gsub(pattern, replacement) {
2007-01-18 22:24:27 +00:00
var result = '', source = this, match;
2008-12-11 10:50:51 +00:00
replacement = prepareReplacement(replacement);
if (Object.isString(pattern))
pattern = RegExp.escape(pattern);
2008-12-11 10:50:51 +00:00
if (!(pattern.length || pattern.source)) {
replacement = replacement('');
return replacement + source.split('').join(replacement) + replacement;
}
2007-01-18 22:24:27 +00:00
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;
2008-12-11 10:50:51 +00:00
}
2008-12-14 04:36:59 +00:00
/**
* 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.
**/
2008-12-11 10:50:51 +00:00
function sub(pattern, replacement, count) {
replacement = prepareReplacement(replacement);
count = Object.isUndefined(count) ? 1 : count;
2008-12-11 10:50:51 +00:00
2007-01-18 22:24:27 +00:00
return this.gsub(pattern, function(match) {
if (--count < 0) return match[0];
return replacement(match);
});
2008-12-11 10:50:51 +00:00
}
2008-12-14 04:36:59 +00:00
/** related to: String#gsub
* String#scan(pattern, iterator) -> String
*
* Allows iterating over every occurrence of the given pattern (which can be a
2008-12-14 04:36:59 +00:00
* string or a regular expression).
* Returns the original string.
**/
2008-12-11 10:50:51 +00:00
function scan(pattern, iterator) {
2007-01-18 22:24:27 +00:00
this.gsub(pattern, iterator);
return String(this);
2008-12-11 10:50:51 +00:00
}
2008-12-14 04:36:59 +00:00
/**
* String#truncate([length = 30[, suffix = '...']]) -> String
*
* Truncates a string to given `length` and appends `suffix` to it (indicating
* that it is only an excerpt).
**/
2008-12-11 10:50:51 +00:00
function truncate(length, truncation) {
2007-01-18 22:24:27 +00:00
length = length || 30;
truncation = Object.isUndefined(truncation) ? '...' : truncation;
return this.length > length ?
this.slice(0, length - truncation.length) + truncation : String(this);
2008-12-11 10:50:51 +00:00
}
2007-01-18 22:24:27 +00:00
2008-12-14 04:36:59 +00:00
/**
* String#strip() -> String
*
* Strips all leading and trailing whitespace from a string.
**/
2008-12-11 10:50:51 +00:00
function strip() {
2007-01-18 22:24:27 +00:00
return this.replace(/^\s+/, '').replace(/\s+$/, '');
2008-12-11 10:50:51 +00:00
}
2008-12-14 04:36:59 +00:00
/**
* String#stripTags() -> String
*
2009-06-05 03:56:55 +00:00
* Strips a string of any HTML tags.
*
* Note that `stripTags` will only strip HTML 4.01 tags &mdash; like `div`,
2009-06-05 03:56:55 +00:00
* `span`, and `abbr`. It _will not_ strip namespace-prefixed tags such
* as `h:table` or `xsl:template`.
2008-12-14 04:36:59 +00:00
**/
2008-12-11 10:50:51 +00:00
function stripTags() {
return this.replace(/<\w+(\s+("[^"]*"|'[^']*'|[^>])+)?>|<\/\w+>/gi, '');
2008-12-11 10:50:51 +00:00
}
2007-01-18 22:24:27 +00:00
2008-12-14 04:36:59 +00:00
/**
* String#stripScripts() -> String
*
* Strips a string of anything that looks like an HTML script block.
**/
2008-12-11 10:50:51 +00:00
function stripScripts() {
2007-01-18 22:24:27 +00:00
return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), '');
2008-12-11 10:50:51 +00:00
}
2008-12-14 04:36:59 +00:00
/**
* String#extractScripts() -> Array
*
2009-06-05 03:56:55 +00:00
* Extracts the content of any script blocks present in the string and
* returns them as an array of strings.
2008-12-14 04:36:59 +00:00
**/
2008-12-11 10:50:51 +00:00
function extractScripts() {
2007-01-18 22:24:27 +00:00
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];
});
2008-12-11 10:50:51 +00:00
}
2008-12-14 04:36:59 +00:00
/**
* String#evalScripts() -> Array
*
* Evaluates the content of any script block present in the string.
* Returns an array containing the value returned by each script.
**/
2008-12-11 10:50:51 +00:00
function evalScripts() {
2007-01-18 22:24:27 +00:00
return this.extractScripts().map(function(script) { return eval(script) });
2008-12-11 10:50:51 +00:00
}
2007-01-18 22:24:27 +00:00
2008-12-14 04:36:59 +00:00
/**
* String#escapeHTML() -> String
*
* Converts HTML special characters to their entity equivalents.
**/
2008-12-11 10:50:51 +00:00
function escapeHTML() {
return this.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
2008-12-11 10:50:51 +00:00
}
2007-01-18 22:24:27 +00:00
2008-12-14 04:36:59 +00:00
/** related to: String#escapeHTML
* String#unescapeHTML() -> String
*
2009-06-05 03:56:55 +00:00
* Strips tags and converts the entity forms of special HTML characters
* to their normal form.
2008-12-14 04:36:59 +00:00
**/
2008-12-11 10:50:51 +00:00
function unescapeHTML() {
// Warning: In 1.7 String#unescapeHTML will no longer call String#stripTags.
return this.stripTags().replace(/&lt;/g,'<').replace(/&gt;/g,'>').replace(/&amp;/g,'&');
2008-12-11 10:50:51 +00:00
}
2009-06-05 03:56:55 +00:00
/**
* String#parseQuery([separator = '&']) -> Object
**/
2008-12-11 10:50:51 +00:00
2008-12-14 04:36:59 +00:00
/** alias of: String#parseQuery, related to: Hash#toQueryString
* String#toQueryParams([separator = '&']) -> Object
*
2009-06-05 03:56:55 +00:00
* Parses a URI-like query string and returns an object composed of
* parameter/value pairs.
2008-12-14 04:36:59 +00:00
**/
2008-12-11 10:50:51 +00:00
function toQueryParams(separator) {
2007-01-18 22:24:27 +00:00
var match = this.strip().match(/([^?#]*)(#.*)?$/);
if (!match) return { };
2008-12-11 10:50:51 +00:00
return match[1].split(separator || '&').inject({ }, function(hash, pair) {
2007-01-18 22:24:27 +00:00
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);
2008-12-11 10:50:51 +00:00
if (key in hash) {
if (!Object.isArray(hash[key])) hash[key] = [hash[key]];
hash[key].push(value);
2007-01-18 22:24:27 +00:00
}
else hash[key] = value;
2007-01-18 22:24:27 +00:00
}
return hash;
});
2008-12-11 10:50:51 +00:00
}
2008-12-14 04:36:59 +00:00
/**
* String#toArray() -> Array
*
2009-06-05 03:56:55 +00:00
* Splits the string character-by-character and returns an array with
* the result.
2008-12-14 04:36:59 +00:00
**/
2008-12-11 10:50:51 +00:00
function toArray() {
2007-01-18 22:24:27 +00:00
return this.split('');
2008-12-11 10:50:51 +00:00
}
2007-01-18 22:24:27 +00:00
2008-12-14 04:36:59 +00:00
/**
* String#succ() -> String
*
* Used internally by ObjectRange.
2009-06-05 03:56:55 +00:00
* Converts the last character of the string to the following character in
* the Unicode alphabet.
2008-12-14 04:36:59 +00:00
**/
2008-12-11 10:50:51 +00:00
function succ() {
2007-01-18 22:24:27 +00:00
return this.slice(0, this.length - 1) +
String.fromCharCode(this.charCodeAt(this.length - 1) + 1);
2008-12-11 10:50:51 +00:00
}
2008-12-14 04:36:59 +00:00
/**
* String#times(count) -> String
*
* Concatenates the string `count` times.
**/
2008-12-11 10:50:51 +00:00
function times(count) {
return count < 1 ? '' : new Array(count + 1).join(this);
2008-12-11 10:50:51 +00:00
}
2008-12-14 04:36:59 +00:00
/**
* String#camelize() -> String
*
* Converts a string separated by dashes into a camelCase equivalent.
2008-12-14 04:36:59 +00:00
* For instance, 'foo-bar' would be converted to 'fooBar'.
2009-06-14 00:27:22 +00:00
*
* <h4>Examples</h4>
*
* 'background-color'.camelize();
* // -> 'backgroundColor'
*
* '-moz-binding'.camelize();
* // -> 'MozBinding'
2008-12-14 04:36:59 +00:00
**/
2008-12-11 10:50:51 +00:00
function camelize() {
2007-01-18 22:24:27 +00:00
var parts = this.split('-'), len = parts.length;
if (len == 1) return parts[0];
2008-12-11 10:50:51 +00:00
var camelized = this.charAt(0) == '-'
2007-01-18 22:24:27 +00:00
? parts[0].charAt(0).toUpperCase() + parts[0].substring(1)
: parts[0];
2008-12-11 10:50:51 +00:00
2007-01-18 22:24:27 +00:00
for (var i = 1; i < len; i++)
camelized += parts[i].charAt(0).toUpperCase() + parts[i].substring(1);
2008-12-11 10:50:51 +00:00
2007-01-18 22:24:27 +00:00
return camelized;
2008-12-11 10:50:51 +00:00
}
2008-12-14 04:36:59 +00:00
/**
* String#capitalize() -> String
*
* Capitalizes the first letter of a string and downcases all the others.
**/
2008-12-11 10:50:51 +00:00
function capitalize() {
2007-01-18 22:24:27 +00:00
return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase();
2008-12-11 10:50:51 +00:00
}
2008-12-14 04:36:59 +00:00
/**
* String#underscore() -> String
*
2009-06-05 03:56:55 +00:00
* Converts a camelized string into a series of words separated by an
* underscore (`_`).
2008-12-14 04:36:59 +00:00
**/
2008-12-11 10:50:51 +00:00
function underscore() {
return this.replace(/::/g, '/')
.replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2')
.replace(/([a-z\d])([A-Z])/g, '$1_$2')
.replace(/-/g, '_')
.toLowerCase();
2008-12-11 10:50:51 +00:00
}
2007-01-18 22:24:27 +00:00
2008-12-14 04:36:59 +00:00
/**
* String#dasherize() -> String
*
* Replaces every instance of the underscore character ("_") by a dash ("-").
**/
2008-12-11 10:50:51 +00:00
function dasherize() {
return this.replace(/_/g, '-');
2008-12-11 10:50:51 +00:00
}
2007-01-18 22:24:27 +00:00
2008-12-14 04:36:59 +00:00
/** related to: Object.inspect
* String#inspect([useDoubleQuotes = false]) -> String
*
* Returns a debug-oriented version of the string (i.e. wrapped in single or
2008-12-14 04:36:59 +00:00
* double quotes, with backslashes and quotes escaped).
**/
2008-12-11 10:50:51 +00:00
function inspect(useDoubleQuotes) {
var escapedString = this.replace(/[\x00-\x1f\\]/g, function(character) {
if (character in String.specialChar) {
return String.specialChar[character];
}
return '\\u00' + character.charCodeAt().toPaddedString(2, 16);
});
if (useDoubleQuotes) return '"' + escapedString.replace(/"/g, '\\"') + '"';
return "'" + escapedString.replace(/'/g, '\\\'') + "'";
2008-12-11 10:50:51 +00:00
}
2008-12-14 04:36:59 +00:00
/** related to: Object.toJSON
* String#toJSON() -> String
*
* Returns a JSON string.
**/
2008-12-11 10:50:51 +00:00
function toJSON() {
return this.inspect(true);
2008-12-11 10:50:51 +00:00
}
2008-12-14 04:36:59 +00:00
/**
* String#unfilterJSON([filter = Prototype.JSONFilter]) -> String
*
* Strips comment delimiters around Ajax JSON or JavaScript responses.
* This security method is called internally.
**/
2008-12-11 10:50:51 +00:00
function unfilterJSON(filter) {
return this.replace(filter || Prototype.JSONFilter, '$1');
2008-12-11 10:50:51 +00:00
}
2008-12-14 04:36:59 +00:00
/**
* String#isJSON() -> Boolean
*
* Check if the string is valid JSON by the use of regular expressions.
2008-12-14 04:36:59 +00:00
* This security method is called internally.
**/
2008-12-11 10:50:51 +00:00
function isJSON() {
var str = this;
if (str.blank()) return false;
str = this.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, '');
return (/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(str);
2008-12-11 10:50:51 +00:00
}
2008-12-14 04:36:59 +00:00
/**
* String#evalJSON([sanitize = false]) -> object
*
2009-06-05 03:56:55 +00:00
* Evaluates the JSON in the string and returns the resulting object.
*
2009-06-05 03:56:55 +00:00
* If the optional `sanitize` parameter is set to `true`, the string is
* checked for possible malicious attempts; if one is detected, `eval`
* is _not called_.
2008-12-14 04:36:59 +00:00
**/
2008-12-11 10:50:51 +00:00
function evalJSON(sanitize) {
var json = this.unfilterJSON();
try {
if (!sanitize || json.isJSON()) return eval('(' + json + ')');
} catch (e) { }
throw new SyntaxError('Badly formed JSON string: ' + this.inspect());
2008-12-11 10:50:51 +00:00
}
2008-12-14 04:36:59 +00:00
/**
* String#include(substring) -> Boolean
*
2009-06-05 03:56:55 +00:00
* Checks if the string contains `substring`.
2008-12-14 04:36:59 +00:00
**/
2008-12-11 10:50:51 +00:00
function include(pattern) {
return this.indexOf(pattern) > -1;
2008-12-11 10:50:51 +00:00
}
2008-12-14 04:36:59 +00:00
/**
* String#startsWith(substring) -> Boolean
*
2009-06-05 03:56:55 +00:00
* Checks if the string starts with `substring`.
2008-12-14 04:36:59 +00:00
**/
2008-12-11 10:50:51 +00:00
function startsWith(pattern) {
return this.indexOf(pattern) === 0;
2008-12-11 10:50:51 +00:00
}
2008-12-14 04:36:59 +00:00
/**
* String#endsWith(substring) -> Boolean
*
2009-06-05 03:56:55 +00:00
* Checks if the string ends with `substring`.
2008-12-14 04:36:59 +00:00
**/
2008-12-11 10:50:51 +00:00
function endsWith(pattern) {
var d = this.length - pattern.length;
return d >= 0 && this.lastIndexOf(pattern) === d;
2008-12-11 10:50:51 +00:00
}
2008-12-14 04:36:59 +00:00
/**
* String#empty() -> Boolean
*
* Checks if the string is empty.
**/
2008-12-11 10:50:51 +00:00
function empty() {
return this == '';
2008-12-11 10:50:51 +00:00
}
2008-12-14 04:36:59 +00:00
/**
* String#blank() -> Boolean
*
* Check if the string is "blank" &mdash; either empty (length of `0`) or containing
2009-06-05 03:56:55 +00:00
* only whitespace.
2008-12-14 04:36:59 +00:00
**/
2008-12-11 10:50:51 +00:00
function blank() {
return /^\s*$/.test(this);
2008-12-11 10:50:51 +00:00
}
2008-12-14 04:36:59 +00:00
/**
* String#interpolate(object[, pattern]) -> String
*
* Treats the string as a [[Template]] and fills it with `object`'s
2009-06-05 03:56:55 +00:00
* properties.
2008-12-14 04:36:59 +00:00
**/
2008-12-11 10:50:51 +00:00
function interpolate(object, pattern) {
return new Template(this, pattern).evaluate(object);
2007-01-18 22:24:27 +00:00
}
2008-12-11 10:50:51 +00:00
return {
gsub: gsub,
sub: sub,
scan: scan,
truncate: truncate,
// Firefox 3.5+ supports String.prototype.trim
// (`trim` is ~ 5x faster than `strip` in FF3.5)
strip: String.prototype.trim ? String.prototype.trim : strip,
2008-12-11 10:50:51 +00:00
stripTags: stripTags,
stripScripts: stripScripts,
extractScripts: extractScripts,
evalScripts: evalScripts,
escapeHTML: escapeHTML,
unescapeHTML: unescapeHTML,
toQueryParams: toQueryParams,
parseQuery: toQueryParams,
toArray: toArray,
succ: succ,
times: times,
camelize: camelize,
capitalize: capitalize,
underscore: underscore,
dasherize: dasherize,
inspect: inspect,
toJSON: toJSON,
unfilterJSON: unfilterJSON,
isJSON: isJSON,
evalJSON: evalJSON,
include: include,
startsWith: startsWith,
endsWith: endsWith,
empty: empty,
blank: blank,
interpolate: interpolate
};
})());
2007-01-18 22:24:27 +00:00