| @@ 1-238 (lines=238) @@ | ||
| 1 | import assignInWith from './assignInWith.js'; |
|
| 2 | import attempt from './attempt.js'; |
|
| 3 | import baseValues from './_baseValues.js'; |
|
| 4 | import customDefaultsAssignIn from './_customDefaultsAssignIn.js'; |
|
| 5 | import escapeStringChar from './_escapeStringChar.js'; |
|
| 6 | import isError from './isError.js'; |
|
| 7 | import isIterateeCall from './_isIterateeCall.js'; |
|
| 8 | import keys from './keys.js'; |
|
| 9 | import reInterpolate from './_reInterpolate.js'; |
|
| 10 | import templateSettings from './templateSettings.js'; |
|
| 11 | import toString from './toString.js'; |
|
| 12 | ||
| 13 | /** Used to match empty string literals in compiled template source. */ |
|
| 14 | var reEmptyStringLeading = /\b__p \+= '';/g, |
|
| 15 | reEmptyStringMiddle = /\b(__p \+=) '' \+/g, |
|
| 16 | reEmptyStringTrailing = /(__e\(.*?\)|\b__t\)) \+\n'';/g; |
|
| 17 | ||
| 18 | /** |
|
| 19 | * Used to match |
|
| 20 | * [ES template delimiters](http://ecma-international.org/ecma-262/7.0/#sec-template-literal-lexical-components). |
|
| 21 | */ |
|
| 22 | var reEsTemplate = /\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g; |
|
| 23 | ||
| 24 | /** Used to ensure capturing order of template delimiters. */ |
|
| 25 | var reNoMatch = /($^)/; |
|
| 26 | ||
| 27 | /** Used to match unescaped characters in compiled string literals. */ |
|
| 28 | var reUnescapedString = /['\n\r\u2028\u2029\\]/g; |
|
| 29 | ||
| 30 | /** |
|
| 31 | * Creates a compiled template function that can interpolate data properties |
|
| 32 | * in "interpolate" delimiters, HTML-escape interpolated data properties in |
|
| 33 | * "escape" delimiters, and execute JavaScript in "evaluate" delimiters. Data |
|
| 34 | * properties may be accessed as free variables in the template. If a setting |
|
| 35 | * object is given, it takes precedence over `_.templateSettings` values. |
|
| 36 | * |
|
| 37 | * **Note:** In the development build `_.template` utilizes |
|
| 38 | * [sourceURLs](http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/#toc-sourceurl) |
|
| 39 | * for easier debugging. |
|
| 40 | * |
|
| 41 | * For more information on precompiling templates see |
|
| 42 | * [lodash's custom builds documentation](https://lodash.com/custom-builds). |
|
| 43 | * |
|
| 44 | * For more information on Chrome extension sandboxes see |
|
| 45 | * [Chrome's extensions documentation](https://developer.chrome.com/extensions/sandboxingEval). |
|
| 46 | * |
|
| 47 | * @static |
|
| 48 | * @since 0.1.0 |
|
| 49 | * @memberOf _ |
|
| 50 | * @category String |
|
| 51 | * @param {string} [string=''] The template string. |
|
| 52 | * @param {Object} [options={}] The options object. |
|
| 53 | * @param {RegExp} [options.escape=_.templateSettings.escape] |
|
| 54 | * The HTML "escape" delimiter. |
|
| 55 | * @param {RegExp} [options.evaluate=_.templateSettings.evaluate] |
|
| 56 | * The "evaluate" delimiter. |
|
| 57 | * @param {Object} [options.imports=_.templateSettings.imports] |
|
| 58 | * An object to import into the template as free variables. |
|
| 59 | * @param {RegExp} [options.interpolate=_.templateSettings.interpolate] |
|
| 60 | * The "interpolate" delimiter. |
|
| 61 | * @param {string} [options.sourceURL='templateSources[n]'] |
|
| 62 | * The sourceURL of the compiled template. |
|
| 63 | * @param {string} [options.variable='obj'] |
|
| 64 | * The data object variable name. |
|
| 65 | * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`. |
|
| 66 | * @returns {Function} Returns the compiled template function. |
|
| 67 | * @example |
|
| 68 | * |
|
| 69 | * // Use the "interpolate" delimiter to create a compiled template. |
|
| 70 | * var compiled = _.template('hello <%= user %>!'); |
|
| 71 | * compiled({ 'user': 'fred' }); |
|
| 72 | * // => 'hello fred!' |
|
| 73 | * |
|
| 74 | * // Use the HTML "escape" delimiter to escape data property values. |
|
| 75 | * var compiled = _.template('<b><%- value %></b>'); |
|
| 76 | * compiled({ 'value': '<script>' }); |
|
| 77 | * // => '<b><script></b>' |
|
| 78 | * |
|
| 79 | * // Use the "evaluate" delimiter to execute JavaScript and generate HTML. |
|
| 80 | * var compiled = _.template('<% _.forEach(users, function(user) { %><li><%- user %></li><% }); %>'); |
|
| 81 | * compiled({ 'users': ['fred', 'barney'] }); |
|
| 82 | * // => '<li>fred</li><li>barney</li>' |
|
| 83 | * |
|
| 84 | * // Use the internal `print` function in "evaluate" delimiters. |
|
| 85 | * var compiled = _.template('<% print("hello " + user); %>!'); |
|
| 86 | * compiled({ 'user': 'barney' }); |
|
| 87 | * // => 'hello barney!' |
|
| 88 | * |
|
| 89 | * // Use the ES template literal delimiter as an "interpolate" delimiter. |
|
| 90 | * // Disable support by replacing the "interpolate" delimiter. |
|
| 91 | * var compiled = _.template('hello ${ user }!'); |
|
| 92 | * compiled({ 'user': 'pebbles' }); |
|
| 93 | * // => 'hello pebbles!' |
|
| 94 | * |
|
| 95 | * // Use backslashes to treat delimiters as plain text. |
|
| 96 | * var compiled = _.template('<%= "\\<%- value %\\>" %>'); |
|
| 97 | * compiled({ 'value': 'ignored' }); |
|
| 98 | * // => '<%- value %>' |
|
| 99 | * |
|
| 100 | * // Use the `imports` option to import `jQuery` as `jq`. |
|
| 101 | * var text = '<% jq.each(users, function(user) { %><li><%- user %></li><% }); %>'; |
|
| 102 | * var compiled = _.template(text, { 'imports': { 'jq': jQuery } }); |
|
| 103 | * compiled({ 'users': ['fred', 'barney'] }); |
|
| 104 | * // => '<li>fred</li><li>barney</li>' |
|
| 105 | * |
|
| 106 | * // Use the `sourceURL` option to specify a custom sourceURL for the template. |
|
| 107 | * var compiled = _.template('hello <%= user %>!', { 'sourceURL': '/basic/greeting.jst' }); |
|
| 108 | * compiled(data); |
|
| 109 | * // => Find the source of "greeting.jst" under the Sources tab or Resources panel of the web inspector. |
|
| 110 | * |
|
| 111 | * // Use the `variable` option to ensure a with-statement isn't used in the compiled template. |
|
| 112 | * var compiled = _.template('hi <%= data.user %>!', { 'variable': 'data' }); |
|
| 113 | * compiled.source; |
|
| 114 | * // => function(data) { |
|
| 115 | * // var __t, __p = ''; |
|
| 116 | * // __p += 'hi ' + ((__t = ( data.user )) == null ? '' : __t) + '!'; |
|
| 117 | * // return __p; |
|
| 118 | * // } |
|
| 119 | * |
|
| 120 | * // Use custom template delimiters. |
|
| 121 | * _.templateSettings.interpolate = /{{([\s\S]+?)}}/g; |
|
| 122 | * var compiled = _.template('hello {{ user }}!'); |
|
| 123 | * compiled({ 'user': 'mustache' }); |
|
| 124 | * // => 'hello mustache!' |
|
| 125 | * |
|
| 126 | * // Use the `source` property to inline compiled templates for meaningful |
|
| 127 | * // line numbers in error messages and stack traces. |
|
| 128 | * fs.writeFileSync(path.join(process.cwd(), 'jst.js'), '\ |
|
| 129 | * var JST = {\ |
|
| 130 | * "main": ' + _.template(mainText).source + '\ |
|
| 131 | * };\ |
|
| 132 | * '); |
|
| 133 | */ |
|
| 134 | function template(string, options, guard) { |
|
| 135 | // Based on John Resig's `tmpl` implementation |
|
| 136 | // (http://ejohn.org/blog/javascript-micro-templating/) |
|
| 137 | // and Laura Doktorova's doT.js (https://github.com/olado/doT). |
|
| 138 | var settings = templateSettings.imports._.templateSettings || templateSettings; |
|
| 139 | ||
| 140 | if (guard && isIterateeCall(string, options, guard)) { |
|
| 141 | options = undefined; |
|
| 142 | } |
|
| 143 | string = toString(string); |
|
| 144 | options = assignInWith({}, options, settings, customDefaultsAssignIn); |
|
| 145 | ||
| 146 | var imports = assignInWith({}, options.imports, settings.imports, customDefaultsAssignIn), |
|
| 147 | importsKeys = keys(imports), |
|
| 148 | importsValues = baseValues(imports, importsKeys); |
|
| 149 | ||
| 150 | var isEscaping, |
|
| 151 | isEvaluating, |
|
| 152 | index = 0, |
|
| 153 | interpolate = options.interpolate || reNoMatch, |
|
| 154 | source = "__p += '"; |
|
| 155 | ||
| 156 | // Compile the regexp to match each delimiter. |
|
| 157 | var reDelimiters = RegExp( |
|
| 158 | (options.escape || reNoMatch).source + '|' + |
|
| 159 | interpolate.source + '|' + |
|
| 160 | (interpolate === reInterpolate ? reEsTemplate : reNoMatch).source + '|' + |
|
| 161 | (options.evaluate || reNoMatch).source + '|$' |
|
| 162 | , 'g'); |
|
| 163 | ||
| 164 | // Use a sourceURL for easier debugging. |
|
| 165 | var sourceURL = 'sourceURL' in options ? '//# sourceURL=' + options.sourceURL + '\n' : ''; |
|
| 166 | ||
| 167 | string.replace(reDelimiters, function(match, escapeValue, interpolateValue, esTemplateValue, evaluateValue, offset) { |
|
| 168 | interpolateValue || (interpolateValue = esTemplateValue); |
|
| 169 | ||
| 170 | // Escape characters that can't be included in string literals. |
|
| 171 | source += string.slice(index, offset).replace(reUnescapedString, escapeStringChar); |
|
| 172 | ||
| 173 | // Replace delimiters with snippets. |
|
| 174 | if (escapeValue) { |
|
| 175 | isEscaping = true; |
|
| 176 | source += "' +\n__e(" + escapeValue + ") +\n'"; |
|
| 177 | } |
|
| 178 | if (evaluateValue) { |
|
| 179 | isEvaluating = true; |
|
| 180 | source += "';\n" + evaluateValue + ";\n__p += '"; |
|
| 181 | } |
|
| 182 | if (interpolateValue) { |
|
| 183 | source += "' +\n((__t = (" + interpolateValue + ")) == null ? '' : __t) +\n'"; |
|
| 184 | } |
|
| 185 | index = offset + match.length; |
|
| 186 | ||
| 187 | // The JS engine embedded in Adobe products needs `match` returned in |
|
| 188 | // order to produce the correct `offset` value. |
|
| 189 | return match; |
|
| 190 | }); |
|
| 191 | ||
| 192 | source += "';\n"; |
|
| 193 | ||
| 194 | // If `variable` is not specified wrap a with-statement around the generated |
|
| 195 | // code to add the data object to the top of the scope chain. |
|
| 196 | var variable = options.variable; |
|
| 197 | if (!variable) { |
|
| 198 | source = 'with (obj) {\n' + source + '\n}\n'; |
|
| 199 | } |
|
| 200 | // Cleanup code by stripping empty strings. |
|
| 201 | source = (isEvaluating ? source.replace(reEmptyStringLeading, '') : source) |
|
| 202 | .replace(reEmptyStringMiddle, '$1') |
|
| 203 | .replace(reEmptyStringTrailing, '$1;'); |
|
| 204 | ||
| 205 | // Frame code as the function body. |
|
| 206 | source = 'function(' + (variable || 'obj') + ') {\n' + |
|
| 207 | (variable |
|
| 208 | ? '' |
|
| 209 | : 'obj || (obj = {});\n' |
|
| 210 | ) + |
|
| 211 | "var __t, __p = ''" + |
|
| 212 | (isEscaping |
|
| 213 | ? ', __e = _.escape' |
|
| 214 | : '' |
|
| 215 | ) + |
|
| 216 | (isEvaluating |
|
| 217 | ? ', __j = Array.prototype.join;\n' + |
|
| 218 | "function print() { __p += __j.call(arguments, '') }\n" |
|
| 219 | : ';\n' |
|
| 220 | ) + |
|
| 221 | source + |
|
| 222 | 'return __p\n}'; |
|
| 223 | ||
| 224 | var result = attempt(function() { |
|
| 225 | return Function(importsKeys, sourceURL + 'return ' + source) |
|
| 226 | .apply(undefined, importsValues); |
|
| 227 | }); |
|
| 228 | ||
| 229 | // Provide the compiled function's source by its `toString` method or |
|
| 230 | // the `source` property as a convenience for inlining compiled templates. |
|
| 231 | result.source = source; |
|
| 232 | if (isError(result)) { |
|
| 233 | throw result; |
|
| 234 | } |
|
| 235 | return result; |
|
| 236 | } |
|
| 237 | ||
| 238 | export default template; |
|
| 239 | ||
| @@ 1-238 (lines=238) @@ | ||
| 1 | var assignInWith = require('./assignInWith'), |
|
| 2 | attempt = require('./attempt'), |
|
| 3 | baseValues = require('./_baseValues'), |
|
| 4 | customDefaultsAssignIn = require('./_customDefaultsAssignIn'), |
|
| 5 | escapeStringChar = require('./_escapeStringChar'), |
|
| 6 | isError = require('./isError'), |
|
| 7 | isIterateeCall = require('./_isIterateeCall'), |
|
| 8 | keys = require('./keys'), |
|
| 9 | reInterpolate = require('./_reInterpolate'), |
|
| 10 | templateSettings = require('./templateSettings'), |
|
| 11 | toString = require('./toString'); |
|
| 12 | ||
| 13 | /** Used to match empty string literals in compiled template source. */ |
|
| 14 | var reEmptyStringLeading = /\b__p \+= '';/g, |
|
| 15 | reEmptyStringMiddle = /\b(__p \+=) '' \+/g, |
|
| 16 | reEmptyStringTrailing = /(__e\(.*?\)|\b__t\)) \+\n'';/g; |
|
| 17 | ||
| 18 | /** |
|
| 19 | * Used to match |
|
| 20 | * [ES template delimiters](http://ecma-international.org/ecma-262/7.0/#sec-template-literal-lexical-components). |
|
| 21 | */ |
|
| 22 | var reEsTemplate = /\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g; |
|
| 23 | ||
| 24 | /** Used to ensure capturing order of template delimiters. */ |
|
| 25 | var reNoMatch = /($^)/; |
|
| 26 | ||
| 27 | /** Used to match unescaped characters in compiled string literals. */ |
|
| 28 | var reUnescapedString = /['\n\r\u2028\u2029\\]/g; |
|
| 29 | ||
| 30 | /** |
|
| 31 | * Creates a compiled template function that can interpolate data properties |
|
| 32 | * in "interpolate" delimiters, HTML-escape interpolated data properties in |
|
| 33 | * "escape" delimiters, and execute JavaScript in "evaluate" delimiters. Data |
|
| 34 | * properties may be accessed as free variables in the template. If a setting |
|
| 35 | * object is given, it takes precedence over `_.templateSettings` values. |
|
| 36 | * |
|
| 37 | * **Note:** In the development build `_.template` utilizes |
|
| 38 | * [sourceURLs](http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/#toc-sourceurl) |
|
| 39 | * for easier debugging. |
|
| 40 | * |
|
| 41 | * For more information on precompiling templates see |
|
| 42 | * [lodash's custom builds documentation](https://lodash.com/custom-builds). |
|
| 43 | * |
|
| 44 | * For more information on Chrome extension sandboxes see |
|
| 45 | * [Chrome's extensions documentation](https://developer.chrome.com/extensions/sandboxingEval). |
|
| 46 | * |
|
| 47 | * @static |
|
| 48 | * @since 0.1.0 |
|
| 49 | * @memberOf _ |
|
| 50 | * @category String |
|
| 51 | * @param {string} [string=''] The template string. |
|
| 52 | * @param {Object} [options={}] The options object. |
|
| 53 | * @param {RegExp} [options.escape=_.templateSettings.escape] |
|
| 54 | * The HTML "escape" delimiter. |
|
| 55 | * @param {RegExp} [options.evaluate=_.templateSettings.evaluate] |
|
| 56 | * The "evaluate" delimiter. |
|
| 57 | * @param {Object} [options.imports=_.templateSettings.imports] |
|
| 58 | * An object to import into the template as free variables. |
|
| 59 | * @param {RegExp} [options.interpolate=_.templateSettings.interpolate] |
|
| 60 | * The "interpolate" delimiter. |
|
| 61 | * @param {string} [options.sourceURL='templateSources[n]'] |
|
| 62 | * The sourceURL of the compiled template. |
|
| 63 | * @param {string} [options.variable='obj'] |
|
| 64 | * The data object variable name. |
|
| 65 | * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`. |
|
| 66 | * @returns {Function} Returns the compiled template function. |
|
| 67 | * @example |
|
| 68 | * |
|
| 69 | * // Use the "interpolate" delimiter to create a compiled template. |
|
| 70 | * var compiled = _.template('hello <%= user %>!'); |
|
| 71 | * compiled({ 'user': 'fred' }); |
|
| 72 | * // => 'hello fred!' |
|
| 73 | * |
|
| 74 | * // Use the HTML "escape" delimiter to escape data property values. |
|
| 75 | * var compiled = _.template('<b><%- value %></b>'); |
|
| 76 | * compiled({ 'value': '<script>' }); |
|
| 77 | * // => '<b><script></b>' |
|
| 78 | * |
|
| 79 | * // Use the "evaluate" delimiter to execute JavaScript and generate HTML. |
|
| 80 | * var compiled = _.template('<% _.forEach(users, function(user) { %><li><%- user %></li><% }); %>'); |
|
| 81 | * compiled({ 'users': ['fred', 'barney'] }); |
|
| 82 | * // => '<li>fred</li><li>barney</li>' |
|
| 83 | * |
|
| 84 | * // Use the internal `print` function in "evaluate" delimiters. |
|
| 85 | * var compiled = _.template('<% print("hello " + user); %>!'); |
|
| 86 | * compiled({ 'user': 'barney' }); |
|
| 87 | * // => 'hello barney!' |
|
| 88 | * |
|
| 89 | * // Use the ES template literal delimiter as an "interpolate" delimiter. |
|
| 90 | * // Disable support by replacing the "interpolate" delimiter. |
|
| 91 | * var compiled = _.template('hello ${ user }!'); |
|
| 92 | * compiled({ 'user': 'pebbles' }); |
|
| 93 | * // => 'hello pebbles!' |
|
| 94 | * |
|
| 95 | * // Use backslashes to treat delimiters as plain text. |
|
| 96 | * var compiled = _.template('<%= "\\<%- value %\\>" %>'); |
|
| 97 | * compiled({ 'value': 'ignored' }); |
|
| 98 | * // => '<%- value %>' |
|
| 99 | * |
|
| 100 | * // Use the `imports` option to import `jQuery` as `jq`. |
|
| 101 | * var text = '<% jq.each(users, function(user) { %><li><%- user %></li><% }); %>'; |
|
| 102 | * var compiled = _.template(text, { 'imports': { 'jq': jQuery } }); |
|
| 103 | * compiled({ 'users': ['fred', 'barney'] }); |
|
| 104 | * // => '<li>fred</li><li>barney</li>' |
|
| 105 | * |
|
| 106 | * // Use the `sourceURL` option to specify a custom sourceURL for the template. |
|
| 107 | * var compiled = _.template('hello <%= user %>!', { 'sourceURL': '/basic/greeting.jst' }); |
|
| 108 | * compiled(data); |
|
| 109 | * // => Find the source of "greeting.jst" under the Sources tab or Resources panel of the web inspector. |
|
| 110 | * |
|
| 111 | * // Use the `variable` option to ensure a with-statement isn't used in the compiled template. |
|
| 112 | * var compiled = _.template('hi <%= data.user %>!', { 'variable': 'data' }); |
|
| 113 | * compiled.source; |
|
| 114 | * // => function(data) { |
|
| 115 | * // var __t, __p = ''; |
|
| 116 | * // __p += 'hi ' + ((__t = ( data.user )) == null ? '' : __t) + '!'; |
|
| 117 | * // return __p; |
|
| 118 | * // } |
|
| 119 | * |
|
| 120 | * // Use custom template delimiters. |
|
| 121 | * _.templateSettings.interpolate = /{{([\s\S]+?)}}/g; |
|
| 122 | * var compiled = _.template('hello {{ user }}!'); |
|
| 123 | * compiled({ 'user': 'mustache' }); |
|
| 124 | * // => 'hello mustache!' |
|
| 125 | * |
|
| 126 | * // Use the `source` property to inline compiled templates for meaningful |
|
| 127 | * // line numbers in error messages and stack traces. |
|
| 128 | * fs.writeFileSync(path.join(process.cwd(), 'jst.js'), '\ |
|
| 129 | * var JST = {\ |
|
| 130 | * "main": ' + _.template(mainText).source + '\ |
|
| 131 | * };\ |
|
| 132 | * '); |
|
| 133 | */ |
|
| 134 | function template(string, options, guard) { |
|
| 135 | // Based on John Resig's `tmpl` implementation |
|
| 136 | // (http://ejohn.org/blog/javascript-micro-templating/) |
|
| 137 | // and Laura Doktorova's doT.js (https://github.com/olado/doT). |
|
| 138 | var settings = templateSettings.imports._.templateSettings || templateSettings; |
|
| 139 | ||
| 140 | if (guard && isIterateeCall(string, options, guard)) { |
|
| 141 | options = undefined; |
|
| 142 | } |
|
| 143 | string = toString(string); |
|
| 144 | options = assignInWith({}, options, settings, customDefaultsAssignIn); |
|
| 145 | ||
| 146 | var imports = assignInWith({}, options.imports, settings.imports, customDefaultsAssignIn), |
|
| 147 | importsKeys = keys(imports), |
|
| 148 | importsValues = baseValues(imports, importsKeys); |
|
| 149 | ||
| 150 | var isEscaping, |
|
| 151 | isEvaluating, |
|
| 152 | index = 0, |
|
| 153 | interpolate = options.interpolate || reNoMatch, |
|
| 154 | source = "__p += '"; |
|
| 155 | ||
| 156 | // Compile the regexp to match each delimiter. |
|
| 157 | var reDelimiters = RegExp( |
|
| 158 | (options.escape || reNoMatch).source + '|' + |
|
| 159 | interpolate.source + '|' + |
|
| 160 | (interpolate === reInterpolate ? reEsTemplate : reNoMatch).source + '|' + |
|
| 161 | (options.evaluate || reNoMatch).source + '|$' |
|
| 162 | , 'g'); |
|
| 163 | ||
| 164 | // Use a sourceURL for easier debugging. |
|
| 165 | var sourceURL = 'sourceURL' in options ? '//# sourceURL=' + options.sourceURL + '\n' : ''; |
|
| 166 | ||
| 167 | string.replace(reDelimiters, function(match, escapeValue, interpolateValue, esTemplateValue, evaluateValue, offset) { |
|
| 168 | interpolateValue || (interpolateValue = esTemplateValue); |
|
| 169 | ||
| 170 | // Escape characters that can't be included in string literals. |
|
| 171 | source += string.slice(index, offset).replace(reUnescapedString, escapeStringChar); |
|
| 172 | ||
| 173 | // Replace delimiters with snippets. |
|
| 174 | if (escapeValue) { |
|
| 175 | isEscaping = true; |
|
| 176 | source += "' +\n__e(" + escapeValue + ") +\n'"; |
|
| 177 | } |
|
| 178 | if (evaluateValue) { |
|
| 179 | isEvaluating = true; |
|
| 180 | source += "';\n" + evaluateValue + ";\n__p += '"; |
|
| 181 | } |
|
| 182 | if (interpolateValue) { |
|
| 183 | source += "' +\n((__t = (" + interpolateValue + ")) == null ? '' : __t) +\n'"; |
|
| 184 | } |
|
| 185 | index = offset + match.length; |
|
| 186 | ||
| 187 | // The JS engine embedded in Adobe products needs `match` returned in |
|
| 188 | // order to produce the correct `offset` value. |
|
| 189 | return match; |
|
| 190 | }); |
|
| 191 | ||
| 192 | source += "';\n"; |
|
| 193 | ||
| 194 | // If `variable` is not specified wrap a with-statement around the generated |
|
| 195 | // code to add the data object to the top of the scope chain. |
|
| 196 | var variable = options.variable; |
|
| 197 | if (!variable) { |
|
| 198 | source = 'with (obj) {\n' + source + '\n}\n'; |
|
| 199 | } |
|
| 200 | // Cleanup code by stripping empty strings. |
|
| 201 | source = (isEvaluating ? source.replace(reEmptyStringLeading, '') : source) |
|
| 202 | .replace(reEmptyStringMiddle, '$1') |
|
| 203 | .replace(reEmptyStringTrailing, '$1;'); |
|
| 204 | ||
| 205 | // Frame code as the function body. |
|
| 206 | source = 'function(' + (variable || 'obj') + ') {\n' + |
|
| 207 | (variable |
|
| 208 | ? '' |
|
| 209 | : 'obj || (obj = {});\n' |
|
| 210 | ) + |
|
| 211 | "var __t, __p = ''" + |
|
| 212 | (isEscaping |
|
| 213 | ? ', __e = _.escape' |
|
| 214 | : '' |
|
| 215 | ) + |
|
| 216 | (isEvaluating |
|
| 217 | ? ', __j = Array.prototype.join;\n' + |
|
| 218 | "function print() { __p += __j.call(arguments, '') }\n" |
|
| 219 | : ';\n' |
|
| 220 | ) + |
|
| 221 | source + |
|
| 222 | 'return __p\n}'; |
|
| 223 | ||
| 224 | var result = attempt(function() { |
|
| 225 | return Function(importsKeys, sourceURL + 'return ' + source) |
|
| 226 | .apply(undefined, importsValues); |
|
| 227 | }); |
|
| 228 | ||
| 229 | // Provide the compiled function's source by its `toString` method or |
|
| 230 | // the `source` property as a convenience for inlining compiled templates. |
|
| 231 | result.source = source; |
|
| 232 | if (isError(result)) { |
|
| 233 | throw result; |
|
| 234 | } |
|
| 235 | return result; |
|
| 236 | } |
|
| 237 | ||
| 238 | module.exports = template; |
|
| 239 | ||
| @@ 1-226 (lines=226) @@ | ||
| 1 | var assignOwnDefaults = require('../internal/assignOwnDefaults'), |
|
| 2 | assignWith = require('../internal/assignWith'), |
|
| 3 | attempt = require('../utility/attempt'), |
|
| 4 | baseAssign = require('../internal/baseAssign'), |
|
| 5 | baseToString = require('../internal/baseToString'), |
|
| 6 | baseValues = require('../internal/baseValues'), |
|
| 7 | escapeStringChar = require('../internal/escapeStringChar'), |
|
| 8 | isError = require('../lang/isError'), |
|
| 9 | isIterateeCall = require('../internal/isIterateeCall'), |
|
| 10 | keys = require('../object/keys'), |
|
| 11 | reInterpolate = require('../internal/reInterpolate'), |
|
| 12 | templateSettings = require('./templateSettings'); |
|
| 13 | ||
| 14 | /** Used to match empty string literals in compiled template source. */ |
|
| 15 | var reEmptyStringLeading = /\b__p \+= '';/g, |
|
| 16 | reEmptyStringMiddle = /\b(__p \+=) '' \+/g, |
|
| 17 | reEmptyStringTrailing = /(__e\(.*?\)|\b__t\)) \+\n'';/g; |
|
| 18 | ||
| 19 | /** Used to match [ES template delimiters](http://ecma-international.org/ecma-262/6.0/#sec-template-literal-lexical-components). */ |
|
| 20 | var reEsTemplate = /\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g; |
|
| 21 | ||
| 22 | /** Used to ensure capturing order of template delimiters. */ |
|
| 23 | var reNoMatch = /($^)/; |
|
| 24 | ||
| 25 | /** Used to match unescaped characters in compiled string literals. */ |
|
| 26 | var reUnescapedString = /['\n\r\u2028\u2029\\]/g; |
|
| 27 | ||
| 28 | /** |
|
| 29 | * Creates a compiled template function that can interpolate data properties |
|
| 30 | * in "interpolate" delimiters, HTML-escape interpolated data properties in |
|
| 31 | * "escape" delimiters, and execute JavaScript in "evaluate" delimiters. Data |
|
| 32 | * properties may be accessed as free variables in the template. If a setting |
|
| 33 | * object is provided it takes precedence over `_.templateSettings` values. |
|
| 34 | * |
|
| 35 | * **Note:** In the development build `_.template` utilizes |
|
| 36 | * [sourceURLs](http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/#toc-sourceurl) |
|
| 37 | * for easier debugging. |
|
| 38 | * |
|
| 39 | * For more information on precompiling templates see |
|
| 40 | * [lodash's custom builds documentation](https://lodash.com/custom-builds). |
|
| 41 | * |
|
| 42 | * For more information on Chrome extension sandboxes see |
|
| 43 | * [Chrome's extensions documentation](https://developer.chrome.com/extensions/sandboxingEval). |
|
| 44 | * |
|
| 45 | * @static |
|
| 46 | * @memberOf _ |
|
| 47 | * @category String |
|
| 48 | * @param {string} [string=''] The template string. |
|
| 49 | * @param {Object} [options] The options object. |
|
| 50 | * @param {RegExp} [options.escape] The HTML "escape" delimiter. |
|
| 51 | * @param {RegExp} [options.evaluate] The "evaluate" delimiter. |
|
| 52 | * @param {Object} [options.imports] An object to import into the template as free variables. |
|
| 53 | * @param {RegExp} [options.interpolate] The "interpolate" delimiter. |
|
| 54 | * @param {string} [options.sourceURL] The sourceURL of the template's compiled source. |
|
| 55 | * @param {string} [options.variable] The data object variable name. |
|
| 56 | * @param- {Object} [otherOptions] Enables the legacy `options` param signature. |
|
| 57 | * @returns {Function} Returns the compiled template function. |
|
| 58 | * @example |
|
| 59 | * |
|
| 60 | * // using the "interpolate" delimiter to create a compiled template |
|
| 61 | * var compiled = _.template('hello <%= user %>!'); |
|
| 62 | * compiled({ 'user': 'fred' }); |
|
| 63 | * // => 'hello fred!' |
|
| 64 | * |
|
| 65 | * // using the HTML "escape" delimiter to escape data property values |
|
| 66 | * var compiled = _.template('<b><%- value %></b>'); |
|
| 67 | * compiled({ 'value': '<script>' }); |
|
| 68 | * // => '<b><script></b>' |
|
| 69 | * |
|
| 70 | * // using the "evaluate" delimiter to execute JavaScript and generate HTML |
|
| 71 | * var compiled = _.template('<% _.forEach(users, function(user) { %><li><%- user %></li><% }); %>'); |
|
| 72 | * compiled({ 'users': ['fred', 'barney'] }); |
|
| 73 | * // => '<li>fred</li><li>barney</li>' |
|
| 74 | * |
|
| 75 | * // using the internal `print` function in "evaluate" delimiters |
|
| 76 | * var compiled = _.template('<% print("hello " + user); %>!'); |
|
| 77 | * compiled({ 'user': 'barney' }); |
|
| 78 | * // => 'hello barney!' |
|
| 79 | * |
|
| 80 | * // using the ES delimiter as an alternative to the default "interpolate" delimiter |
|
| 81 | * var compiled = _.template('hello ${ user }!'); |
|
| 82 | * compiled({ 'user': 'pebbles' }); |
|
| 83 | * // => 'hello pebbles!' |
|
| 84 | * |
|
| 85 | * // using custom template delimiters |
|
| 86 | * _.templateSettings.interpolate = /{{([\s\S]+?)}}/g; |
|
| 87 | * var compiled = _.template('hello {{ user }}!'); |
|
| 88 | * compiled({ 'user': 'mustache' }); |
|
| 89 | * // => 'hello mustache!' |
|
| 90 | * |
|
| 91 | * // using backslashes to treat delimiters as plain text |
|
| 92 | * var compiled = _.template('<%= "\\<%- value %\\>" %>'); |
|
| 93 | * compiled({ 'value': 'ignored' }); |
|
| 94 | * // => '<%- value %>' |
|
| 95 | * |
|
| 96 | * // using the `imports` option to import `jQuery` as `jq` |
|
| 97 | * var text = '<% jq.each(users, function(user) { %><li><%- user %></li><% }); %>'; |
|
| 98 | * var compiled = _.template(text, { 'imports': { 'jq': jQuery } }); |
|
| 99 | * compiled({ 'users': ['fred', 'barney'] }); |
|
| 100 | * // => '<li>fred</li><li>barney</li>' |
|
| 101 | * |
|
| 102 | * // using the `sourceURL` option to specify a custom sourceURL for the template |
|
| 103 | * var compiled = _.template('hello <%= user %>!', { 'sourceURL': '/basic/greeting.jst' }); |
|
| 104 | * compiled(data); |
|
| 105 | * // => find the source of "greeting.jst" under the Sources tab or Resources panel of the web inspector |
|
| 106 | * |
|
| 107 | * // using the `variable` option to ensure a with-statement isn't used in the compiled template |
|
| 108 | * var compiled = _.template('hi <%= data.user %>!', { 'variable': 'data' }); |
|
| 109 | * compiled.source; |
|
| 110 | * // => function(data) { |
|
| 111 | * // var __t, __p = ''; |
|
| 112 | * // __p += 'hi ' + ((__t = ( data.user )) == null ? '' : __t) + '!'; |
|
| 113 | * // return __p; |
|
| 114 | * // } |
|
| 115 | * |
|
| 116 | * // using the `source` property to inline compiled templates for meaningful |
|
| 117 | * // line numbers in error messages and a stack trace |
|
| 118 | * fs.writeFileSync(path.join(cwd, 'jst.js'), '\ |
|
| 119 | * var JST = {\ |
|
| 120 | * "main": ' + _.template(mainText).source + '\ |
|
| 121 | * };\ |
|
| 122 | * '); |
|
| 123 | */ |
|
| 124 | function template(string, options, otherOptions) { |
|
| 125 | // Based on John Resig's `tmpl` implementation (http://ejohn.org/blog/javascript-micro-templating/) |
|
| 126 | // and Laura Doktorova's doT.js (https://github.com/olado/doT). |
|
| 127 | var settings = templateSettings.imports._.templateSettings || templateSettings; |
|
| 128 | ||
| 129 | if (otherOptions && isIterateeCall(string, options, otherOptions)) { |
|
| 130 | options = otherOptions = undefined; |
|
| 131 | } |
|
| 132 | string = baseToString(string); |
|
| 133 | options = assignWith(baseAssign({}, otherOptions || options), settings, assignOwnDefaults); |
|
| 134 | ||
| 135 | var imports = assignWith(baseAssign({}, options.imports), settings.imports, assignOwnDefaults), |
|
| 136 | importsKeys = keys(imports), |
|
| 137 | importsValues = baseValues(imports, importsKeys); |
|
| 138 | ||
| 139 | var isEscaping, |
|
| 140 | isEvaluating, |
|
| 141 | index = 0, |
|
| 142 | interpolate = options.interpolate || reNoMatch, |
|
| 143 | source = "__p += '"; |
|
| 144 | ||
| 145 | // Compile the regexp to match each delimiter. |
|
| 146 | var reDelimiters = RegExp( |
|
| 147 | (options.escape || reNoMatch).source + '|' + |
|
| 148 | interpolate.source + '|' + |
|
| 149 | (interpolate === reInterpolate ? reEsTemplate : reNoMatch).source + '|' + |
|
| 150 | (options.evaluate || reNoMatch).source + '|$' |
|
| 151 | , 'g'); |
|
| 152 | ||
| 153 | // Use a sourceURL for easier debugging. |
|
| 154 | var sourceURL = 'sourceURL' in options ? '//# sourceURL=' + options.sourceURL + '\n' : ''; |
|
| 155 | ||
| 156 | string.replace(reDelimiters, function(match, escapeValue, interpolateValue, esTemplateValue, evaluateValue, offset) { |
|
| 157 | interpolateValue || (interpolateValue = esTemplateValue); |
|
| 158 | ||
| 159 | // Escape characters that can't be included in string literals. |
|
| 160 | source += string.slice(index, offset).replace(reUnescapedString, escapeStringChar); |
|
| 161 | ||
| 162 | // Replace delimiters with snippets. |
|
| 163 | if (escapeValue) { |
|
| 164 | isEscaping = true; |
|
| 165 | source += "' +\n__e(" + escapeValue + ") +\n'"; |
|
| 166 | } |
|
| 167 | if (evaluateValue) { |
|
| 168 | isEvaluating = true; |
|
| 169 | source += "';\n" + evaluateValue + ";\n__p += '"; |
|
| 170 | } |
|
| 171 | if (interpolateValue) { |
|
| 172 | source += "' +\n((__t = (" + interpolateValue + ")) == null ? '' : __t) +\n'"; |
|
| 173 | } |
|
| 174 | index = offset + match.length; |
|
| 175 | ||
| 176 | // The JS engine embedded in Adobe products requires returning the `match` |
|
| 177 | // string in order to produce the correct `offset` value. |
|
| 178 | return match; |
|
| 179 | }); |
|
| 180 | ||
| 181 | source += "';\n"; |
|
| 182 | ||
| 183 | // If `variable` is not specified wrap a with-statement around the generated |
|
| 184 | // code to add the data object to the top of the scope chain. |
|
| 185 | var variable = options.variable; |
|
| 186 | if (!variable) { |
|
| 187 | source = 'with (obj) {\n' + source + '\n}\n'; |
|
| 188 | } |
|
| 189 | // Cleanup code by stripping empty strings. |
|
| 190 | source = (isEvaluating ? source.replace(reEmptyStringLeading, '') : source) |
|
| 191 | .replace(reEmptyStringMiddle, '$1') |
|
| 192 | .replace(reEmptyStringTrailing, '$1;'); |
|
| 193 | ||
| 194 | // Frame code as the function body. |
|
| 195 | source = 'function(' + (variable || 'obj') + ') {\n' + |
|
| 196 | (variable |
|
| 197 | ? '' |
|
| 198 | : 'obj || (obj = {});\n' |
|
| 199 | ) + |
|
| 200 | "var __t, __p = ''" + |
|
| 201 | (isEscaping |
|
| 202 | ? ', __e = _.escape' |
|
| 203 | : '' |
|
| 204 | ) + |
|
| 205 | (isEvaluating |
|
| 206 | ? ', __j = Array.prototype.join;\n' + |
|
| 207 | "function print() { __p += __j.call(arguments, '') }\n" |
|
| 208 | : ';\n' |
|
| 209 | ) + |
|
| 210 | source + |
|
| 211 | 'return __p\n}'; |
|
| 212 | ||
| 213 | var result = attempt(function() { |
|
| 214 | return Function(importsKeys, sourceURL + 'return ' + source).apply(undefined, importsValues); |
|
| 215 | }); |
|
| 216 | ||
| 217 | // Provide the compiled function's source by its `toString` method or |
|
| 218 | // the `source` property as a convenience for inlining compiled templates. |
|
| 219 | result.source = source; |
|
| 220 | if (isError(result)) { |
|
| 221 | throw result; |
|
| 222 | } |
|
| 223 | return result; |
|
| 224 | } |
|
| 225 | ||
| 226 | module.exports = template; |
|
| 227 | ||