| /** |
| * TrimPath Template. Release 1.0.38. |
| * Copyright (C) 2004, 2005 Metaha. |
| * |
| * 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. |
| */ |
| var 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 == null)
|
| TrimPath = new Object();
|
| 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; |
| }
|
|
|
| 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, "<").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: " + e.toString() + (e.message ? '; ' + e.message : '') + "]"); |
| 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); |
| } |
| }) (); |