From 8c911a2fd031e1326d51fa5de014d844b5036bd0 Mon Sep 17 00:00:00 2001 From: John-David Dalton Date: Mon, 27 Aug 2012 02:05:37 -0700 Subject: [PATCH] Add `_.unescape` method. [closes #63] Former-commit-id: 10eada385fd0e1157271a2da6fb32de047d6d88a --- build/pre-compile.js | 2 +- lodash.js | 76 +++++++++++++++++++++++++++++++++++++------- test/test.js | 30 ++++++++++++++--- 3 files changed, 90 insertions(+), 18 deletions(-) diff --git a/build/pre-compile.js b/build/pre-compile.js index db395d6d2..31bf03fd6 100644 --- a/build/pre-compile.js +++ b/build/pre-compile.js @@ -132,7 +132,6 @@ 'each', 'environment', 'escape', - 'escape', 'evaluate', 'every', 'extend', @@ -215,6 +214,7 @@ 'throttle', 'times', 'toArray', + 'unescape', 'union', 'uniq', 'unique', diff --git a/lodash.js b/lodash.js index a373591d8..0356e7ffc 100644 --- a/lodash.js +++ b/lodash.js @@ -56,6 +56,9 @@ /** Used to detect delimiter values that should be processed by `tokenizeEvaluate` */ var reComplexDelimiter = /[-+=!~*%&^<>|{(\/]|\[\D|\b(?:delete|in|instanceof|new|typeof|void)\b/; + /** Used to match HTML entities */ + var reEscapedHtml = /&(?:amp|lt|gt|quot|#[xX]27);/g; + /** Used to match empty string literals in compiled template source */ var reEmptyStringLeading = /\b__p \+= '';/g, reEmptyStringMiddle = /\b(__p \+=) '' \+/g, @@ -74,11 +77,11 @@ .replace(/valueOf|for [^\]]+/g, '.+?') + '$' ); - /** Used to match tokens in template text */ + /** Used to match internally used tokens in template text */ var reToken = /__token__(\d+)/g; - /** Used to match unescaped characters in strings for inclusion in HTML */ - var reUnescapedHtml = /[&<"']/g; + /** Used to match HTML characters */ + var reUnescapedHtml = /[&<>"']/g; /** Used to match unescaped characters in compiled string literals */ var reUnescapedString = /['\n\r\t\u2028\u2029\\]/g; @@ -233,19 +236,36 @@ cloneableClasses[stringClass] = true; /** - * Used to escape characters for inclusion in HTML: + * Used to convert characters to HTML entities: * - * The `>` and `/` characters don't require escaping in HTML and have no - * special meaning unless they're part of a tag or an unquoted attribute value - * http://mathiasbynens.be/notes/ambiguous-ampersands (semi-related fun fact) + * Though the `>` character is escaped for symmetry, characters like `>` and `/` + * don't require escaping in HTML and have no special meaning unless they're part + * of a tag or an unquoted attribute value. + * http://mathiasbynens.be/notes/ambiguous-ampersands (under "semi-related fun fact") */ var htmlEscapes = { '&': '&', '<': '<', + '>': '>', '"': '"', "'": ''' }; + /** + * Used to convert HTML entities to characters: + * + * Numeric character references are case-insensitive. + * http://www.w3.org/TR/html4/charset.html#h-5.3.1 + */ + var htmlUnescapes = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + ''': "'", + ''': "'" + }; + /** Used to determine if values are of the language type Object */ var objectTypes = { 'boolean': false, @@ -522,7 +542,10 @@ ' callback = iteratorBind(callback, thisArg)\n' + '}', 'inLoop': - 'if (isFunc ? !callback(value, index, object) : indexOf(props, index) < 0) result[index] = value' + 'if (isFunc\n' + + ' ? !callback(value, index, object)\n' + + ' : indexOf(props, index) < 0\n' + + ') result[index] = value' }; /** Reusable iterator options for `every` and `some` */ @@ -778,7 +801,7 @@ } /** - * Used by `escape` to escape characters for inclusion in HTML. + * Used by `escape` to convert characters to HTML entities. * * @private * @param {String} match The matched character to escape. @@ -913,6 +936,17 @@ return token + index; } + /** + * Used by `unescape` to convert HTML entities to characters. + * + * @private + * @param {String} match The matched character to unescape. + * @returns {String} Returns the unescaped character. + */ + function unescapeHtmlChar(match) { + return htmlUnescapes[match]; + } + /*--------------------------------------------------------------------------*/ /** @@ -1852,7 +1886,6 @@ * array, string, or `arguments` object. If `value` is an object, size is * determined by returning the number of own enumerable properties it has. * - * @deprecated * @static * @memberOf _ * @category Objects @@ -3653,8 +3686,8 @@ /*--------------------------------------------------------------------------*/ /** - * Escapes a string for inclusion in HTML, replacing `&`, `<`, `"`, and `'` - * characters. + * Converts the characters `&`, `<`, `>`, `"`, and `'` in `string` to their + * corresponding HTML entities. * * @static * @memberOf _ @@ -4003,6 +4036,24 @@ } } + /** + * Converts the HTML entities `&`, `<`, `>`, `"`, and `'` + * in `string` to their corresponding characters. + * + * @static + * @memberOf _ + * @category Utilities + * @param {String} string The string to unescape. + * @returns {String} Returns the unescaped string. + * @example + * + * _.unescape('Moe, Larry & Curly'); + * // => "Moe, Larry & Curly" + */ + function unescape(string) { + return string == null ? '' : (string + '').replace(reEscapedHtml, unescapeHtmlChar); + } + /** * Generates a unique id. If `prefix` is passed, the id will be appended to it. * @@ -4204,6 +4255,7 @@ lodash.throttle = throttle; lodash.times = times; lodash.toArray = toArray; + lodash.unescape = unescape; lodash.union = union; lodash.uniq = uniq; lodash.uniqueId = uniqueId; diff --git a/test/test.js b/test/test.js index b8344e892..687e49a68 100644 --- a/test/test.js +++ b/test/test.js @@ -395,15 +395,11 @@ QUnit.module('lodash.escape'); (function() { - test('should not escape the ">" character', function() { - equal(_.escape('>'), '>'); - }); - test('should not escape the "/" character', function() { equal(_.escape('/'), '/'); }); - test('should return empty string when passed `null` or `undefined`', function() { + test('should return an empty string when passed `null` or `undefined`', function() { equal(_.escape(null), ''); equal(_.escape(undefined), ''); }); @@ -1540,6 +1536,30 @@ /*--------------------------------------------------------------------------*/ + QUnit.module('lodash.unescape'); + + (function() { + test('should perform a case-insensitive replacement of numeric character references', function() { + equal(_.unescape(''x''), "'x'"); + }); + + test('should unescape entities in the correct order', function() { + equal(_.unescape('&lt;'), '<'); + }); + + test('should unescape the proper entities', function() { + var escaped = '<h1>Moe's famous "death by chocolate" brownies & cake<\/h1>'; + equal(_.unescape(escaped), '

Moe\'s famous "death by chocolate" brownies & cake<\/h1>'); + }); + + test('should return an empty string when passed `null` or `undefined`', function() { + equal(_.unescape(null), ''); + equal(_.unescape(undefined), ''); + }); + }()); + + /*--------------------------------------------------------------------------*/ + QUnit.module('lodash.uniq'); (function() {