1/* 2 mustache.js â Logic-less templates in JavaScript 3 4 See http://mustache.github.com/ for more info. 5*/ 6 7var Mustache = function() { 8 var Renderer = function() {}; 9 10 Renderer.prototype = { 11 otag: "{{", 12 ctag: "}}", 13 pragmas: {}, 14 buffer: [], 15 pragmas_implemented: { 16 "IMPLICIT-ITERATOR": true 17 }, 18 context: {}, 19 20 render: function(template, context, partials, in_recursion) { 21 // reset buffer & set context 22 if(!in_recursion) { 23 this.context = context; 24 this.buffer = []; // TODO: make this non-lazy 25 } 26 27 // fail fast 28 if(!this.includes("", template)) { 29 if(in_recursion) { 30 return template; 31 } else { 32 this.send(template); 33 return; 34 } 35 } 36 37 template = this.render_pragmas(template); 38 var html = this.render_section(template, context, partials); 39 if(in_recursion) { 40 return this.render_tags(html, context, partials, in_recursion); 41 } 42 43 this.render_tags(html, context, partials, in_recursion); 44 }, 45 46 /* 47 Sends parsed lines 48 */ 49 send: function(line) { 50 if(line != "") { 51 this.buffer.push(line); 52 } 53 }, 54 55 /* 56 Looks for %PRAGMAS 57 */ 58 render_pragmas: function(template) { 59 // no pragmas 60 if(!this.includes("%", template)) { 61 return template; 62 } 63 64 var that = this; 65 var regex = new RegExp(this.otag + "%([\\w-]+) ?([\\w]+=[\\w]+)?" + 66 this.ctag); 67 return template.replace(regex, function(match, pragma, options) { 68 if(!that.pragmas_implemented[pragma]) { 69 throw({message: 70 "This implementation of mustache doesn't understand the '" + 71 pragma + "' pragma"}); 72 } 73 that.pragmas[pragma] = {}; 74 if(options) { 75 var opts = options.split("="); 76 that.pragmas[pragma][opts[0]] = opts[1]; 77 } 78 return ""; 79 // ignore unknown pragmas silently 80 }); 81 }, 82 83 /* 84 Tries to find a partial in the curent scope and render it 85 */ 86 render_partial: function(name, context, partials) { 87 name = this.trim(name); 88 if(!partials || partials[name] === undefined) { 89 throw({message: "unknown_partial '" + name + "'"}); 90 } 91 if(typeof(context[name]) != "object") { 92 return this.render(partials[name], context, partials, true); 93 } 94 return this.render(partials[name], context[name], partials, true); 95 }, 96 97 /* 98 Renders inverted (^) and normal (#) sections 99 */ 100 render_section: function(template, context, partials) { 101 if(!this.includes("#", template) && !this.includes("^", template)) { 102 return template; 103 } 104 105 var that = this; 106 // CSW - Added "+?" so it finds the tighest bound, not the widest 107 var regex = new RegExp(this.otag + "(\\^|\\#)\\s*(.+)\\s*" + this.ctag + 108 "\n*([\\s\\S]+?)" + this.otag + "\\/\\s*\\2\\s*" + this.ctag + 109 "\\s*", "mg"); 110 111 // for each {{#foo}}{{/foo}} section do... 112 return template.replace(regex, function(match, type, name, content) { 113 var value = that.find(name, context); 114 if(type == "^") { // inverted section 115 if(!value || that.is_array(value) && value.length === 0) { 116 // false or empty list, render it 117 return that.render(content, context, partials, true); 118 } else { 119 return ""; 120 } 121 } else if(type == "#") { // normal section 122 if(that.is_array(value)) { // Enumerable, Let's loop! 123 return that.map(value, function(row) { 124 return that.render(content, that.create_context(row), 125 partials, true); 126 }).join(""); 127 } else if(that.is_object(value)) { // Object, Use it as subcontext! 128 return that.render(content, that.create_context(value), 129 partials, true); 130 } else if(typeof value === "function") { 131 // higher order section 132 return value.call(context, content, function(text) { 133 return that.render(text, context, partials, true); 134 }); 135 } else if(value) { // boolean section 136 return that.render(content, context, partials, true); 137 } else { 138 return ""; 139 } 140 } 141 }); 142 }, 143 144 /* 145 Replace {{foo}} and friends with values from our view 146 */ 147 render_tags: function(template, context, partials, in_recursion) { 148 // tit for tat 149 var that = this; 150 151 var new_regex = function() { 152 return new RegExp(that.otag + "(=|!|>|\\{|%)?([^\\/#\\^]+?)\\1?" + 153 that.ctag + "+", "g"); 154 }; 155 156 var regex = new_regex(); 157 var tag_replace_callback = function(match, operator, name) { 158 switch(operator) { 159 case "!": // ignore comments 160 return ""; 161 case "=": // set new delimiters, rebuild the replace regexp 162 that.set_delimiters(name); 163 regex = new_regex(); 164 return ""; 165 case ">": // render partial 166 return that.render_partial(name, context, partials); 167 case "{": // the triple mustache is unescaped 168 return that.find(name, context); 169 default: // escape the value 170 return that.escape(that.find(name, context)); 171 } 172 }; 173 var lines = template.split("\n"); 174 for(var i = 0; i < lines.length; i++) { 175 lines[i] = lines[i].replace(regex, tag_replace_callback, this); 176 if(!in_recursion) { 177 this.send(lines[i]); 178 } 179 } 180 181 if(in_recursion) { 182 return lines.join("\n"); 183 } 184 }, 185 186 set_delimiters: function(delimiters) { 187 var dels = delimiters.split(" "); 188 this.otag = this.escape_regex(dels[0]); 189 this.ctag = this.escape_regex(dels[1]); 190 }, 191 192 escape_regex: function(text) { 193 // thank you Simon Willison 194 if(!arguments.callee.sRE) { 195 var specials = [ 196 '/', '.', '*', '+', '?', '|', 197 '(', ')', '[', ']', '{', '}', '\\' 198 ]; 199 arguments.callee.sRE = new RegExp( 200 '(\\' + specials.join('|\\') + ')', 'g' 201 ); 202 } 203 return text.replace(arguments.callee.sRE, '\\$1'); 204 }, 205 206 /* 207 find `name` in current `context`. That is find me a value 208 from the view object 209 */ 210 find: function(name, context) { 211 name = this.trim(name); 212 213 // Checks whether a value is thruthy or false or 0 214 function is_kinda_truthy(bool) { 215 return bool === false || bool === 0 || bool; 216 } 217 218 var value; 219 if(is_kinda_truthy(context[name])) { 220 value = context[name]; 221 } else if(is_kinda_truthy(this.context[name])) { 222 value = this.context[name]; 223 } 224 225 if(typeof value === "function") {
226 return value.apply(context); 227 } 228 if(value !== undefined) { 229 return value; 230 } 231 // silently ignore unkown variables 232 return ""; 233 }, 234 235 // Utility methods 236 237 /* includes tag */ 238 includes: function(needle, haystack) { 239 return haystack.indexOf(this.otag + needle) != -1; 240 }, 241 242 /* 243 Does away with nasty characters 244 */ 245 escape: function(s) { 246 s = String(s === null ? "" : s); 247 return s.replace(/&(?!\w+;)|["<>\\]/g, function(s) { 248 switch(s) { 249 case "&": return "&"; 250 case "\\": return "\\\\"; 251 case '"': return '\"'; 252 case "<": return "<"; 253 case ">": return ">"; 254 default: return s; 255 } 256 }); 257 }, 258 259 // by @langalex, support for arrays of strings 260 create_context: function(_context) { 261 if(this.is_object(_context)) { 262 return _context; 263 } else { 264 var iterator = "."; 265 if(this.pragmas["IMPLICIT-ITERATOR"]) { 266 iterator = this.pragmas["IMPLICIT-ITERATOR"].iterator; 267 } 268 var ctx = {}; 269 ctx[iterator] = _context; 270 return ctx; 271 } 272 }, 273 274 is_object: function(a) { 275 return a && typeof a == "object"; 276 }, 277 278 is_array: function(a) { 279 return Object.prototype.toString.call(a) === '[object Array]'; 280 }, 281 282 /* 283 Gets rid of leading and trailing whitespace 284 */ 285 trim: function(s) { 286 return s.replace(/^\s*|\s*$/g, ""); 287 }, 288 289 /* 290 Why, why, why? Because IE. Cry, cry cry. 291 */ 292 map: function(array, fn) { 293 if (typeof array.map == "function") { 294 return array.map(fn); 295 } else { 296 var r = []; 297 var l = array.length; 298 for(var i = 0; i < l; i++) { 299 r.push(fn(array[i])); 300 } 301 return r; 302 } 303 } 304 }; 305 306 return({ 307 name: "mustache.js", 308 version: "0.3.1-dev", 309 310 /* 311 Turns a template and view into HTML 312 */ 313 to_html: function(template, view, partials, send_fun) { 314 var renderer = new Renderer(); 315 if(send_fun) { 316 renderer.send = send_fun; 317 } 318 renderer.render(template, view, partials); 319 if(!send_fun) { 320 return renderer.buffer.join("\n"); 321 } 322 } 323 }); 324}();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.