/** * trimpath template. release 1.1.2. * copyright (c) 2004 - 2007 trimpath. * * trimpath template is licensed under the gnu general public license * and the apache license, version 2.0, as follows: * * this program is free software; you can redistribute it and/or * modify it under the terms of the gnu general public license * as published by the free software foundation; either version 2 * of the license, or (at your option) any later version. * * this program is distributed without any warranty; without even the * implied warranty of merchantability or fitness for a particular purpose. * see the gnu general public license for more details. * * you should have received a copy of the gnu general public license * along with this program; if not, write to the free software * foundation, inc., 59 temple place - suite 330, boston, ma 02111-1307, usa. * * licensed under the apache license, version 2.0 (the "license"); * you may not use this file except in compliance with the license. * you may obtain a copy of the license at * * http://www.apache.org/licenses/license-2.0 * * unless required by applicable law or agreed to in writing, software * distributed under the license is distributed on an "as is" basis, * without warranties or conditions of any kind, either express or implied. * see the license for the specific language governing permissions and * limitations under the license. */ if (typeof(trimpath) == 'undefined') trimpath = {}; // todo: debugging mode vs stop-on-error mode - runtime flag. // todo: handle || (or) characters and backslashes. // todo: add more modifiers. (function() { // using a closure to keep global namespace clean. if (trimpath.evalex == null) trimpath.evalex = function(src) { return eval(src); }; var undefined; if (array.prototype.pop == null) // ie 5.x fix from igor poteryaev. array.prototype.pop = function() { if (this.length === 0) {return undefined;} return this[--this.length]; }; if (array.prototype.push == null) // ie 5.x fix from igor poteryaev. array.prototype.push = function() { for (var i = 0; i < arguments.length; ++i) {this[this.length] = arguments[i];} return this.length; }; trimpath.parsetemplate = function(tmplcontent, opttmplname, optetc) { if (optetc == null) optetc = trimpath.parsetemplate_etc; var funcsrc = parse(tmplcontent, opttmplname, optetc); var func = trimpath.evalex(funcsrc, opttmplname, 1); if (func != null) return new optetc.template(opttmplname, tmplcontent, funcsrc, func, optetc); return null; } var exceptiondetails = function(e) { return (e.tostring()) + ";\n " + (e.message) + ";\n " + (e.name) + ";\n " + (e.stack || 'no stack trace') + ";\n " + (e.description || 'no further description') + ";\n " + (e.filename || 'no file name') + ";\n " + (e.linenumber || 'no line number'); } try { string.prototype.process = function(context, optflags) { var template = trimpath.parsetemplate(this, null); if (template != null) return template.process(context, optflags); return this; } } catch (e) { // swallow exception, such as when string.prototype is sealed. } trimpath.parsetemplate_etc = {}; // exposed for extensibility. trimpath.parsetemplate_etc.statementtag = "forelse|for|if|elseif|else|var|macro"; trimpath.parsetemplate_etc.statementdef = { // lookup table for statement tags. "if" : { delta: 1, prefix: "if (", suffix: ") {", parammin: 1 }, "else" : { delta: 0, prefix: "} else {" }, "elseif" : { delta: 0, prefix: "} else if (", suffix: ") {", paramdefault: "true" }, "/if" : { delta: -1, prefix: "}" }, "for" : { delta: 1, parammin: 3, prefixfunc : function(stmtparts, state, tmplname, etc) { if (stmtparts[2] != "in") throw new etc.parseerror(tmplname, state.line, "bad for loop statement: " + stmtparts.join(' ')); var itervar = stmtparts[1]; var listvar = "__list__" + itervar; return [ "var ", listvar, " = ", stmtparts[3], ";", // fix from ross shaull for hash looping, make sure that we have an array of loop lengths to treat like a stack. "var __length_stack__;", "if (typeof(__length_stack__) == 'undefined' || !__length_stack__.length) __length_stack__ = new array();", "__length_stack__[__length_stack__.length] = 0;", // push a new for-loop onto the stack of loop lengths. "if ((", listvar, ") != null) { ", "var ", itervar, "_ct = 0;", // itervar_ct variable, added by b. bittman "for (var ", itervar, "_index in ", listvar, ") { ", itervar, "_ct++;", "if (typeof(", listvar, "[", itervar, "_index]) == 'function') {continue;}", // ie 5.x fix from igor poteryaev. "__length_stack__[__length_stack__.length - 1]++;", "var ", itervar, " = ", listvar, "[", itervar, "_index];" ].join(""); } }, "forelse" : { delta: 0, prefix: "} } if (__length_stack__[__length_stack__.length - 1] == 0) { if (", suffix: ") {", paramdefault: "true" }, "/for" : { delta: -1, prefix: "} }; delete __length_stack__[__length_stack__.length - 1];" }, // remove the just-finished for-loop from the stack of loop lengths. "var" : { delta: 0, prefix: "var ", suffix: ";" }, "macro" : { delta: 1, prefixfunc : function(stmtparts, state, tmplname, etc) { var macroname = stmtparts[1].split('(')[0]; return [ "var ", macroname, " = function", stmtparts.slice(1).join(' ').substring(macroname.length), "{ var _out_arr = []; var _out = { write: function(m) { if (m) _out_arr.push(m); } }; " ].join(''); } }, "/macro" : { delta: -1, prefix: " return _out_arr.join(''); };" } } trimpath.parsetemplate_etc.modifierdef = { "eat" : function(v) { return ""; }, "escape" : function(s) { return string(s).replace(/&/g, "&").replace(//g, ">"); }, "capitalize" : function(s) { return string(s).touppercase(); }, "default" : function(s, d) { return s != null ? s : d; } } trimpath.parsetemplate_etc.modifierdef.h = trimpath.parsetemplate_etc.modifierdef.escape; trimpath.parsetemplate_etc.template = function(tmplname, tmplcontent, funcsrc, func, etc) { this.process = function(context, flags) { if (context == null) context = {}; if (context._modifiers == null) context._modifiers = {}; if (context.defined == null) context.defined = function(str) { return (context[str] != undefined); }; for (var k in etc.modifierdef) { if (context._modifiers[k] == null) context._modifiers[k] = etc.modifierdef[k]; } if (flags == null) flags = {}; var resultarr = []; var resultout = { write: function(m) { resultarr.push(m); } }; try { func(resultout, context, flags); } catch (e) { if (flags.throwexceptions == true) throw e; var result = new string(resultarr.join("") + "[error: template:
" + exceptiondetails(e) + "
]"); result["exception"] = e; return result; } return resultarr.join(""); } this.name = tmplname; this.source = tmplcontent; this.sourcefunc = funcsrc; this.tostring = function() { return "trimpath.template [" + tmplname + "]"; } } trimpath.parsetemplate_etc.parseerror = function(name, line, message) { this.name = name; this.line = line; this.message = message; } trimpath.parsetemplate_etc.parseerror.prototype.tostring = function() { return ("trimpath template parseerror in " + this.name + ": line " + this.line + ", " + this.message); } var parse = function(body, tmplname, etc) { body = cleanwhitespace(body); var functext = [ "var trimpath_template_temp = function(_out, _context, _flags) { with (_context) {" ]; var state = { stack: [], line: 1 }; // todo: fix line number counting. var endstmtprev = -1; while (endstmtprev + 1 < body.length) { var begstmt = endstmtprev; // scan until we find some statement markup. begstmt = body.indexof("{", begstmt + 1); while (begstmt >= 0) { var endstmt = body.indexof('}', begstmt + 1); var stmt = body.substring(begstmt, endstmt); var blockrx = stmt.match(/^\{(cdata|minify|eval)/); // from b. bittman, minify/eval/cdata implementation. if (blockrx) { var blocktype = blockrx[1]; var blockmarkerbeg = begstmt + blocktype.length + 1; var blockmarkerend = body.indexof('}', blockmarkerbeg); if (blockmarkerend >= 0) { var blockmarker; if( blockmarkerend - blockmarkerbeg <= 0 ) { blockmarker = "{/" + blocktype + "}"; } else { blockmarker = body.substring(blockmarkerbeg + 1, blockmarkerend); } var blockend = body.indexof(blockmarker, blockmarkerend + 1); if (blockend >= 0) { emitsectiontext(body.substring(endstmtprev + 1, begstmt), functext); var blocktext = body.substring(blockmarkerend + 1, blockend); if (blocktype == 'cdata') { emittext(blocktext, functext); } else if (blocktype == 'minify') { emittext(scrubwhitespace(blocktext), functext); } else if (blocktype == 'eval') { if (blocktext != null && blocktext.length > 0) // from b. bittman, eval should not execute until process(). functext.push('_out.write( (function() { ' + blocktext + ' })() );'); } begstmt = endstmtprev = blockend + blockmarker.length - 1; } } } else if (body.charat(begstmt - 1) != '$' && // not an expression or backslashed, body.charat(begstmt - 1) != '\\') { // so check if it is a statement tag. var offset = (body.charat(begstmt + 1) == '/' ? 2 : 1); // close tags offset of 2 skips '/'. // 10 is larger than maximum statement tag length. if (body.substring(begstmt + offset, begstmt + 10 + offset).search(trimpath.parsetemplate_etc.statementtag) == 0) break; // found a match. } begstmt = body.indexof("{", begstmt + 1); } if (begstmt < 0) // in "a{for}c", begstmt will be 1. break; var endstmt = body.indexof("}", begstmt + 1); // in "a{for}c", endstmt will be 5. if (endstmt < 0) break; emitsectiontext(body.substring(endstmtprev + 1, begstmt), functext); emitstatement(body.substring(begstmt, endstmt + 1), state, functext, tmplname, etc); endstmtprev = endstmt; } emitsectiontext(body.substring(endstmtprev + 1), functext); if (state.stack.length != 0) throw new etc.parseerror(tmplname, state.line, "unclosed, unmatched statement(s): " + state.stack.join(",")); functext.push("}}; trimpath_template_temp"); return functext.join(""); } var emitstatement = function(stmtstr, state, functext, tmplname, etc) { var parts = stmtstr.slice(1, -1).split(' '); var stmt = etc.statementdef[parts[0]]; // here, parts[0] == for/if/else/... if (stmt == null) { // not a real statement. emitsectiontext(stmtstr, functext); return; } if (stmt.delta < 0) { if (state.stack.length <= 0) throw new etc.parseerror(tmplname, state.line, "close tag does not match any previous statement: " + stmtstr); state.stack.pop(); } if (stmt.delta > 0) state.stack.push(stmtstr); if (stmt.parammin != null && stmt.parammin >= parts.length) throw new etc.parseerror(tmplname, state.line, "statement needs more parameters: " + stmtstr); if (stmt.prefixfunc != null) functext.push(stmt.prefixfunc(parts, state, tmplname, etc)); else functext.push(stmt.prefix); if (stmt.suffix != null) { if (parts.length <= 1) { if (stmt.paramdefault != null) functext.push(stmt.paramdefault); } else { for (var i = 1; i < parts.length; i++) { if (i > 1) functext.push(' '); functext.push(parts[i]); } } functext.push(stmt.suffix); } } var emitsectiontext = function(text, functext) { if (text.length <= 0) return; var nlprefix = 0; // index to first non-newline in prefix. var nlsuffix = text.length - 1; // index to first non-space/tab in suffix. while (nlprefix < text.length && (text.charat(nlprefix) == '\n')) nlprefix++; while (nlsuffix >= 0 && (text.charat(nlsuffix) == ' ' || text.charat(nlsuffix) == '\t')) nlsuffix--; if (nlsuffix < nlprefix) nlsuffix = nlprefix; if (nlprefix > 0) { functext.push('if (_flags.keepwhitespace == true) _out.write("'); var s = text.substring(0, nlprefix).replace('\n', '\\n'); // a macro ie fix from bjessen. if (s.charat(s.length - 1) == '\n') s = s.substring(0, s.length - 1); functext.push(s); functext.push('");'); } var lines = text.substring(nlprefix, nlsuffix + 1).split('\n'); for (var i = 0; i < lines.length; i++) { emitsectiontextline(lines[i], functext); if (i < lines.length - 1) functext.push('_out.write("\\n");\n'); } if (nlsuffix + 1 < text.length) { functext.push('if (_flags.keepwhitespace == true) _out.write("'); var s = text.substring(nlsuffix + 1).replace('\n', '\\n'); if (s.charat(s.length - 1) == '\n') s = s.substring(0, s.length - 1); functext.push(s); functext.push('");'); } } var emitsectiontextline = function(line, functext) { var endmarkprev = '}'; var endexprprev = -1; while (endexprprev + endmarkprev.length < line.length) { var begmark = "${", endmark = "}"; var begexpr = line.indexof(begmark, endexprprev + endmarkprev.length); // in "a${b}c", begexpr == 1 if (begexpr < 0) break; if (line.charat(begexpr + 2) == '%') { begmark = "${%"; endmark = "%}"; } var endexpr = line.indexof(endmark, begexpr + begmark.length); // in "a${b}c", endexpr == 4; if (endexpr < 0) break; emittext(line.substring(endexprprev + endmarkprev.length, begexpr), functext); // example: exprs == 'firstname|default:"john doe"|capitalize'.split('|') var exprarr = line.substring(begexpr + begmark.length, endexpr).replace(/\|\|/g, "#@@#").split('|'); for (var k in exprarr) { if (exprarr[k].replace) // ie 5.x fix from igor poteryaev. exprarr[k] = exprarr[k].replace(/#@@#/g, '||'); } functext.push('_out.write('); emitexpression(exprarr, exprarr.length - 1, functext); functext.push(');'); endexprprev = endexpr; endmarkprev = endmark; } emittext(line.substring(endexprprev + endmarkprev.length), functext); } var emittext = function(text, functext) { if (text == null || text.length <= 0) return; text = text.replace(/\\/g, '\\\\'); text = text.replace(/\n/g, '\\n'); text = text.replace(/"/g, '\\"'); functext.push('_out.write("'); functext.push(text); functext.push('");'); } var emitexpression = function(exprarr, index, functext) { // ex: foo|a:x|b:y1,y2|c:z1,z2 is emitted as c(b(a(foo,x),y1,y2),z1,z2) var expr = exprarr[index]; // ex: exprarr == [firstname,capitalize,default:"john doe"] if (index <= 0) { // ex: expr == 'default:"john doe"' functext.push(expr); return; } var parts = expr.split(':'); functext.push('_modifiers["'); functext.push(parts[0]); // the parts[0] is a modifier function name, like capitalize. functext.push('"]('); emitexpression(exprarr, index - 1, functext); if (parts.length > 1) { functext.push(','); functext.push(parts[1]); } functext.push(')'); } var cleanwhitespace = function(result) { result = result.replace(/\t/g, " "); result = result.replace(/\r\n/g, "\n"); result = result.replace(/\r/g, "\n"); result = result.replace(/^(\s*\s*(\s+\s+)*)\s*$/, '$1'); // right trim by igor poteryaev. return result; } var scrubwhitespace = function(result) { result = result.replace(/^\s+/g, ""); result = result.replace(/\s+$/g, ""); result = result.replace(/\s+/g, " "); result = result.replace(/^(\s*\s*(\s+\s+)*)\s*$/, '$1'); // right trim by igor poteryaev. return result; } // the dom helper functions depend on dom/dhtml, so they only work in a browser. // however, these are not considered core to the engine. // trimpath.parsedomtemplate = function(elementid, optdocument, optetc) { if (optdocument == null) optdocument = document; var element = optdocument.getelementbyid(elementid); var content = element.value; // like textarea.value. if (content == null) content = element.innerhtml; // like textarea.innerhtml. content = content.replace(/</g, "<").replace(/>/g, ">"); return trimpath.parsetemplate(content, elementid, optetc); } trimpath.processdomtemplate = function(elementid, context, optflags, optdocument, optetc) { return trimpath.parsedomtemplate(elementid, optdocument, optetc).process(context, optflags); } }) ();