2 * TrimPath Template. Release 1.0.38.
3 * Copyright (C) 2004, 2005 Metaha.
5 * TrimPath Template is licensed under the GNU General Public License
6 * and the Apache License, Version 2.0, as follows:
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU General Public License
10 * as published by the Free Software Foundation; either version 2
11 * of the License, or (at your option) any later version.
13 * This program is distributed WITHOUT ANY WARRANTY; without even the
14 * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
15 * See the GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
21 * Licensed under the Apache License, Version 2.0 (the "License");
22 * you may not use this file except in compliance with the License.
23 * You may obtain a copy of the License at
25 * http://www.apache.org/licenses/LICENSE-2.0
27 * Unless required by applicable law or agreed to in writing, software
28 * distributed under the License is distributed on an "AS IS" BASIS,
29 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
30 * See the License for the specific language governing permissions and
31 * limitations under the License.
35 // TODO: Debugging mode vs stop-on-error mode - runtime flag.
\r
36 // TODO: Handle || (or) characters and backslashes.
37 // TODO: Add more modifiers.
39 (function() { // Using a closure to keep global namespace clean.
\r
40 if (TrimPath == null)
\r
41 TrimPath = new Object();
\r
42 if (TrimPath.evalEx == null)
\r
43 TrimPath.evalEx = function(src) { return eval(src); };
\r
46 if (Array.prototype.pop == null) // IE 5.x fix from Igor Poteryaev.
47 Array.prototype.pop = function() {
48 if (this.length === 0) {return UNDEFINED;}
49 return this[--this.length];
51 if (Array.prototype.push == null) // IE 5.x fix from Igor Poteryaev.
52 Array.prototype.push = function() {
53 for (var i = 0; i < arguments.length; ++i) {this[this.length] = arguments[i];}
57 TrimPath.parseTemplate = function(tmplContent, optTmplName, optEtc) {
\r
59 optEtc = TrimPath.parseTemplate_etc;
\r
60 var funcSrc = parse(tmplContent, optTmplName, optEtc);
61 var func = TrimPath.evalEx(funcSrc, optTmplName, 1);
63 return new optEtc.Template(optTmplName, tmplContent, funcSrc, func, optEtc);
68 String.prototype.process = function(context, optFlags) {
\r
69 var template = TrimPath.parseTemplate(this, null);
71 return template.process(context, optFlags);
74 } catch (e) { // Swallow exception, such as when String.prototype is sealed.
\r
77 TrimPath.parseTemplate_etc = {}; // Exposed for extensibility.
\r
78 TrimPath.parseTemplate_etc.statementTag = "forelse|for|if|elseif|else|var|macro";
\r
79 TrimPath.parseTemplate_etc.statementDef = { // Lookup table for statement tags.
80 "if" : { delta: 1, prefix: "if (", suffix: ") {", paramMin: 1 },
81 "else" : { delta: 0, prefix: "} else {" },
82 "elseif" : { delta: 0, prefix: "} else if (", suffix: ") {", paramDefault: "true" },
83 "/if" : { delta: -1, prefix: "}" },
84 "for" : { delta: 1, paramMin: 3,
85 prefixFunc : function(stmtParts, state, tmplName, etc) {
86 if (stmtParts[2] != "in")
87 throw new etc.ParseError(tmplName, state.line, "bad for loop statement: " + stmtParts.join(' '));
88 var iterVar = stmtParts[1];
89 var listVar = "__LIST__" + iterVar;
90 return [ "var ", listVar, " = ", stmtParts[3], ";",
91 // Fix from Ross Shaull for hash looping, make sure that we have an array of loop lengths to treat like a stack.
92 "var __LENGTH_STACK__;",
93 "if (typeof(__LENGTH_STACK__) == 'undefined' || !__LENGTH_STACK__.length) __LENGTH_STACK__ = new Array();",
94 "__LENGTH_STACK__[__LENGTH_STACK__.length] = 0;", // Push a new for-loop onto the stack of loop lengths.
95 "if ((", listVar, ") != null) { ",
96 "var ", iterVar, "_ct = 0;", // iterVar_ct variable, added by B. Bittman
97 "for (var ", iterVar, "_index in ", listVar, ") { ",
99 "if (typeof(", listVar, "[", iterVar, "_index]) == 'function') {continue;}", // IE 5.x fix from Igor Poteryaev.
100 "__LENGTH_STACK__[__LENGTH_STACK__.length - 1]++;",
101 "var ", iterVar, " = ", listVar, "[", iterVar, "_index];" ].join("");
103 "forelse" : { delta: 0, prefix: "} } if (__LENGTH_STACK__[__LENGTH_STACK__.length - 1] == 0) { if (", suffix: ") {", paramDefault: "true" },
104 "/for" : { delta: -1, prefix: "} }; delete __LENGTH_STACK__[__LENGTH_STACK__.length - 1];" }, // Remove the just-finished for-loop from the stack of loop lengths.
105 "var" : { delta: 0, prefix: "var ", suffix: ";" },
106 "macro" : { delta: 1,
107 prefixFunc : function(stmtParts, state, tmplName, etc) {
108 var macroName = stmtParts[1].split('(')[0];
109 return [ "var ", macroName, " = function",
110 stmtParts.slice(1).join(' ').substring(macroName.length),
111 "{ var _OUT_arr = []; var _OUT = { write: function(m) { if (m) _OUT_arr.push(m); } }; " ].join('');
113 "/macro" : { delta: -1, prefix: " return _OUT_arr.join(''); };" }
115 TrimPath.parseTemplate_etc.modifierDef = {
\r
116 "eat" : function(v) { return ""; },
\r
117 "escape" : function(s) { return String(s).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">"); },
\r
118 "capitalize" : function(s) { return String(s).toUpperCase(); },
\r
119 "default" : function(s, d) { return s != null ? s : d; }
\r
121 TrimPath.parseTemplate_etc.modifierDef.h = TrimPath.parseTemplate_etc.modifierDef.escape;
123 TrimPath.parseTemplate_etc.Template = function(tmplName, tmplContent, funcSrc, func, etc) {
124 this.process = function(context, flags) {
\r
125 if (context == null)
\r
127 if (context._MODIFIERS == null)
\r
128 context._MODIFIERS = {};
129 if (context.defined == null)
130 context.defined = function(str) { return (context[str] != undefined); };
\r
131 for (var k in etc.modifierDef) {
\r
132 if (context._MODIFIERS[k] == null)
\r
133 context._MODIFIERS[k] = etc.modifierDef[k];
\r
138 var resultOut = { write: function(m) { resultArr.push(m); } };
\r
140 func(resultOut, context, flags);
\r
142 if (flags.throwExceptions == true)
\r
144 var result = new String(resultArr.join("") + "[ERROR: " + e.toString() + (e.message ? '; ' + e.message : '') + "]");
145 result["exception"] = e;
148 return resultArr.join("");
150 this.name = tmplName;
\r
151 this.source = tmplContent;
\r
152 this.sourceFunc = funcSrc;
\r
153 this.toString = function() { return "TrimPath.Template [" + tmplName + "]"; }
155 TrimPath.parseTemplate_etc.ParseError = function(name, line, message) {
\r
158 this.message = message;
160 TrimPath.parseTemplate_etc.ParseError.prototype.toString = function() {
161 return ("TrimPath template ParseError in " + this.name + ": line " + this.line + ", " + this.message);
164 var parse = function(body, tmplName, etc) {
165 body = cleanWhiteSpace(body);
166 var funcText = [ "var TrimPath_Template_TEMP = function(_OUT, _CONTEXT, _FLAGS) { with (_CONTEXT) {" ];
167 var state = { stack: [], line: 1 }; // TODO: Fix line number counting.
168 var endStmtPrev = -1;
169 while (endStmtPrev + 1 < body.length) {
170 var begStmt = endStmtPrev;
171 // Scan until we find some statement markup.
172 begStmt = body.indexOf("{", begStmt + 1);
173 while (begStmt >= 0) {
174 var endStmt = body.indexOf('}', begStmt + 1);
175 var stmt = body.substring(begStmt, endStmt);
176 var blockrx = stmt.match(/^\{(cdata|minify|eval)/); // From B. Bittman, minify/eval/cdata implementation.
178 var blockType = blockrx[1];
179 var blockMarkerBeg = begStmt + blockType.length + 1;
180 var blockMarkerEnd = body.indexOf('}', blockMarkerBeg);
181 if (blockMarkerEnd >= 0) {
183 if( blockMarkerEnd - blockMarkerBeg <= 0 ) {
184 blockMarker = "{/" + blockType + "}";
186 blockMarker = body.substring(blockMarkerBeg + 1, blockMarkerEnd);
189 var blockEnd = body.indexOf(blockMarker, blockMarkerEnd + 1);
191 emitSectionText(body.substring(endStmtPrev + 1, begStmt), funcText);
193 var blockText = body.substring(blockMarkerEnd + 1, blockEnd);
194 if (blockType == 'cdata') {
195 emitText(blockText, funcText);
196 } else if (blockType == 'minify') {
197 emitText(scrubWhiteSpace(blockText), funcText);
198 } else if (blockType == 'eval') {
199 if (blockText != null && blockText.length > 0) // From B. Bittman, eval should not execute until process().
200 funcText.push('_OUT.write( (function() { ' + blockText + ' })() );');
202 begStmt = endStmtPrev = blockEnd + blockMarker.length - 1;
205 } else if (body.charAt(begStmt - 1) != '$' && // Not an expression or backslashed,
206 body.charAt(begStmt - 1) != '\\') { // so check if it is a statement tag.
207 var offset = (body.charAt(begStmt + 1) == '/' ? 2 : 1); // Close tags offset of 2 skips '/'.
208 // 10 is larger than maximum statement tag length.
209 if (body.substring(begStmt + offset, begStmt + 10 + offset).search(TrimPath.parseTemplate_etc.statementTag) == 0)
210 break; // Found a match.
212 begStmt = body.indexOf("{", begStmt + 1);
214 if (begStmt < 0) // In "a{for}c", begStmt will be 1.
216 var endStmt = body.indexOf("}", begStmt + 1); // In "a{for}c", endStmt will be 5.
219 emitSectionText(body.substring(endStmtPrev + 1, begStmt), funcText);
220 emitStatement(body.substring(begStmt, endStmt + 1), state, funcText, tmplName, etc);
221 endStmtPrev = endStmt;
223 emitSectionText(body.substring(endStmtPrev + 1), funcText);
224 if (state.stack.length != 0)
225 throw new etc.ParseError(tmplName, state.line, "unclosed, unmatched statement(s): " + state.stack.join(","));
226 funcText.push("}}; TrimPath_Template_TEMP");
227 return funcText.join("");
230 var emitStatement = function(stmtStr, state, funcText, tmplName, etc) {
231 var parts = stmtStr.slice(1, -1).split(' ');
232 var stmt = etc.statementDef[parts[0]]; // Here, parts[0] == for/if/else/...
233 if (stmt == null) { // Not a real statement.
234 emitSectionText(stmtStr, funcText);
237 if (stmt.delta < 0) {
238 if (state.stack.length <= 0)
239 throw new etc.ParseError(tmplName, state.line, "close tag does not match any previous statement: " + stmtStr);
243 state.stack.push(stmtStr);
245 if (stmt.paramMin != null &&
246 stmt.paramMin >= parts.length)
247 throw new etc.ParseError(tmplName, state.line, "statement needs more parameters: " + stmtStr);
248 if (stmt.prefixFunc != null)
249 funcText.push(stmt.prefixFunc(parts, state, tmplName, etc));
251 funcText.push(stmt.prefix);
252 if (stmt.suffix != null) {
253 if (parts.length <= 1) {
254 if (stmt.paramDefault != null)
255 funcText.push(stmt.paramDefault);
257 for (var i = 1; i < parts.length; i++) {
260 funcText.push(parts[i]);
263 funcText.push(stmt.suffix);
267 var emitSectionText = function(text, funcText) {
268 if (text.length <= 0)
270 var nlPrefix = 0; // Index to first non-newline in prefix.
271 var nlSuffix = text.length - 1; // Index to first non-space/tab in suffix.
\r
272 while (nlPrefix < text.length && (text.charAt(nlPrefix) == '\n'))
274 while (nlSuffix >= 0 && (text.charAt(nlSuffix) == ' ' || text.charAt(nlSuffix) == '\t'))
276 if (nlSuffix < nlPrefix)
279 funcText.push('if (_FLAGS.keepWhitespace == true) _OUT.write("');
\r
280 var s = text.substring(0, nlPrefix).replace('\n', '\\n'); // A macro IE fix from BJessen.
281 if (s.charAt(s.length - 1) == '\n')
282 s = s.substring(0, s.length - 1);
284 funcText.push('");');
286 var lines = text.substring(nlPrefix, nlSuffix + 1).split('\n');
287 for (var i = 0; i < lines.length; i++) {
288 emitSectionTextLine(lines[i], funcText);
289 if (i < lines.length - 1)
290 funcText.push('_OUT.write("\\n");\n');
292 if (nlSuffix + 1 < text.length) {
293 funcText.push('if (_FLAGS.keepWhitespace == true) _OUT.write("');
294 var s = text.substring(nlSuffix + 1).replace('\n', '\\n');
295 if (s.charAt(s.length - 1) == '\n')
296 s = s.substring(0, s.length - 1);
298 funcText.push('");');
302 var emitSectionTextLine = function(line, funcText) {
303 var endMarkPrev = '}';
304 var endExprPrev = -1;
305 while (endExprPrev + endMarkPrev.length < line.length) {
306 var begMark = "${", endMark = "}";
307 var begExpr = line.indexOf(begMark, endExprPrev + endMarkPrev.length); // In "a${b}c", begExpr == 1
310 if (line.charAt(begExpr + 2) == '%') {
314 var endExpr = line.indexOf(endMark, begExpr + begMark.length); // In "a${b}c", endExpr == 4;
317 emitText(line.substring(endExprPrev + endMarkPrev.length, begExpr), funcText);
318 // Example: exprs == 'firstName|default:"John Doe"|capitalize'.split('|')
319 var exprArr = line.substring(begExpr + begMark.length, endExpr).replace(/\|\|/g, "#@@#").split('|');
320 for (var k in exprArr) {
321 if (exprArr[k].replace) // IE 5.x fix from Igor Poteryaev.
322 exprArr[k] = exprArr[k].replace(/#@@#/g, '||');
324 funcText.push('_OUT.write(');
325 emitExpression(exprArr, exprArr.length - 1, funcText);
327 endExprPrev = endExpr;
328 endMarkPrev = endMark;
330 emitText(line.substring(endExprPrev + endMarkPrev.length), funcText);
333 var emitText = function(text, funcText) {
337 text = text.replace(/\\/g, '\\\\');
338 text = text.replace(/\n/g, '\\n');
339 text = text.replace(/"/g, '\\"');
340 funcText.push('_OUT.write("');
342 funcText.push('");');
345 var emitExpression = function(exprArr, index, funcText) {
346 // Ex: foo|a:x|b:y1,y2|c:z1,z2 is emitted as c(b(a(foo,x),y1,y2),z1,z2)
347 var expr = exprArr[index]; // Ex: exprArr == [firstName,capitalize,default:"John Doe"]
348 if (index <= 0) { // Ex: expr == 'default:"John Doe"'
\r
352 var parts = expr.split(':');
353 funcText.push('_MODIFIERS["');
354 funcText.push(parts[0]); // The parts[0] is a modifier function name, like capitalize.
355 funcText.push('"](');
356 emitExpression(exprArr, index - 1, funcText);
357 if (parts.length > 1) {
359 funcText.push(parts[1]);
364 var cleanWhiteSpace = function(result) {
365 result = result.replace(/\t/g, " ");
366 result = result.replace(/\r\n/g, "\n");
367 result = result.replace(/\r/g, "\n");
368 result = result.replace(/^(\s*\S*(\s+\S+)*)\s*$/, '$1'); // Right trim by Igor Poteryaev.
372 var scrubWhiteSpace = function(result) {
373 result = result.replace(/^\s+/g, "");
374 result = result.replace(/\s+$/g, "");
375 result = result.replace(/\s+/g, " ");
376 result = result.replace(/^(\s*\S*(\s+\S+)*)\s*$/, '$1'); // Right trim by Igor Poteryaev.
380 // The DOM helper functions depend on DOM/DHTML, so they only work in a browser.
381 // However, these are not considered core to the engine.
383 TrimPath.parseDOMTemplate = function(elementId, optDocument, optEtc) {
384 if (optDocument == null)
385 optDocument = document;
386 var element = optDocument.getElementById(elementId);
387 var content = element.value; // Like textarea.value.
389 content = element.innerHTML; // Like textarea.innerHTML.
390 content = content.replace(/</g, "<").replace(/>/g, ">");
391 return TrimPath.parseTemplate(content, elementId, optEtc);
394 TrimPath.processDOMTemplate = function(elementId, context, optFlags, optDocument, optEtc) {
395 return TrimPath.parseDOMTemplate(elementId, optDocument, optEtc).process(context, optFlags);