var Haml; (function () { var matchers, self_close_tags, embedder, forceXML, escaperName, escapeHtmlByDefault; function html_escape(text) { return (text + ""). replace(/&/g, "&"). replace(//g, ">"). replace(/\"/g, """); } function render_attribs(attribs) { var key, value, result = []; for (key in attribs) { if (key !== '_content' && attribs.hasOwnProperty(key)) { switch (attribs[key]) { case 'undefined': case 'false': case 'null': case '""': break; default: try { value = JSON.parse("[" + attribs[key] +"]")[0]; if (value === true) { value = key; } else if (typeof value === 'string' && embedder.test(value)) { value = '" +\n' + parse_interpol(html_escape(value)) + ' +\n"'; } else { value = html_escape(value); } result.push(" " + key + '=\\"' + value + '\\"'); } catch (e) { result.push(" " + key + '=\\"" + '+escaperName+'(' + attribs[key] + ') + "\\"'); } } } } return result.join(""); } // Parse the attribute block using a state machine function parse_attribs(line) { var attributes = {}, l = line.length, i, c, count = 1, quote = false, skip = false, open, close, joiner, seperator, pair = { start: 1, middle: null, end: null }; if (!(l > 0 && (line.charAt(0) === '{' || line.charAt(0) === '('))) { return { _content: line[0] === ' ' ? line.substr(1, l) : line }; } open = line.charAt(0); close = (open === '{') ? '}' : ')'; joiner = (open === '{') ? ':' : '='; seperator = (open === '{') ? ',' : ' '; function process_pair() { if (typeof pair.start === 'number' && typeof pair.middle === 'number' && typeof pair.end === 'number') { var key = line.substr(pair.start, pair.middle - pair.start).trim(), value = line.substr(pair.middle + 1, pair.end - pair.middle - 1).trim(); attributes[key] = value; } pair = { start: null, middle: null, end: null }; } for (i = 1; count > 0; i += 1) { // If we reach the end of the line, then there is a problem if (i > l) { throw "Malformed attribute block"; } c = line.charAt(i); if (skip) { skip = false; } else { if (quote) { if (c === '\\') { skip = true; } if (c === quote) { quote = false; } } else { if (c === '"' || c === "'") { quote = c; } if (count === 1) { if (c === joiner) { pair.middle = i; } if (c === seperator || c === close) { pair.end = i; process_pair(); if (c === seperator) { pair.start = i + 1; } } } if (c === open || c === "(") { count += 1; } if (c === close || (count > 1 && c === ")")) { count -= 1; } } } } attributes._content = line.substr(i, line.length); return attributes; } // Split interpolated strings into an array of literals and code fragments. function parse_interpol(value) { var items = [], pos = 0, next = 0, match; while (true) { // Match up to embedded string next = value.substr(pos).search(embedder); if (next < 0) { if (pos < value.length) { items.push(JSON.stringify(value.substr(pos))); } break; } items.push(JSON.stringify(value.substr(pos, next))); pos += next; // Match embedded string match = value.substr(pos).match(embedder); next = match[0].length; if (next < 0) { break; } if(match[1] === "#"){ items.push(escaperName+"("+(match[2] || match[3])+")"); }else{ //unsafe!!! items.push(match[2] || match[3]); } pos += next; } return items.filter(function (part) { return part && part.length > 0}).join(" +\n"); } // Used to find embedded code in interpolated strings. embedder = /([#!])\{([^}]*)\}/; self_close_tags = ["meta", "img", "link", "br", "hr", "input", "area", "base"]; // All matchers' regexps should capture leading whitespace in first capture // and trailing content in last capture matchers = [ // html tags { name: "html tags", regexp: /^(\s*)((?:[.#%][a-z_\-][a-z0-9_:\-]*)+)(.*)$/i, process: function () { var line_beginning, tag, classes, ids, attribs, content, whitespaceSpecifier, whitespace={}, output; line_beginning = this.matches[2]; classes = line_beginning.match(/\.([a-z_\-][a-z0-9_\-]*)/gi); ids = line_beginning.match(/\#([a-z_\-][a-z0-9_\-]*)/gi); tag = line_beginning.match(/\%([a-z_\-][a-z0-9_:\-]*)/gi); // Default to
tag tag = tag ? tag[0].substr(1, tag[0].length) : 'div'; attribs = this.matches[3]; if (attribs) { attribs = parse_attribs(attribs); if (attribs._content) { var leader0 = attribs._content.charAt(0), leader1 = attribs._content.charAt(1), leaderLength = 0; if(leader0 == "<"){ leaderLength++; whitespace.inside = true; if(leader1 == ">"){ leaderLength++; whitespace.around = true; } }else if(leader0 == ">"){ leaderLength++; whitespace.around = true; if(leader1 == "<"){ leaderLength++; whitespace.inside = true; } } attribs._content = attribs._content.substr(leaderLength); //once we've identified the tag and its attributes, the rest is content. // this is currently trimmed for neatness. this.contents.unshift(attribs._content.trim()); delete(attribs._content); } } else { attribs = {}; } if (classes) { classes = classes.map(function (klass) { return klass.substr(1, klass.length); }).join(' '); if (attribs['class']) { try { attribs['class'] = JSON.stringify(classes + " " + JSON.parse(attribs['class'])); } catch (e) { attribs['class'] = JSON.stringify(classes + " ") + " + " + attribs['class']; } } else { attribs['class'] = JSON.stringify(classes); } } if (ids) { ids = ids.map(function (id) { return id.substr(1, id.length); }).join(' '); if (attribs.id) { attribs.id = JSON.stringify(ids + " ") + attribs.id; } else { attribs.id = JSON.stringify(ids); } } attribs = render_attribs(attribs); content = this.render_contents(); if (content === '""') { content = ''; } if(whitespace.inside){ if(content.length==0){ content='" "' }else{ try{ //remove quotes if they are there content = '" '+JSON.parse(content)+' "'; }catch(e){ content = '" "+\n'+content+'+\n" "'; } } } if (forceXML ? content.length > 0 : self_close_tags.indexOf(tag) == -1) { output = '"<' + tag + attribs + '>"' + (content.length > 0 ? ' + \n' + content : "") + ' + \n""'; } else { output = '"<' + tag + attribs + ' />"'; } if(whitespace.around){ //output now contains '"hello"' //we need to crack it open to insert whitespace. output = '" '+output.substr(1, output.length - 2)+' "'; } return output; } }, // each loops { name: "each loop", regexp: /^(\s*)(?::for|:each)\s+(?:([a-z_][a-z_\-]*),\s*)?([a-z_][a-z_\-]*)\s+in\s+(.*)(\s*)$/i, process: function () { var ivar = this.matches[2] || '__key__', // index vvar = this.matches[3], // value avar = this.matches[4], // array rvar = '__result__'; // results if (this.matches[5]) { this.contents.unshift(this.matches[5]); } return '(function () { ' + 'var ' + rvar + ' = [], ' + ivar + ', ' + vvar + '; ' + 'for (' + ivar + ' in ' + avar + ') { ' + 'if (' + avar + '.hasOwnProperty(' + ivar + ')) { ' + vvar + ' = ' + avar + '[' + ivar + ']; ' + rvar + '.push(\n' + (this.render_contents() || "''") + '\n); ' + '} } return ' + rvar + '.join(""); }).call(this)'; } }, // if statements { name: "if", regexp: /^(\s*):if\s+(.*)\s*$/i, process: function () { var condition = this.matches[2]; this.pushIfCondition([condition]); return '(function () { ' + 'if (' + condition + ') { ' + 'return (\n' + (this.render_contents() || '') + '\n);' + '} else { return ""; } }).call(this)'; } }, // else if statements { name: "else if", regexp: /^(\s*):else if\s+(.*)\s*$/i, process: function () { var condition = this.matches[2], conditionsArray = this.getIfConditions()[this.getIfConditions().length - 1], ifArray = [], ifStatement; for (var i=0, l=conditionsArray.length; i"'; } }, // raw js { name: "rawjs", regexp: /^(\s*)-\s*(.*)\s*$/i, process: function () { this.contents.unshift(this.matches[2]); return '"";' + this.contents.join("\n")+"; _$output = _$output "; } }, // raw js { name: "pre", regexp: /^(\s*):pre(\s+(.*)|$)/i, process: function () { this.contents.unshift(this.matches[2]); return '"
"+\n' + JSON.stringify(this.contents.join("\n"))+'+\n"
"'; } }, // declarations { name: "doctype", regexp: /^()!!!(?:\s*(.*))\s*$/, process: function () { var line = ''; switch ((this.matches[2] || '').toLowerCase()) { case '': // XHTML 1.0 Transitional line = ''; break; case 'strict': case '1.0': // XHTML 1.0 Strict line = ''; break; case 'frameset': // XHTML 1.0 Frameset line = ''; break; case '5': // XHTML 5 line = ''; break; case '1.1': // XHTML 1.1 line = ''; break; case 'basic': // XHTML Basic 1.1 line = ''; break; case 'mobile': // XHTML Mobile 1.2 line = ''; break; case 'xml': // XML line = ""; break; case 'xml iso-8859-1': // XML iso-8859-1 line = ""; break; } return JSON.stringify(line + "\n"); } }, // Embedded markdown. Needs to be added to exports externally. { name: "markdown", regexp: /^(\s*):markdown\s*$/i, process: function () { return parse_interpol(exports.Markdown.encode(this.contents.join("\n"))); } }, // script blocks { name: "script", regexp: /^(\s*):(?:java)?script\s*$/, process: function () { return parse_interpol('\n\n"); } }, // css blocks { name: "css", regexp: /^(\s*):css\s*$/, process: function () { return JSON.stringify('"); } } ]; function compile(lines) { var block = false, output = [], ifConditions = []; // If lines is a string, turn it into an array if (typeof lines === 'string') { lines = lines.trim().replace(/\n\r|\r/g, '\n').split('\n'); } lines.forEach(function(line) { var match, found = false; // Collect all text as raw until outdent if (block) { match = block.check_indent.exec(line); if (match) { block.contents.push(match[1] || ""); return; } else { output.push(block.process()); block = false; } } matchers.forEach(function (matcher) { if (!found) { match = matcher.regexp.exec(line); if (match) { block = { contents: [], indent_level: (match[1]), matches: match, check_indent: new RegExp("^(?:\\s*|" + match[1] + " (.*))$"), process: matcher.process, getIfConditions: function() { return ifConditions; }, pushIfCondition: function(condition) { ifConditions.push(condition); }, popIfCondition: function() { return ifConditions.pop(); }, render_contents: function () { return compile(this.contents); } }; found = true; } } }); // Match plain text if (!found) { output.push(function () { // Escaped plain text if (line[0] === '\\') { return parse_interpol(line.substr(1, line.length)); } function escapedLine(){ try { return escaperName+'('+JSON.stringify(JSON.parse(line)) +')'; } catch (e2) { return escaperName+'(' + line + ')'; } } function unescapedLine(){ try { return parse_interpol(JSON.parse(line)); } catch (e) { return line; } } // always escaped if((line.substr(0, 2) === "&=")) { line = line.substr(2, line.length).trim(); return escapedLine(); } //never escaped if((line.substr(0, 2) === "!=")) { line = line.substr(2, line.length).trim(); return unescapedLine(); } // sometimes escaped if ( (line[0] === '=')) { line = line.substr(1, line.length).trim(); if(escapeHtmlByDefault){ return escapedLine(); }else{ return unescapedLine(); } } // Plain text return parse_interpol(line); }()); } }); if (block) { output.push(block.process()); } var txt = output.filter(function (part) { return part && part.length > 0}).join(" +\n"); if(txt.length == 0){ txt = '""'; } return txt; }; function optimize(js) { var new_js = [], buffer = [], part, end; function flush() { if (buffer.length > 0) { new_js.push(JSON.stringify(buffer.join("")) + end); buffer = []; } } js.replace(/\n\r|\r/g, '\n').split('\n').forEach(function (line) { part = line.match(/^(\".*\")(\s*\+\s*)?$/); if (!part) { flush(); new_js.push(line); return; } end = part[2] || ""; part = part[1]; try { buffer.push(JSON.parse(part)); } catch (e) { flush(); new_js.push(line); } }); flush(); return new_js.join("\n"); }; function render(text, options) { options = options || {}; text = text || ""; var js = compile(text, options); if (options.optimize) { js = Haml.optimize(js); } return execute(js, options.context || Haml, options.locals); }; function execute(js, self, locals) { return (function () { with(locals || {}) { try { var _$output; eval("_$output =" + js ); return _$output; //set in eval } catch (e) { return "\n
" + html_escape(e.stack) + "
\n"; } } }).call(self); }; Haml = function Haml(haml, config) { if(typeof(config) != "object"){ forceXML = config; config = {}; } var escaper; if(config.customEscape){ escaper = ""; escaperName = config.customEscape; }else{ escaper = html_escape.toString() + "\n"; escaperName = "html_escape"; } escapeHtmlByDefault = (config.escapeHtmlByDefault || config.escapeHTML || config.escape_html); var js = optimize(compile(haml)); var str = "with(locals || {}) {\n" + " try {\n" + " var _$output=" + js + ";\n return _$output;" + " } catch (e) {\n" + " return \"\\n
\" + "+escaperName+"(e.stack) + \"
\\n\";\n" + " }\n" + "}" try{ var f = new Function("locals", escaper + str ); return f; }catch(e){ if ( typeof(console) !== 'undefined' ) { console.error(str); } throw e; } } Haml.compile = compile; Haml.optimize = optimize; Haml.render = render; Haml.execute = execute; Haml.html_escape = html_escape; }()); // Hook into module system if (typeof module !== 'undefined') { module.exports = Haml; }