phnq_ejs
Version:
Embedded JavaScript templating tool
189 lines (165 loc) • 4.16 kB
JavaScript
require("phnq_log").exec("phnq_ejs", function(log)
{
var phnq_core = require("phnq_core");
var ESC_REGEX = /<%(=)?([\s\S]*?)%>/g;
var BODY_FN_REGEX = /([\s,])?function\(([^)]*)\)\s*\{\s*$/;
var phnq_ejs =
{
compile: function(str, options)
{
options = options || {};
options.trimLines = options.trimLines == undefined ? true : options.trimLines;
options.expressions = options.expressions == undefined ? true : options.expressions;
// Trim leading and trailing whitespace from each line, then add a new-line char.
if(options.trimLines)
str = phnq_core.trimLines(str, true);
if(options.expressions)
str = processExpressions(str);
// console.log("EJS: ", str);
var buf = [];
buf.push("(function(_locals, _this){");
buf.push("_locals = _locals || {};");
buf.push("_this = _this || _locals;");
buf.push("var _b=[];");
buf.push("var _i=0;");
buf.push("(function(){");
buf.push("with(_locals){");
var m;
var s;
var idx = 0;
while((m = ESC_REGEX.exec(str)))
{
s = phnq_core.escapeJS(str.substring(idx, m.index));
buf.push(s ? "_b[_i++]=\""+s+"\";" : "");
s = m[2].trim();
if(s)
{
// If it's a <%= %> expression then add the result to the buf
if(m[1])
buf.push("_b[_i++]=");
var bm = BODY_FN_REGEX.exec(s);
if(bm)
{
buf.push(s.substring(0, bm.index));
buf.push(bm[1]);
buf.push("function(_b){var _i=0;");
}
else
{
buf.push(s);
if(m[1])
buf.push(";");
}
}
idx = ESC_REGEX.lastIndex;
}
s = phnq_core.escapeJS(str.substring(idx));
buf.push(s ? "_b[_i++]=\""+s+"\";" : "");
buf.push("}");
buf.push("}).call(_this);");
buf.push("return _b.join(\"\");})");
return buf.join("");
}
};
if(phnq_core.isServer())
{
module.exports = phnq_ejs;
}
else
{
window.phnq_ejs = phnq_ejs;
}
});
var processExpressions = function(ejs)
{
return processStructures(processLiterals(ejs));
};
var EXP_LITERALS_REGEX = /\$\{(.*?)\}/g
var processLiterals = function(ejs)
{
return ejs.replace(EXP_LITERALS_REGEX, function(match, $1, offset, orig)
{
return "<%="+$1+"%>";
});
};
var DOUBLE_BRACES_REGEX = /\{\{(.*)(\}{2,})/g;
var replaceDoubleBraces = function(ejs)
{
return ejs.replace(DOUBLE_BRACES_REGEX, function(match, $1, $2, offset, orig)
{
var trailingBraces = $2;
var content = $1 + trailingBraces.substring(2);
return "{"+$1.replace(/\}/g, "[[[[[RIGHT_BRACE]]]]]")+"}";
});
};
var EXP_TAG_REGEX = /\{(\/)?(\w+)(\s+.*?)?(\/)?\s*}/g;
var CONTROL_STRUCT_NAMES = /^(if|for|while|else|with|each)$/;
var nextEachIdx = 0;
var processStructures = function(ejs)
{
ejs = replaceDoubleBraces(ejs);
// collapse space between {/if} and {else}
ejs = ejs.replace(/\{\/if\}\s*?\{else\}/g, "{/if}{else}");
var nameStack = [];
return ejs.replace(EXP_TAG_REGEX, function(match, $1, $2, $3, $4, offset, orig)
{
var isClose = $1 == "/";
var name = $2;
var args = $3;
var isEmpty = $4 == "/";
if(isClose)
{
if(nameStack.length == 0 || nameStack.pop() != name)
throw "Invalid syntax";
if(name.match(CONTROL_STRUCT_NAMES))
return "<%}%>";
else
return "<%});%>";
}
else if(isEmpty)
{
return "<%="+name+"("+args+")%>";
}
else // open non-empty
{
nameStack.push(name);
if(name.match(CONTROL_STRUCT_NAMES))
{
if(name == "each")
{
var m = /([\w]*)\s*in\s*(.*)/.exec(args);
if(m)
{
var idxVar = "each"+(nextEachIdx++);
var buf = [];
buf.push("<%for(var "+idxVar+"=0; "+idxVar+"<"+m[2]+".length; "+idxVar+"++){");
buf.push("var "+m[1]+"="+m[2]+"["+idxVar+"];\n");
buf.push("var index = "+idxVar+";\n");
buf.push("%>");
return buf.join("");
}
else
{
throw "invalid 'each' syntax";
}
}
else
{
if(args)
args = "("+args+")";
else
args = "";
return "<%"+name+args+"{%>";
}
}
else
{
if(args)
args = args + ",";
else
args = "";
return "<%="+name+"("+args+"function(){%>";
}
}
}).replace(/\[\[\[\[\[RIGHT_BRACE\]\]\]\]\]/g, "}");
};