| var assignInWith = require('./assignInWith'), |
| attempt = require('./attempt'), |
| baseValues = require('./_baseValues'), |
| customDefaultsAssignIn = require('./_customDefaultsAssignIn'), |
| escapeStringChar = require('./_escapeStringChar'), |
| isError = require('./isError'), |
| isIterateeCall = require('./_isIterateeCall'), |
| keys = require('./keys'), |
| reInterpolate = require('./_reInterpolate'), |
| templateSettings = require('./templateSettings'), |
| toString = require('./toString'); |
| |
| /** Used to match empty string literals in compiled template source. */ |
| var reEmptyStringLeading = /\b__p \+= '';/g, |
| reEmptyStringMiddle = /\b(__p \+=) '' \+/g, |
| reEmptyStringTrailing = /(__e\(.*?\)|\b__t\)) \+\n'';/g; |
| |
| /** |
| * Used to match |
| * [ES template delimiters](http://ecma-international.org/ecma-262/7.0/#sec-template-literal-lexical-components). |
| */ |
| var reEsTemplate = /\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g; |
| |
| /** Used to ensure capturing order of template delimiters. */ |
| var reNoMatch = /($^)/; |
| |
| /** Used to match unescaped characters in compiled string literals. */ |
| var reUnescapedString = /['\n\r\u2028\u2029\\]/g; |
| |
| /** Used for built-in method references. */ |
| var objectProto = Object.prototype; |
| |
| /** Used to check objects for own properties. */ |
| var hasOwnProperty = objectProto.hasOwnProperty; |
| |
| /** |
| * Creates a compiled template function that can interpolate data properties |
| * in "interpolate" delimiters, HTML-escape interpolated data properties in |
| * "escape" delimiters, and execute JavaScript in "evaluate" delimiters. Data |
| * properties may be accessed as free variables in the template. If a setting |
| * object is given, it takes precedence over `_.templateSettings` values. |
| * |
| * **Note:** In the development build `_.template` utilizes |
| * [sourceURLs](http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/#toc-sourceurl) |
| * for easier debugging. |
| * |
| * For more information on precompiling templates see |
| * [lodash's custom builds documentation](https://lodash.com/custom-builds). |
| * |
| * For more information on Chrome extension sandboxes see |
| * [Chrome's extensions documentation](https://developer.chrome.com/extensions/sandboxingEval). |
| * |
| * @static |
| * @since 0.1.0 |
| * @memberOf _ |
| * @category String |
| * @param {string} [string=''] The template string. |
| * @param {Object} [options={}] The options object. |
| * @param {RegExp} [options.escape=_.templateSettings.escape] |
| * The HTML "escape" delimiter. |
| * @param {RegExp} [options.evaluate=_.templateSettings.evaluate] |
| * The "evaluate" delimiter. |
| * @param {Object} [options.imports=_.templateSettings.imports] |
| * An object to import into the template as free variables. |
| * @param {RegExp} [options.interpolate=_.templateSettings.interpolate] |
| * The "interpolate" delimiter. |
| * @param {string} [options.sourceURL='templateSources[n]'] |
| * The sourceURL of the compiled template. |
| * @param {string} [options.variable='obj'] |
| * The data object variable name. |
| * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`. |
| * @returns {Function} Returns the compiled template function. |
| * @example |
| * |
| * // Use the "interpolate" delimiter to create a compiled template. |
| * var compiled = _.template('hello <%= user %>!'); |
| * compiled({ 'user': 'fred' }); |
| * // => 'hello fred!' |
| * |
| * // Use the HTML "escape" delimiter to escape data property values. |
| * var compiled = _.template('<b><%- value %></b>'); |
| * compiled({ 'value': '<script>' }); |
| * // => '<b><script></b>' |
| * |
| * // Use the "evaluate" delimiter to execute JavaScript and generate HTML. |
| * var compiled = _.template('<% _.forEach(users, function(user) { %><li><%- user %></li><% }); %>'); |
| * compiled({ 'users': ['fred', 'barney'] }); |
| * // => '<li>fred</li><li>barney</li>' |
| * |
| * // Use the internal `print` function in "evaluate" delimiters. |
| * var compiled = _.template('<% print("hello " + user); %>!'); |
| * compiled({ 'user': 'barney' }); |
| * // => 'hello barney!' |
| * |
| * // Use the ES template literal delimiter as an "interpolate" delimiter. |
| * // Disable support by replacing the "interpolate" delimiter. |
| * var compiled = _.template('hello ${ user }!'); |
| * compiled({ 'user': 'pebbles' }); |
| * // => 'hello pebbles!' |
| * |
| * // Use backslashes to treat delimiters as plain text. |
| * var compiled = _.template('<%= "\\<%- value %\\>" %>'); |
| * compiled({ 'value': 'ignored' }); |
| * // => '<%- value %>' |
| * |
| * // Use the `imports` option to import `jQuery` as `jq`. |
| * var text = '<% jq.each(users, function(user) { %><li><%- user %></li><% }); %>'; |
| * var compiled = _.template(text, { 'imports': { 'jq': jQuery } }); |
| * compiled({ 'users': ['fred', 'barney'] }); |
| * // => '<li>fred</li><li>barney</li>' |
| * |
| * // Use the `sourceURL` option to specify a custom sourceURL for the template. |
| * var compiled = _.template('hello <%= user %>!', { 'sourceURL': '/basic/greeting.jst' }); |
| * compiled(data); |
| * // => Find the source of "greeting.jst" under the Sources tab or Resources panel of the web inspector. |
| * |
| * // Use the `variable` option to ensure a with-statement isn't used in the compiled template. |
| * var compiled = _.template('hi <%= data.user %>!', { 'variable': 'data' }); |
| * compiled.source; |
| * // => function(data) { |
| * // var __t, __p = ''; |
| * // __p += 'hi ' + ((__t = ( data.user )) == null ? '' : __t) + '!'; |
| * // return __p; |
| * // } |
| * |
| * // Use custom template delimiters. |
| * _.templateSettings.interpolate = /{{([\s\S]+?)}}/g; |
| * var compiled = _.template('hello {{ user }}!'); |
| * compiled({ 'user': 'mustache' }); |
| * // => 'hello mustache!' |
| * |
| * // Use the `source` property to inline compiled templates for meaningful |
| * // line numbers in error messages and stack traces. |
| * fs.writeFileSync(path.join(process.cwd(), 'jst.js'), '\ |
| * var JST = {\ |
| * "main": ' + _.template(mainText).source + '\ |
| * };\ |
| * '); |
| */ |
| function template(string, options, guard) { |
| // Based on John Resig's `tmpl` implementation |
| // (http://ejohn.org/blog/javascript-micro-templating/) |
| // and Laura Doktorova's doT.js (https://github.com/olado/doT). |
| var settings = templateSettings.imports._.templateSettings || templateSettings; |
| |
| if (guard && isIterateeCall(string, options, guard)) { |
| options = undefined; |
| } |
| string = toString(string); |
| options = assignInWith({}, options, settings, customDefaultsAssignIn); |
| |
| var imports = assignInWith({}, options.imports, settings.imports, customDefaultsAssignIn), |
| importsKeys = keys(imports), |
| importsValues = baseValues(imports, importsKeys); |
| |
| var isEscaping, |
| isEvaluating, |
| index = 0, |
| interpolate = options.interpolate || reNoMatch, |
| source = "__p += '"; |
| |
| // Compile the regexp to match each delimiter. |
| var reDelimiters = RegExp( |
| (options.escape || reNoMatch).source + '|' + |
| interpolate.source + '|' + |
| (interpolate === reInterpolate ? reEsTemplate : reNoMatch).source + '|' + |
| (options.evaluate || reNoMatch).source + '|$' |
| , 'g'); |
| |
| // Use a sourceURL for easier debugging. |
| // The sourceURL gets injected into the source that's eval-ed, so be careful |
| // with lookup (in case of e.g. prototype pollution), and strip newlines if any. |
| // A newline wouldn't be a valid sourceURL anyway, and it'd enable code injection. |
| var sourceURL = hasOwnProperty.call(options, 'sourceURL') |
| ? ('//# sourceURL=' + |
| (options.sourceURL + '').replace(/[\r\n]/g, ' ') + |
| '\n') |
| : ''; |
| |
| string.replace(reDelimiters, function(match, escapeValue, interpolateValue, esTemplateValue, evaluateValue, offset) { |
| interpolateValue || (interpolateValue = esTemplateValue); |
| |
| // Escape characters that can't be included in string literals. |
| source += string.slice(index, offset).replace(reUnescapedString, escapeStringChar); |
| |
| // Replace delimiters with snippets. |
| if (escapeValue) { |
| isEscaping = true; |
| source += "' +\n__e(" + escapeValue + ") +\n'"; |
| } |
| if (evaluateValue) { |
| isEvaluating = true; |
| source += "';\n" + evaluateValue + ";\n__p += '"; |
| } |
| if (interpolateValue) { |
| source += "' +\n((__t = (" + interpolateValue + ")) == null ? '' : __t) +\n'"; |
| } |
| index = offset + match.length; |
| |
| // The JS engine embedded in Adobe products needs `match` returned in |
| // order to produce the correct `offset` value. |
| return match; |
| }); |
| |
| source += "';\n"; |
| |
| // If `variable` is not specified wrap a with-statement around the generated |
| // code to add the data object to the top of the scope chain. |
| // Like with sourceURL, we take care to not check the option's prototype, |
| // as this configuration is a code injection vector. |
| var variable = hasOwnProperty.call(options, 'variable') && options.variable; |
| if (!variable) { |
| source = 'with (obj) {\n' + source + '\n}\n'; |
| } |
| // Cleanup code by stripping empty strings. |
| source = (isEvaluating ? source.replace(reEmptyStringLeading, '') : source) |
| .replace(reEmptyStringMiddle, '$1') |
| .replace(reEmptyStringTrailing, '$1;'); |
| |
| // Frame code as the function body. |
| source = 'function(' + (variable || 'obj') + ') {\n' + |
| (variable |
| ? '' |
| : 'obj || (obj = {});\n' |
| ) + |
| "var __t, __p = ''" + |
| (isEscaping |
| ? ', __e = _.escape' |
| : '' |
| ) + |
| (isEvaluating |
| ? ', __j = Array.prototype.join;\n' + |
| "function print() { __p += __j.call(arguments, '') }\n" |
| : ';\n' |
| ) + |
| source + |
| 'return __p\n}'; |
| |
| var result = attempt(function() { |
| return Function(importsKeys, sourceURL + 'return ' + source) |
| .apply(undefined, importsValues); |
| }); |
| |
| // Provide the compiled function's source by its `toString` method or |
| // the `source` property as a convenience for inlining compiled templates. |
| result.source = source; |
| if (isError(result)) { |
| throw result; |
| } |
| return result; |
| } |
| |
| module.exports = template; |