template.js 2.8 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
  1. var underscore = require('./underscore.js');
  2. var defaults = require('./defaults.js');
  3. require('./templateSettings.js');
  4. // When customizing `_.templateSettings`, if you don't want to define an
  5. // interpolation, evaluation or escaping regex, we need one that is
  6. // guaranteed not to match.
  7. var noMatch = /(.)^/;
  8. // Certain characters need to be escaped so that they can be put into a
  9. // string literal.
  10. var escapes = {
  11. "'": "'",
  12. '\\': '\\',
  13. '\r': 'r',
  14. '\n': 'n',
  15. '\u2028': 'u2028',
  16. '\u2029': 'u2029'
  17. };
  18. var escapeRegExp = /\\|'|\r|\n|\u2028|\u2029/g;
  19. function escapeChar(match) {
  20. return '\\' + escapes[match];
  21. }
  22. var bareIdentifier = /^\s*(\w|\$)+\s*$/;
  23. // JavaScript micro-templating, similar to John Resig's implementation.
  24. // Underscore templating handles arbitrary delimiters, preserves whitespace,
  25. // and correctly escapes quotes within interpolated code.
  26. // NB: `oldSettings` only exists for backwards compatibility.
  27. function template(text, settings, oldSettings) {
  28. if (!settings && oldSettings) settings = oldSettings;
  29. settings = defaults({}, settings, underscore.templateSettings);
  30. // Combine delimiters into one regular expression via alternation.
  31. var matcher = RegExp([
  32. (settings.escape || noMatch).source,
  33. (settings.interpolate || noMatch).source,
  34. (settings.evaluate || noMatch).source
  35. ].join('|') + '|$', 'g');
  36. // Compile the template source, escaping string literals appropriately.
  37. var index = 0;
  38. var source = "__p+='";
  39. text.replace(matcher, function(match, escape, interpolate, evaluate, offset) {
  40. source += text.slice(index, offset).replace(escapeRegExp, escapeChar);
  41. index = offset + match.length;
  42. if (escape) {
  43. source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'";
  44. } else if (interpolate) {
  45. source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'";
  46. } else if (evaluate) {
  47. source += "';\n" + evaluate + "\n__p+='";
  48. }
  49. // Adobe VMs need the match returned to produce the correct offset.
  50. return match;
  51. });
  52. source += "';\n";
  53. var argument = settings.variable;
  54. if (argument) {
  55. if (!bareIdentifier.test(argument)) throw new Error(argument);
  56. } else {
  57. // If a variable is not specified, place data values in local scope.
  58. source = 'with(obj||{}){\n' + source + '}\n';
  59. argument = 'obj';
  60. }
  61. source = "var __t,__p='',__j=Array.prototype.join," +
  62. "print=function(){__p+=__j.call(arguments,'');};\n" +
  63. source + 'return __p;\n';
  64. var render;
  65. try {
  66. render = new Function(argument, '_', source);
  67. } catch (e) {
  68. e.source = source;
  69. throw e;
  70. }
  71. var template = function(data) {
  72. return render.call(this, data, underscore);
  73. };
  74. // Provide the compiled source as a convenience for precompilation.
  75. template.source = 'function(' + argument + '){\n' + source + '}';
  76. return template;
  77. }
  78. module.exports = template;