2 * jSmart Javascript template engine
3 * https://github.com/umakantp/jsmart
5 * Copyright 2011-2015, Max Miroshnikov <miroshnikov at gmail dot com>
6 * Umakant Patil <me at umakantpatil dot.com>
7 * jSmart is licensed under the GNU Lesser General Public License
8 * http://opensource.org/licenses/LGPL-3.0
15 merges two or more objects into one
16 shallow copy for objects
18 function obMerge(ob1, ob2 /*, ...*/)
20 for (var i=1; i<arguments.length; ++i)
22 for (var nm in arguments[i])
24 ob1[nm] = arguments[i][nm];
31 @return number of own properties in ob
33 function countProperties(ob)
38 if (ob.hasOwnProperty(nm))
49 function findInArray(a, v)
51 if (Array.prototype.indexOf) {
54 for (var i=0; i < a.length; ++i)
64 function evalString(s)
66 return s.replace(/\\t/,'\t').replace(/\\n/,'\n').replace(/\\(['"\\])/g,'$1');
70 @return s trimmed and without quotes
72 function trimQuotes(s)
74 return evalString(s.replace(/^['"](.*)['"]$/,'$1')).replace(/^\s+|\s+$/g,'');
78 finds first {tag} in string
79 @param re string with regular expression or an empty string to find any tag
80 @return null or s.match(re) result object where
81 [0] - full tag matched with delimiters (and whitespaces at the begin and the end): { tag }
82 [1] - found part from passed re
83 [index] - position of tag starting { in s
85 function findTag(re,s)
89 var ldelim = jSmart.prototype.left_delimiter;
90 var rdelim = jSmart.prototype.right_delimiter;
91 var skipInWS = jSmart.prototype.auto_literal;
93 var reAny = /^\s*(.+)\s*$/i;
94 var reTag = re ? new RegExp('^\\s*('+re+')\\s*$','i') : reAny;
96 for (var i=0; i<s.length; ++i)
98 if (s.substr(i,ldelim.length) == ldelim)
100 if (skipInWS && i+1 < s.length && s.substr(i+1,1).match(/\s/))
107 offset += parseInt(i);
112 else if (s.substr(i,rdelim.length) == rdelim)
114 if (skipInWS && i-1 >= 0 && s.substr(i-1,1).match(/\s/))
120 var sTag = s.slice(ldelim.length,i).replace(/[\r\n]/g, ' ');
121 var found = sTag.match(reTag);
124 found.index = offset;
125 found[0] = s.slice(0,i+rdelim.length);
129 if (openCount < 0) //ignore any number of unmatched right delimiters
138 function findCloseTag(reClose,reOpen,s)
149 findIndex += closeTag[0].length;
151 closeTag = findTag(reClose,s);
154 throw new Error('Unclosed {'+reOpen+'}');
156 sInner += s.slice(0,closeTag.index);
157 findIndex += closeTag.index;
158 s = s.slice(closeTag.index+closeTag[0].length);
160 openTag = findTag(reOpen,sInner);
163 sInner = sInner.slice(openTag.index+openTag[0].length);
168 closeTag.index = findIndex;
172 function findElseTag(reOpen, reClose, reElse, s)
175 for (var elseTag=findTag(reElse,s); elseTag; elseTag=findTag(reElse,s))
177 var openTag = findTag(reOpen,s);
178 if (!openTag || openTag.index > elseTag.index)
180 elseTag.index += offset;
185 s = s.slice(openTag.index+openTag[0].length);
186 offset += openTag.index+openTag[0].length;
187 var closeTag = findCloseTag(reClose,reOpen,s);
188 s = s.slice(closeTag.index + closeTag[0].length);
189 offset += closeTag.index + closeTag[0].length;
195 function execute(code, data)
197 if (typeof(code) == 'string')
199 with ({'__code':code})
210 throw new Error(e.message + ' in \n' + code);
220 * Execute function when we have a object.
222 * @param object obj Object of the function to be called.
223 * @param array args Arguments to pass to a function.
226 * @throws Error If function obj does not exists.
228 function executeByFuncObject(obj, args) {
230 return obj.apply(this, args);
232 throw new Error(e.message);
236 function assignVar(nm, val, data)
238 if (nm.match(/\[\]$/)) //ar[] =
240 data[ nm.replace(/\[\]$/,'') ].push(val);
248 var buildInFunctions =
252 parse: function(s, tree)
254 var e = parseExpression(s);
260 params: parseParams(s.slice(e.value.length).replace(/^\s+|\s+$/g,''))
266 process: function(node, data)
268 var params = getActualParamValues(node.params, data);
269 var res = process([node.expression],data);
271 if (findInArray(params, 'nofilter') < 0)
273 for (var i=0; i<default_modifiers.length; ++i)
275 var m = default_modifiers[i];
276 m.params.__parsed[0] = {type:'text', data:res};
277 res = process([m],data);
281 res = modifiers.escape(res);
283 res = applyFilters(varFilters,res);
285 if (tpl_modifiers.length) {
286 __t = function(){ return res; }
287 res = process(tpl_modifiers,data);
296 process: function(node, data)
298 var params = getActualParamValues(node.params, data);
299 var arg1 = params[0];
301 if (node.optype == 'binary')
303 var arg2 = params[1];
306 getVarValue(node.params.__parsed[0], data, arg2);
309 else if (node.op.match(/(\+=|-=|\*=|\/=|%=)/))
311 arg1 = getVarValue(node.params.__parsed[0], data);
314 case '+=': arg1+=arg2; break;
315 case '-=': arg1-=arg2; break;
316 case '*=': arg1*=arg2; break;
317 case '/=': arg1/=arg2; break;
318 case '%=': arg1%=arg2; break;
320 return getVarValue(node.params.__parsed[0], data, arg1);
322 else if (node.op.match(/div/))
324 return (node.op!='div')^(arg1%arg2==0);
326 else if (node.op.match(/even/))
328 return (node.op!='even')^((arg1/arg2)%2==0);
330 else if (node.op.match(/xor/))
332 return (arg1||arg2) && !(arg1&&arg2);
337 case '==': return arg1==arg2;
338 case '!=': return arg1!=arg2;
339 case '+': return Number(arg1)+Number(arg2);
340 case '-': return Number(arg1)-Number(arg2);
341 case '*': return Number(arg1)*Number(arg2);
342 case '/': return Number(arg1)/Number(arg2);
343 case '%': return Number(arg1)%Number(arg2);
344 case '&&': return arg1&&arg2;
345 case '||': return arg1||arg2;
346 case '<': return arg1<arg2;
347 case '<=': return arg1<=arg2;
348 case '>': return arg1>arg2;
349 case '>=': return arg1>=arg2;
350 case '===': return arg1===arg2;
351 case '!==': return arg1!==arg2;
354 else if (node.op == '!')
360 var isVar = node.params.__parsed[0].type == 'var';
363 arg1 = getVarValue(node.params.__parsed[0], data);
366 if (node.optype == 'pre-unary')
370 case '-': v=-arg1; break;
371 case '++': v=++arg1; break;
372 case '--': v=--arg1; break;
376 getVarValue(node.params.__parsed[0], data, arg1);
383 case '++': arg1++; break;
384 case '--': arg1--; break;
386 getVarValue(node.params.__parsed[0], data, arg1);
396 parse: function(params, tree, content)
399 var subTreeElse = [];
405 subTreeElse: subTreeElse
408 var findElse = findElseTag('section [^}]+', '\/section', 'sectionelse', content);
411 parse(content.slice(0,findElse.index),subTree);
412 parse(content.slice(findElse.index+findElse[0].length).replace(/^[\r\n]/,''), subTreeElse);
416 parse(content, subTree);
420 process: function(node, data)
422 var params = getActualParamValues(node.params, data);
425 data.smarty.section[params.__get('name',null,0)] = props;
427 var show = params.__get('show',true);
431 return process(node.subTreeElse, data);
434 var from = parseInt(params.__get('start',0));
435 var to = (params.loop instanceof Object) ? countProperties(params.loop) : isNaN(params.loop) ? 0 : parseInt(params.loop);
436 var step = parseInt(params.__get('step',1));
437 var max = parseInt(params.__get('max'));
440 max = Number.MAX_VALUE;
453 from = to ? to-1 : 0;
459 for (; i>=0 && i<to && count<max; i+=step,++count)
464 props.loop = count; //? - because it is so in Smarty
468 for (i=from; i>=0 && i<to && count<max; i+=step,++count)
470 if (data.smarty['break'])
475 props.first = (i==from);
476 props.last = ((i+step)<0 || (i+step)>=to);
478 props.index_prev = i-step;
479 props.index_next = i+step;
480 props.iteration = props.rownum = count+1;
482 s += process(node.subTree, data);
483 data.smarty['continue'] = false;
485 data.smarty['break'] = false;
491 return process(node.subTreeElse, data);
498 parseParams: function(paramStr)
500 return [parseExpression('__t()|' + paramStr).tree];
503 parse: function(params, tree, content)
509 subTree: parse(content,[])
513 process: function(node, data)
515 tpl_modifiers = node.params;
516 var s = process(node.subTree, data);
525 parseParams: function(paramStr)
527 var res = paramStr.match(/^\s*\$(\w+)\s*=\s*([^\s]+)\s*to\s*([^\s]+)\s*(?:step\s*([^\s]+))?\s*(.*)$/);
530 throw new Error('Invalid {for} parameters: '+paramStr);
532 return parseParams("varName='"+res[1]+"' from="+res[2]+" to="+res[3]+" step="+(res[4]?res[4]:'1')+" "+res[5]);
535 parse: function(params, tree, content)
538 var subTreeElse = [];
544 subTreeElse: subTreeElse
547 var findElse = findElseTag('for\\s[^}]+', '\/for', 'forelse', content);
550 parse(content.slice(0,findElse.index),subTree);
551 parse(content.slice(findElse.index+findElse[0].length), subTreeElse);
555 parse(content, subTree);
559 process: function(node, data)
561 var params = getActualParamValues(node.params, data);
562 var from = parseInt(params.__get('from'));
563 var to = parseInt(params.__get('to'));
564 var step = parseInt(params.__get('step'));
569 var max = parseInt(params.__get('max'));
572 max = Number.MAX_VALUE;
577 var total = Math.min( Math.ceil( ((step > 0 ? to-from : from-to)+1) / Math.abs(step) ), max);
579 for (var i=parseInt(params.from); count<total; i+=step,++count)
581 if (data.smarty['break'])
585 data[params.varName] = i;
586 s += process(node.subTree, data);
587 data.smarty['continue'] = false;
589 data.smarty['break'] = false;
593 s = process(node.subTreeElse, data);
602 parse: function(params, tree, content)
605 var subTreeElse = [];
610 subTreeIf: subTreeIf,
611 subTreeElse: subTreeElse
614 var findElse = findElseTag('if\\s+[^}]+', '\/if', 'else[^}]*', content);
617 parse(content.slice(0,findElse.index),subTreeIf);
619 content = content.slice(findElse.index+findElse[0].length);
620 var findElseIf = findElse[1].match(/^else\s*if(.*)/);
623 buildInFunctions['if'].parse(parseParams(findElseIf[1]), subTreeElse, content.replace(/^\n/,''));
627 parse(content.replace(/^\n/,''), subTreeElse);
632 parse(content, subTreeIf);
636 process: function(node, data) {
637 var value = getActualParamValues(node.params,data)[0];
638 // Zero length arrays or empty associative arrays are false in PHP.
639 if (value && !((value instanceof Array && value.length == 0)
640 || (typeof value == 'object' && isEmptyObject(value)))
642 return process(node.subTreeIf, data);
644 return process(node.subTreeElse, data);
652 parseParams: function(paramStr)
655 var res = paramStr.match(/^\s*([$].+)\s*as\s*[$](\w+)\s*(=>\s*[$](\w+))?\s*$/i);
656 if (res) //Smarty 3.x syntax => Smarty 2.x syntax
658 paramStr = 'from='+res[1] + ' item='+(res[4]||res[2]);
661 paramStr += ' key='+res[2];
664 return parseParams(paramStr);
667 parse: function(params, tree, content)
670 var subTreeElse = [];
676 subTreeElse: subTreeElse
679 var findElse = findElseTag('foreach\\s[^}]+', '\/foreach', 'foreachelse', content);
682 parse(content.slice(0,findElse.index),subTree);
683 parse(content.slice(findElse.index+findElse[0].length).replace(/^[\r\n]/,''), subTreeElse);
687 parse(content, subTree);
691 process: function(node, data)
693 var params = getActualParamValues(node.params, data);
695 if (typeof a == 'undefined')
699 if (typeof a != 'object')
704 var total = countProperties(a);
706 data[params.item+'__total'] = total;
707 if ('name' in params)
709 data.smarty.foreach[params.name] = {};
710 data.smarty.foreach[params.name].total = total;
717 if (!a.hasOwnProperty(key))
722 if (data.smarty['break'])
727 data[params.item+'__key'] = isNaN(key) ? key : parseInt(key);
730 data[params.key] = data[params.item+'__key'];
732 data[params.item] = a[key];
733 data[params.item+'__index'] = parseInt(i);
734 data[params.item+'__iteration'] = parseInt(i+1);
735 data[params.item+'__first'] = (i===0);
736 data[params.item+'__last'] = (i==total-1);
738 if ('name' in params)
740 data.smarty.foreach[params.name].index = parseInt(i);
741 data.smarty.foreach[params.name].iteration = parseInt(i+1);
742 data.smarty.foreach[params.name].first = (i===0) ? 1 : '';
743 data.smarty.foreach[params.name].last = (i==total-1) ? 1 : '';
748 s += process(node.subTree, data);
749 data.smarty['continue'] = false;
751 data.smarty['break'] = false;
753 data[params.item+'__show'] = (i>0);
756 data.smarty.foreach[params.name].show = (i>0) ? 1 : '';
762 return process(node.subTreeElse, data);
769 parse: function(params, tree, content)
772 plugins[trimQuotes(params.name?params.name:params[0])] =
776 defautParams: params,
777 process: function(params, data)
779 var defaults = getActualParamValues(this.defautParams,data);
780 delete defaults.name;
781 return process(this.subTree, obMerge({},data,defaults,params));
784 parse(content, subTree);
791 parse: function(params, tree, content) {}
797 parse: function(params, tree)
799 tree.splice(0,tree.length);
800 getTemplate(trimQuotes(params.file?params.file:params[0]),tree);
807 parse: function(params, tree, content)
814 params.append = findInArray(params,'append') >= 0;
815 params.prepend = findInArray(params,'prepend') >= 0;
816 params.hide = findInArray(params,'hide') >= 0;
817 params.hasChild = params.hasParent = false;
819 onParseVar = function(nm)
821 if (nm.match(/^\s*[$]smarty.block.child\s*$/))
823 params.hasChild = true;
825 if (nm.match(/^\s*[$]smarty.block.parent\s*$/))
827 params.hasParent = true;
830 var tree = parse(content, []);
831 onParseVar = function(nm) {}
833 var blockName = trimQuotes(params.name?params.name:params[0]);
834 if (!(blockName in blocks))
836 blocks[blockName] = [];
838 blocks[blockName].push({tree:tree, params:params});
841 process: function(node, data)
843 data.smarty.block.parent = data.smarty.block.child = '';
844 var blockName = trimQuotes(node.params.name?node.params.name:node.params[0]);
845 this.processBlocks(blocks[blockName], blocks[blockName].length-1, data);
846 return data.smarty.block.child;
849 processBlocks: function(blockAncestry, i, data)
851 if (!i && blockAncestry[i].params.hide) {
852 data.smarty.block.child = '';
859 if (blockAncestry[i].params.hasParent)
861 var tmpChild = data.smarty.block.child;
862 data.smarty.block.child = '';
863 this.processBlocks(blockAncestry, i-1, data);
864 data.smarty.block.parent = data.smarty.block.child;
865 data.smarty.block.child = tmpChild;
868 var tmpChild = data.smarty.block.child;
869 var s = process(blockAncestry[i].tree, data);
870 data.smarty.block.child = tmpChild;
872 if (blockAncestry[i].params.hasChild)
874 data.smarty.block.child = s;
878 data.smarty.block.child = s + data.smarty.block.child;
882 data.smarty.block.child += s;
884 append = blockAncestry[i].params.append;
885 prepend = blockAncestry[i].params.prepend;
893 parse: function(params, tree, content)
895 parse(content.replace(/[ \t]*[\r\n]+[ \t]*/g, ''), tree);
902 parse: function(params, tree, content)
904 parseText(content, tree);
911 parse: function(params, tree)
913 parseText(jSmart.prototype.left_delimiter, tree);
920 parse: function(params, tree)
922 parseText(jSmart.prototype.right_delimiter, tree);
929 parse: function(params, tree, content)
935 subTree: parse(content, [])
939 process: function(node, data)
942 while (getActualParamValues(node.params,data)[0])
944 if (data.smarty['break'])
948 s += process(node.subTree, data);
949 data.smarty['continue'] = false;
951 data.smarty['break'] = false;
962 var tpl_modifiers = [];
964 function parse(s, tree)
966 for (var openTag=findTag('',s); openTag; openTag=findTag('',s))
970 parseText(s.slice(0,openTag.index),tree);
972 s = s.slice(openTag.index + openTag[0].length);
974 var res = openTag[1].match(/^\s*(\w+)(.*)$/);
978 var paramStr = (res.length>2) ? res[2].replace(/^\s+|\s+$/g,'') : '';
980 if (nm in buildInFunctions)
982 var buildIn = buildInFunctions[nm];
983 var params = ('parseParams' in buildIn ? buildIn.parseParams : parseParams)(paramStr);
984 if (buildIn.type == 'block')
986 s = s.replace(/^\n/,''); //remove new line after block open tag (like in Smarty)
987 var closeTag = findCloseTag('\/'+nm, nm+' +[^}]*', s);
988 buildIn.parse(params, tree, s.slice(0,closeTag.index));
989 s = s.slice(closeTag.index+closeTag[0].length);
993 buildIn.parse(params, tree);
996 tree = []; //throw away further parsing except for {block}
999 s = s.replace(/^\n/,'');
1001 else if (nm in plugins)
1003 var plugin = plugins[nm];
1004 if (plugin.type == 'block')
1006 var closeTag = findCloseTag('\/'+nm, nm+' +[^}]*', s);
1007 parsePluginBlock(nm, parseParams(paramStr), tree, s.slice(0,closeTag.index));
1008 s = s.slice(closeTag.index+closeTag[0].length);
1010 else if (plugin.type == 'function')
1012 parsePluginFunc(nm, parseParams(paramStr), tree);
1014 if (nm=='append' || nm=='assign' || nm=='capture' || nm=='eval' || nm=='include')
1016 s = s.replace(/^\n/,'');
1021 buildInFunctions.expression.parse(openTag[1],tree);
1026 var node = buildInFunctions.expression.parse(openTag[1],tree);
1027 if (node.type=='build-in' && node.name=='operator' && node.op == '=')
1029 s = s.replace(/^\n/,'');
1040 function parseText(text, tree)
1042 if (parseText.parseEmbeddedVars)
1044 var re = /([$][\w@]+)|`([^`]*)`/;
1045 for (var found=re.exec(text); found; found=re.exec(text))
1047 tree.push({type: 'text', data: text.slice(0,found.index)});
1048 tree.push( parseExpression(found[1] ? found[1] : found[2]).tree );
1049 text = text.slice(found.index + found[0].length);
1052 tree.push({type: 'text', data: text});
1056 function parseFunc(name, params, tree)
1058 params.__parsed.name = parseText(name,[])[0];
1067 function parseOperator(op, type, precedence, tree)
1074 precedence: precedence,
1079 function parseVar(s, e, nm)
1081 var rootName = e.token;
1082 var parts = [{type:'text', data:nm.replace(/^(\w+)@(key|index|iteration|first|last|show|total)/gi, "$1__$2")}];
1084 var re = /^(?:\.|\s*->\s*|\[\s*)/;
1085 for (var op=s.match(re); op; op=s.match(re))
1088 s = s.slice(op[0].length);
1090 var eProp = {value:'', tree:[]};
1091 if (op[0].match(/\[/))
1093 eProp = parseExpression(s);
1096 e.token += eProp.value;
1097 parts.push( eProp.tree );
1098 s = s.slice(eProp.value.length);
1101 var closeOp = s.match(/\s*\]/);
1104 e.token += closeOp[0];
1105 s = s.slice(closeOp[0].length);
1110 var parseMod = parseModifiers.stop;
1111 parseModifiers.stop = true;
1112 if (lookUp(s,eProp))
1114 e.token += eProp.value;
1115 var part = eProp.tree[0];
1116 if (part.type == 'plugin' && part.name == '__func')
1118 part.hasOwner = true;
1121 s = s.slice(eProp.value.length);
1127 parseModifiers.stop = parseMod;
1132 parts.push({type:'text', data:''});
1136 e.tree.push({type: 'var', parts: parts});
1138 e.value += e.token.substr(rootName.length);
1140 onParseVar(e.token);
1145 function onParseVar(nm) {}
1151 re: /^\$([\w@]+)/, //var
1152 parse: function(e, s)
1154 parseModifiers(parseVar(s, e, RegExp.$1), e);
1158 re: /^(true|false)/i, //bool
1159 parse: function(e, s)
1161 parseText(e.token.match(/true/i) ? '1' : '', e.tree);
1165 re: /^'([^'\\]*(?:\\.[^'\\]*)*)'/, //single quotes
1166 parse: function(e, s)
1168 parseText(evalString(RegExp.$1), e.tree);
1169 parseModifiers(s, e);
1173 re: /^"([^"\\]*(?:\\.[^"\\]*)*)"/, //double quotes
1174 parse: function(e, s)
1176 var v = evalString(RegExp.$1);
1177 var isVar = v.match(tokens[0].re);
1180 var eVar = {token:isVar[0], tree:[]};
1181 parseVar(v, eVar, isVar[1]);
1182 if (eVar.token.length == v.length)
1184 e.tree.push( eVar.tree[0] );
1188 parseText.parseEmbeddedVars = true;
1192 params: {__parsed: parse(v,[])}
1194 parseText.parseEmbeddedVars = false;
1195 parseModifiers(s, e);
1199 re: /^(\w+)\s*[(]([)]?)/, //func()
1200 parse: function(e, s)
1202 var fnm = RegExp.$1;
1203 var noArgs = RegExp.$2;
1204 var params = parseParams(noArgs?'':s,/^\s*,\s*/);
1205 parseFunc(fnm, params, e.tree);
1206 e.value += params.toString();
1207 parseModifiers(s.slice(params.toString().length), e);
1211 re: /^\s*\(\s*/, //expression in parentheses
1212 parse: function(e, s)
1215 e.tree.push(parens);
1216 parens.parent = e.tree;
1222 parse: function(e, s)
1224 if (e.tree.parent) //it may be the end of func() or (expr)
1226 e.tree = e.tree.parent;
1231 re: /^\s*(\+\+|--)\s*/,
1232 parse: function(e, s)
1234 if (e.tree.length && e.tree[e.tree.length-1].type == 'var')
1236 parseOperator(RegExp.$1, 'post-unary', 1, e.tree);
1240 parseOperator(RegExp.$1, 'pre-unary', 1, e.tree);
1245 re: /^\s*(===|!==|==|!=)\s*/,
1246 parse: function(e, s)
1248 parseOperator(RegExp.$1, 'binary', 6, e.tree);
1252 re: /^\s+(eq|ne|neq)\s+/i,
1253 parse: function(e, s)
1255 var op = RegExp.$1.replace(/ne(q)?/,'!=').replace(/eq/,'==');
1256 parseOperator(op, 'binary', 6, e.tree);
1261 parse: function(e, s)
1263 parseOperator('!', 'pre-unary', 2, e.tree);
1268 parse: function(e, s)
1270 parseOperator('!', 'pre-unary', 2, e.tree);
1274 re: /^\s*(=|\+=|-=|\*=|\/=|%=)\s*/,
1275 parse: function(e, s)
1277 parseOperator(RegExp.$1, 'binary', 10, e.tree);
1281 re: /^\s*(\*|\/|%)\s*/,
1282 parse: function(e, s)
1284 parseOperator(RegExp.$1, 'binary', 3, e.tree);
1289 parse: function(e, s)
1291 parseOperator('%', 'binary', 3, e.tree);
1295 re: /^\s*(\+|-)\s*/,
1296 parse: function(e, s)
1298 if (!e.tree.length || e.tree[e.tree.length-1].name == 'operator')
1300 parseOperator(RegExp.$1, 'pre-unary', 4, e.tree);
1304 parseOperator(RegExp.$1, 'binary', 4, e.tree);
1309 re: /^\s*(<=|>=|<>|<|>)\s*/,
1310 parse: function(e, s)
1312 parseOperator(RegExp.$1.replace(/<>/,'!='), 'binary', 5, e.tree);
1316 re: /^\s+(lt|lte|le|gt|gte|ge)\s+/i,
1317 parse: function(e, s)
1319 var op = RegExp.$1.replace(/lt/,'<').replace(/l(t)?e/,'<=').replace(/gt/,'>').replace(/g(t)?e/,'>=');
1320 parseOperator(op, 'binary', 5, e.tree);
1324 re: /^\s+(is\s+(not\s+)?div\s+by)\s+/i,
1325 parse: function(e, s)
1327 parseOperator(RegExp.$2?'div_not':'div', 'binary', 7, e.tree);
1331 re: /^\s+is\s+(not\s+)?(even|odd)(\s+by\s+)?\s*/i,
1332 parse: function(e, s)
1334 var op = RegExp.$1 ? ((RegExp.$2=='odd')?'even':'even_not') : ((RegExp.$2=='odd')?'even_not':'even');
1335 parseOperator(op, 'binary', 7, e.tree);
1338 parseText('1', e.tree);
1344 parse: function(e, s)
1346 parseOperator(RegExp.$1, 'binary', 8, e.tree);
1350 re: /^\s*(\|\|)\s*/,
1351 parse: function(e, s)
1353 parseOperator(RegExp.$1, 'binary', 9, e.tree);
1358 parse: function(e, s)
1360 parseOperator('&&', 'binary', 11, e.tree);
1365 parse: function(e, s)
1367 parseOperator('xor', 'binary', 12, e.tree);
1372 parse: function(e, s)
1374 parseOperator('||', 'binary', 13, e.tree);
1378 re: /^#(\w+)#/, //config variable
1379 parse: function(e, s)
1381 var eVar = {token:'$smarty',tree:[]};
1382 parseVar('.config.'+RegExp.$1, eVar, 'smarty');
1383 e.tree.push( eVar.tree[0] );
1384 parseModifiers(s, e);
1388 re: /^\s*\[\s*/, //array
1389 parse: function(e, s)
1391 var params = parseParams(s, /^\s*,\s*/, /^('[^'\\]*(?:\\.[^'\\]*)*'|"[^"\\]*(?:\\.[^"\\]*)*"|\w+)\s*=>\s*/);
1392 parsePluginFunc('__array',params,e.tree);
1393 e.value += params.toString();
1394 var paren = s.slice(params.toString().length).match(/\s*\]/);
1397 e.value += paren[0];
1402 re: /^[\d.]+/, //number
1403 parse: function(e, s)
1405 if (e.token.indexOf('.') > -1) {
1406 e.token = parseFloat(e.token);
1408 e.token = parseInt(e.token, 10);
1410 parseText(e.token, e.tree);
1411 parseModifiers(s, e);
1415 re: /^\w+/, //static
1416 parse: function(e, s)
1418 parseText(e.token, e.tree);
1419 parseModifiers(s, e);
1424 function parseModifiers(s, e)
1426 if (parseModifiers.stop)
1431 var modifier = s.match(/^\|(\w+)/);
1437 e.value += modifier[0];
1439 var fnm = modifier[1]=='default' ? 'defaultValue' : modifier[1];
1440 s = s.slice(modifier[0].length).replace(/^\s+/,'');
1442 parseModifiers.stop = true;
1444 for (var colon=s.match(/^\s*:\s*/); colon; colon=s.match(/^\s*:\s*/))
1446 e.value += s.slice(0,colon[0].length);
1447 s = s.slice(colon[0].length);
1449 var param = {value:'', tree:[]};
1450 if (lookUp(s, param))
1452 e.value += param.value;
1453 params.push(param.tree[0]);
1454 s = s.slice(param.value.length);
1458 parseText('',params);
1461 parseModifiers.stop = false;
1463 params.unshift(e.tree.pop()); //modifiers have the highest priority
1464 e.tree.push(parseFunc(fnm,{__parsed:params},[])[0]);
1466 parseModifiers(s, e); //modifiers can be combined
1469 function lookUp(s,e)
1476 if (s.substr(0,jSmart.prototype.left_delimiter.length)==jSmart.prototype.left_delimiter)
1478 var tag = findTag('',s);
1483 parse(tag[0], e.tree);
1484 parseModifiers(s.slice(e.value.length), e);
1489 for (var i=0; i<tokens.length; ++i)
1491 if (s.match(tokens[i].re))
1493 e.token = RegExp.lastMatch;
1494 e.value += RegExp.lastMatch;
1495 tokens[i].parse(e, s.slice(e.token.length));
1502 function bundleOp(i, tree, precedence)
1505 if (op.name == 'operator' && op.precedence == precedence && !op.params.__parsed)
1507 if (op.optype == 'binary')
1509 op.params.__parsed = [tree[i-1],tree[i+1]];
1510 tree.splice(i-1,3,op);
1513 else if (op.optype == 'post-unary')
1515 op.params.__parsed = [tree[i-1]];
1516 tree.splice(i-1,2,op);
1520 op.params.__parsed = [tree[i+1]];
1521 tree.splice(i,2,op);
1526 function composeExpression(tree)
1529 for (i=0; i<tree.length; ++i)
1531 if (tree[i] instanceof Array)
1533 tree[i] = composeExpression(tree[i])
1537 for (var precedence=1; precedence<14; ++precedence)
1539 if (precedence==2 || precedence==10)
1541 for (i=tree.length; i>0; --i)
1543 i -= bundleOp(i-1, tree, precedence);
1548 for (i=0; i<tree.length; ++i)
1550 i -= bundleOp(i, tree, precedence);
1554 return tree[0]; //only one node must be left
1557 function parseExpression(s)
1559 var e = { value:'', tree:[] };
1560 while (lookUp(s.slice(e.value.length), e)){}
1565 e.tree = composeExpression(e.tree);
1569 function parseParams(paramsStr, reDelim, reName)
1571 var s = paramsStr.replace(/\n/g,' ').replace(/^\s+|\s+$/g,'');
1573 params.__parsed = [];
1584 reName = /^(\w+)\s*=\s*/;
1592 var foundName = s.match(reName);
1595 nm = trimQuotes(foundName[1]);
1596 paramsStr += s.slice(0,foundName[0].length);
1597 s = s.slice(foundName[0].length);
1601 var param = parseExpression(s);
1609 params[nm] = param.value;
1610 params.__parsed[nm] = param.tree;
1614 params.push(param.value);
1615 params.__parsed.push(param.tree);
1618 paramsStr += s.slice(0,param.value.length);
1619 s = s.slice(param.value.length);
1621 var foundDelim = s.match(reDelim);
1624 paramsStr += s.slice(0,foundDelim[0].length);
1625 s = s.slice(foundDelim[0].length);
1632 params.toString = function() { return paramsStr; }
1636 function parsePluginBlock(name, params, tree, content)
1642 subTree: parse(content,[])
1646 function parsePluginFunc(name, params, tree)
1655 function getActualParamValues(params,data)
1657 var actualParams = [];
1658 for (var nm in params.__parsed)
1660 if (params.__parsed.hasOwnProperty(nm))
1662 var v = process([params.__parsed[nm]], data);
1663 actualParams[nm] = v;
1667 actualParams.__get = function(nm,defVal,id)
1669 if (nm in actualParams && typeof(actualParams[nm]) != 'undefined')
1671 return actualParams[nm];
1673 if (typeof(id)!='undefined' && typeof(actualParams[id]) != 'undefined')
1675 return actualParams[id];
1677 if (defVal === null)
1679 throw new Error("The required attribute '"+nm+"' is missing");
1683 return actualParams;
1687 * Returns boolean true if object is empty otherwise false.
1689 * @param object hash Object you are testing against.
1693 function isEmptyObject(hash) {
1694 for (var i in hash) {
1695 if (hash.hasOwnProperty(i)) {
1702 function getVarValue(node, data, val)
1706 for (var i=0; i<node.parts.length; ++i)
1708 var part = node.parts[i];
1709 if (part.type == 'plugin' && part.name == '__func' && part.hasOwner)
1712 v = process([node.parts[i]],data);
1713 delete data.__owner;
1717 nm = process([part],data);
1720 if (nm in data.smarty.section && part.type=='text' && process([node.parts[0]],data)!='smarty')
1722 nm = data.smarty.section[nm].index;
1726 if (!nm && typeof val != 'undefined' && v instanceof Array)
1732 if (typeof val != 'undefined' && i==node.parts.length-1)
1737 if (typeof v == 'object' && v !== null && nm in v)
1743 if (typeof val == 'undefined')
1755 function process(tree, data)
1758 for (var i=0; i<tree.length; ++i)
1762 if (node.type == 'text')
1766 else if (node.type == 'var')
1768 s = getVarValue(node,data);
1770 else if (node.type == 'build-in')
1772 s = buildInFunctions[node.name].process(node,data);
1774 else if (node.type == 'plugin')
1776 var plugin = plugins[node.name];
1777 if (plugin.type == 'block')
1779 var repeat = {value:true};
1780 plugin.process(getActualParamValues(node.params,data), '', data, repeat);
1781 while (repeat.value)
1783 repeat.value = false;
1784 s += plugin.process(
1785 getActualParamValues(node.params,data),
1786 process(node.subTree, data),
1792 else if (plugin.type == 'function')
1794 s = plugin.process(getActualParamValues(node.params,data), data);
1797 if (typeof s == 'boolean')
1804 if (tree.length == 1)
1808 res += s!==null ? s : '';
1810 if (data.smarty['continue'] || data.smarty['break'])
1818 function getTemplate(name, tree, nocache)
1820 if (nocache || !(name in files))
1822 var tpl = jSmart.prototype.getTemplate(name);
1823 if (typeof(tpl) != 'string')
1825 throw new Error('No template for '+ name);
1827 parse(applyFilters(jSmart.prototype.filters_global.pre, stripComments(tpl.replace(/\r\n/g,'\n'))), tree);
1837 function stripComments(s)
1840 for (var openTag=s.match(/{\*/); openTag; openTag=s.match(/{\*/))
1842 sRes += s.slice(0,openTag.index);
1843 s = s.slice(openTag.index+openTag[0].length);
1844 var closeTag = s.match(/\*}/);
1847 throw new Error('Unclosed {*');
1849 s = s.slice(closeTag.index+closeTag[0].length);
1854 function applyFilters(filters, s)
1856 for (var i=0; i<filters.length; ++i)
1864 jSmart = function(tpl)
1867 this.tree.blocks = {};
1869 this.default_modifiers = [];
1870 this.filters = {'variable':[], 'post':[]};
1881 now: Math.floor( (new Date()).getTime()/1000 ),
1886 ldelim: jSmart.prototype.left_delimiter,
1887 rdelim: jSmart.prototype.right_delimiter,
1891 blocks = this.tree.blocks;
1893 applyFilters(jSmart.prototype.filters_global.pre, stripComments((new String(tpl?tpl:'')).replace(/\r\n/g,'\n'))),
1898 jSmart.prototype.fetch = function(data)
1900 blocks = this.tree.blocks;
1901 scripts = this.scripts;
1902 escape_html = this.escape_html;
1903 default_modifiers = jSmart.prototype.default_modifiers_global.concat(this.default_modifiers);
1904 this.data = obMerge((typeof data == 'object') ? data : {}, this.smarty);
1905 varFilters = jSmart.prototype.filters_global.variable.concat(this.filters.variable);
1906 var res = process(this.tree, this.data);
1907 if (jSmart.prototype.debugging)
1909 plugins.debug.process([],this.data);
1911 return applyFilters(jSmart.prototype.filters_global.post.concat(this.filters.post), res);
1914 jSmart.prototype.escape_html = false;
1917 @param type valid values are 'function', 'block', 'modifier'
1918 @param callback func(params,data) or block(params,content,data,repeat)
1920 jSmart.prototype.registerPlugin = function(type, name, callback)
1922 if (type == 'modifier')
1924 modifiers[name] = callback;
1928 plugins[name] = {'type': type, 'process': callback};
1933 @param type valid values are 'pre', 'variable', 'post'
1934 @param callback function(textValue) { ... }
1936 jSmart.prototype.registerFilter = function(type, callback)
1938 (this.tree ? this.filters : jSmart.prototype.filters_global)[type=='output'?'post':type].push(callback);
1941 jSmart.prototype.filters_global = {'pre':[],'variable':[],'post':[]};
1943 jSmart.prototype.configLoad = function(confValues, section, data)
1945 data = data ? data : this.data;
1946 var s = confValues.replace(/\r\n/g,'\n').replace(/^\s+|\s+$/g,'');
1947 var re = /^\s*(?:\[([^\]]+)\]|(?:(\w+)[ \t]*=[ \t]*("""|'[^'\\\n]*(?:\\.[^'\\\n]*)*'|"[^"\\\n]*(?:\\.[^"\\\n]*)*"|[^\n]*)))/m;
1949 for (var f=s.match(re); f; f=s.match(re))
1951 s = s.slice(f.index+f[0].length);
1956 else if ((!currSect || currSect == section) && currSect.substr(0,1) != '.')
1960 var triple = s.match(/"""/);
1963 data.smarty.config[f[2]] = s.slice(0,triple.index);
1964 s = s.slice(triple.index + triple[0].length);
1969 data.smarty.config[f[2]] = trimQuotes(f[3]);
1972 var newln = s.match(/\n+/);
1975 s = s.slice(newln.index + newln[0].length);
1984 jSmart.prototype.clearConfig = function(varName)
1988 delete this.data.smarty.config[varName];
1992 this.data.smarty.config = {};
1997 add modifier to implicitly apply to every variable in a template
1998 @param modifiers single string (e.g. "replace:'from':'to'")
1999 or array of strings (e.g. ['escape:"htmlall"', "replace:'from':'to'"])
2001 jSmart.prototype.addDefaultModifier = function(modifiers)
2003 if (!(modifiers instanceof Array))
2005 modifiers = [modifiers];
2008 for (var i=0; i<modifiers.length; ++i)
2010 var e = { value:'', tree:[0] };
2011 parseModifiers('|'+modifiers[i], e);
2012 (this.tree ? this.default_modifiers : this.default_modifiers_global).push( e.tree[0] );
2016 jSmart.prototype.default_modifiers_global = [];
2019 override this function
2020 @param name value of 'file' parameter in {include} and {extends}
2021 @return template text
2023 jSmart.prototype.getTemplate = function(name)
2025 throw new Error('No template for ' + name);
2029 override this function
2030 @param name value of 'file' parameter in {fetch}
2031 @return file content
2033 jSmart.prototype.getFile = function(name)
2035 throw new Error('No file for ' + name);
2039 override this function
2040 @param name value of 'file' parameter in {include_php} and {include_javascript}
2041 or value of 'script' parameter in {insert}
2042 @return Javascript script
2044 jSmart.prototype.getJavascript = function(name)
2046 throw new Error('No Javascript for ' + name);
2050 override this function
2051 @param name value of 'file' parameter in {config_load}
2052 @return config file content
2054 jSmart.prototype.getConfig = function(name)
2056 throw new Error('No config for ' + name);
2062 whether to skip tags in open brace { followed by white space(s) and close brace } with white space(s) before
2064 jSmart.prototype.auto_literal = true;
2066 jSmart.prototype.left_delimiter = '{';
2067 jSmart.prototype.right_delimiter = '}';
2069 /** enables the debugging console */
2070 jSmart.prototype.debugging = false;
2073 jSmart.prototype.PHPJS = function(fnm, modifier)
2075 if (eval('typeof '+fnm) == 'function')
2077 return (typeof window == 'object') ? window : global;
2079 else if (typeof(PHP_JS) == 'function')
2081 return new PHP_JS();
2083 throw new Error("Modifier '" + modifier + "' uses JavaScript port of PHP function '" + fnm + "'. You can find one at http://phpjs.org");
2086 jSmart.prototype.makeTimeStamp = function(s)
2090 return Math.floor( (new Date()).getTime()/1000 );
2094 var tm = jSmart.prototype.PHPJS('strtotime','date_format').strtotime(s);
2095 if (tm == -1 || tm === false) {
2096 return Math.floor( (new Date()).getTime()/1000 );
2101 if (s.length == 14) //mysql timestamp format of YYYYMMDDHHMMSS
2103 return Math.floor( (new Date(s.substr(0,4),s.substr(4,2)-1,s.substr(6,2),s.substr(8,2),s.substr(10,2)).getTime()/1000 ) );
2111 register custom functions
2113 jSmart.prototype.registerPlugin(
2116 function(params, data)
2119 for (var nm in params)
2121 if (params.hasOwnProperty(nm) && params[nm] && typeof params[nm] != 'function')
2130 jSmart.prototype.registerPlugin(
2133 function(params, data) {
2134 var paramNames = [], paramValues = {}, paramData = [];
2135 for (var i=0; i<params.length; ++i) {
2136 paramNames.push(params.name+'__p'+i);
2137 paramData.push(params[i]);
2138 paramValues[params.name+'__p'+i] = params[i];
2140 var fname, mergedParams = obMerge({}, data, paramValues);
2141 if (('__owner' in data && params.name in data.__owner)) {
2142 fname = '__owner.'+params.name;
2143 return execute(fname + '(' + paramNames.join(',') + ')', mergedParams);
2144 } else if (modifiers.hasOwnProperty(params.name)) {
2145 fname = modifiers[params.name]
2146 return executeByFuncObject(fname, paramData, mergedParams);
2148 fname = params.name;
2149 return execute(fname + '(' + paramNames.join(',') + ')', mergedParams);
2154 jSmart.prototype.registerPlugin(
2157 function(params, data)
2159 return params.join('');
2163 jSmart.prototype.registerPlugin(
2166 function(params, data)
2168 var varName = params.__get('var',null,0);
2169 if (!(varName in data) || !(data[varName] instanceof Array))
2173 var index = params.__get('index',false);
2174 var val = params.__get('value',null,1);
2175 if (index === false)
2177 data[varName].push(val);
2181 data[varName][index] = val;
2187 jSmart.prototype.registerPlugin(
2190 function(params, data)
2192 assignVar(params.__get('var',null,0), params.__get('value',null,1), data);
2197 jSmart.prototype.registerPlugin(
2200 function(params, data)
2202 data.smarty['break'] = true;
2207 jSmart.prototype.registerPlugin(
2210 function(params, data)
2212 var fname = params.__get('name',null,0);
2214 var assignTo = params.__get('assign',false);
2215 delete params.assign;
2216 var s = plugins[fname].process(params, data);
2219 assignVar(assignTo, s, data);
2226 jSmart.prototype.registerPlugin(
2229 function(params, content, data, repeat)
2233 content = content.replace(/^\n/,'');
2234 data.smarty.capture[params.__get('name','default',0)] = content;
2236 if ('assign' in params)
2238 assignVar(params.assign, content, data);
2241 var append = params.__get('append',false);
2246 if (data[append] instanceof Array)
2248 data[append].push(content);
2253 data[append] = [content];
2261 jSmart.prototype.registerPlugin(
2264 function(params, data)
2266 data.smarty['continue'] = true;
2271 jSmart.prototype.registerPlugin(
2274 function(params, data)
2276 var name = params.__get('name','default');
2277 if (name in data.smarty.counter)
2279 var counter = data.smarty.counter[name];
2280 if ('start' in params)
2282 counter.value = parseInt(params['start']);
2286 counter.value = parseInt(counter.value);
2287 counter.skip = parseInt(counter.skip);
2288 if ('down' == counter.direction)
2290 counter.value -= counter.skip;
2294 counter.value += counter.skip;
2297 counter.skip = params.__get('skip',counter.skip);
2298 counter.direction = params.__get('direction',counter.direction);
2299 counter.assign = params.__get('assign',counter.assign);
2303 data.smarty.counter[name] = {
2304 value: parseInt(params.__get('start',1)),
2305 skip: parseInt(params.__get('skip',1)),
2306 direction: params.__get('direction','up'),
2307 assign: params.__get('assign',false)
2311 if (data.smarty.counter[name].assign)
2313 data[data.smarty.counter[name].assign] = data.smarty.counter[name].value;
2317 if (params.__get('print',true))
2319 return data.smarty.counter[name].value;
2326 jSmart.prototype.registerPlugin(
2329 function(params, data)
2331 var name = params.__get('name','default');
2332 var reset = params.__get('reset',false);
2333 if (!(name in data.smarty.cycle))
2335 data.smarty.cycle[name] = {arr: [''], delimiter: params.__get('delimiter',','), index: 0};
2339 if (params.__get('delimiter',false))
2341 data.smarty.cycle[name].delimiter = params.delimiter;
2343 var values = params.__get('values',false);
2347 if (values instanceof Object)
2351 arr.push(values[nm]);
2356 arr = values.split(data.smarty.cycle[name].delimiter);
2359 if (arr.length != data.smarty.cycle[name].arr.length || arr[0] != data.smarty.cycle[name].arr[0])
2361 data.smarty.cycle[name].arr = arr;
2362 data.smarty.cycle[name].index = 0;
2367 if (params.__get('advance','true'))
2369 data.smarty.cycle[name].index += 1;
2371 if (data.smarty.cycle[name].index >= data.smarty.cycle[name].arr.length || reset)
2373 data.smarty.cycle[name].index = 0;
2376 if (params.__get('assign',false))
2378 assignVar(params.assign, data.smarty.cycle[name].arr[ data.smarty.cycle[name].index ], data);
2382 if (params.__get('print',true))
2384 return data.smarty.cycle[name].arr[ data.smarty.cycle[name].index ];
2391 jSmart.prototype.print_r = function(v,indent)
2393 if (v instanceof Object)
2395 var s = ((v instanceof Array) ? 'Array['+v.length+']' : 'Object') + '<br>';
2398 if (v.hasOwnProperty(nm))
2400 s += indent + ' <strong>' + nm + '</strong> : ' + jSmart.prototype.print_r(v[nm],indent+' ') + '<br>';
2408 jSmart.prototype.registerPlugin(
2411 function(params, data)
2413 if (typeof dbgWnd != 'undefined')
2417 dbgWnd = window.open('','','width=680,height=600,resizable,scrollbars=yes');
2420 for (var nm in data)
2422 sVars += '<tr class=' + (++i%2?'odd':'even') + '><td><strong>' + nm + '</strong></td><td>' + jSmart.prototype.print_r(data[nm],'') + '</td></tr>';
2424 dbgWnd.document.write(" \
2425 <html xmlns='http://www.w3.org/1999/xhtml' xml:lang='en'> \
2427 <title>jSmart Debug Console</title> \
2428 <style type='text/css'> \
2429 table {width: 100%;} \
2430 td {vertical-align:top;width: 50%;} \
2431 .even td {background-color: #fafafa;} \
2435 <h1>jSmart Debug Console</h1> \
2436 <h2>assigned template variables</h2> \
2437 <table>" + sVars + "</table> \
2445 jSmart.prototype.registerPlugin(
2448 function(params, data)
2451 parse(params.__get('var','',0), tree);
2452 var s = process(tree, data);
2453 if ('assign' in params)
2455 assignVar(params.assign, s, data);
2462 jSmart.prototype.registerPlugin(
2465 function(params, data)
2467 var s = jSmart.prototype.getFile(params.__get('file',null,0));
2468 if ('assign' in params)
2470 assignVar(params.assign, s, data);
2477 jSmart.prototype.registerPlugin(
2480 function (params, data) {
2481 var type = params.__get('type','checkbox'),
2482 name = params.__get('name',type),
2483 realName = params.__get('name',type),
2484 values = params.__get('values',params.options),
2485 output = params.__get('options',[]),
2486 useName = ('options' in params),
2487 selected = params.__get('selected',false),
2488 separator = params.__get('separator',''),
2489 labels = Boolean(params.__get('labels',true)),
2490 label_ids = Boolean(params.__get('label_ids',false)),
2498 if (type == 'checkbox') {
2502 for (p in params.output) {
2503 output.push(params.output[p]);
2508 if (values.hasOwnProperty(p)) {
2509 value = (useName ? p : values[p]);
2510 id = realName + '_' + value;
2511 s = (labels ? ( label_ids ? '<label for="'+id+'">' : '<label>') : '');
2513 s += '<input type="' + type + '" name="' + name + '" value="' + value + '" ';
2515 s += 'id="'+id+'" ';
2517 if (selected == (useName ? p : values[p])) {
2518 s += 'checked="checked" ';
2520 s += '/>' + output[useName?p:i++];
2521 s += (labels ? '</label>' : '');
2526 if ('assign' in params) {
2527 assignVar(params.assign, res, data);
2530 return res.join('\n');
2534 jSmart.prototype.registerPlugin(
2537 function (params, data) {
2538 var url = params.__get('file', null),
2539 width = params.__get('width', false),
2540 height = params.__get('height', false),
2541 alt = params.__get('alt', ''),
2542 href = params.__get('href', params.__get('link', false)),
2543 path_prefix = params.__get('path_prefix', ''),
2544 paramNames = {file:1, width:1, height:1, alt:1, href:1, basedir:1, path_prefix:1, link:1},
2545 s = '<img src="' + path_prefix + url + '"' + ' alt="'+alt+'"' + (width ? ' width="'+width+'"':'') + (height ? ' height="'+height+'"':''),
2549 if (params.hasOwnProperty(p) && typeof(params[p]) == 'string') {
2550 if (!(p in paramNames)) {
2551 s += ' ' + p + '="' + params[p] + '"';
2556 return href ? '<a href="'+href+'">'+s+'</a>' : s;
2560 jSmart.prototype.registerPlugin(
2563 function(params, data)
2565 var values = params.__get('values',params.options);
2566 var output = params.__get('options',[]);
2567 var useName = ('options' in params);
2571 for (p in params.output)
2573 output.push(params.output[p]);
2576 var selected = params.__get('selected',false);
2583 if (values.hasOwnProperty(p))
2585 s = '<option value="' + (useName ? p : values[p]) + '"';
2586 if (selected == (useName ? p : values[p]))
2588 s += ' selected="selected"';
2590 s += '>' + output[useName ? p : i++] + '</option>';
2594 var name = params.__get('name',false);
2595 return (name ? ('<select name="' + name + '">\n' + res.join('\n') + '\n</select>') : res.join('\n')) + '\n';
2599 jSmart.prototype.registerPlugin(
2602 function(params, data)
2604 params.type = 'radio';
2605 return plugins.html_checkboxes.process(params,data);
2609 jSmart.prototype.registerPlugin(
2612 function(params, data)
2614 var prefix = params.__get('prefix','Date_');
2615 var months = ['January','February','March','April','May','June','July','August','September','October','November','December'];
2618 s += '<select name="'+prefix+'Month">\n';
2620 for (i=0; i<months.length; ++i)
2622 s += '<option value="' + i + '">' + months[i] + '</option>\n';
2626 s += '<select name="'+prefix+'Day">\n';
2627 for (i=0; i<31; ++i)
2629 s += '<option value="' + i + '">' + i + '</option>\n';
2636 jSmart.prototype.registerPlugin(
2639 function(params, data)
2643 if (params.loop instanceof Array)
2649 for (p in params.loop)
2651 if (params.loop.hasOwnProperty(p))
2653 loop.push( params.loop[p] );
2657 var rows = params.__get('rows',false);
2658 var cols = params.__get('cols',false);
2661 cols = rows ? Math.ceil(loop.length/rows) : 3;
2666 if (typeof cols == 'object')
2670 if (cols.hasOwnProperty(p))
2672 colNames.push(cols[p]);
2678 colNames = cols.split(/\s*,\s*/);
2680 cols = colNames.length;
2682 rows = rows ? rows : Math.ceil(loop.length/cols);
2684 var inner = params.__get('inner','cols');
2685 var caption = params.__get('caption','');
2686 var table_attr = params.__get('table_attr','border="1"');
2687 var th_attr = params.__get('th_attr',false);
2688 if (th_attr && typeof th_attr != 'object')
2690 th_attr = [th_attr];
2692 var tr_attr = params.__get('tr_attr',false);
2693 if (tr_attr && typeof tr_attr != 'object')
2695 tr_attr = [tr_attr];
2697 var td_attr = params.__get('td_attr',false);
2698 if (td_attr && typeof td_attr != 'object')
2700 td_attr = [td_attr];
2702 var trailpad = params.__get('trailpad',' ');
2703 var hdir = params.__get('hdir','right');
2704 var vdir = params.__get('vdir','down');
2707 for (var row=0; row<rows; ++row)
2709 s += '<tr' + (tr_attr ? ' '+tr_attr[row%tr_attr.length] : '') + '>\n';
2710 for (var col=0; col<cols; ++col)
2712 var idx = (inner=='cols') ? ((vdir=='down'?row:rows-1-row) * cols + (hdir=='right'?col:cols-1-col)) : ((hdir=='right'?col:cols-1-col) * rows + (vdir=='down'?row:rows-1-row));
2714 s += '<td' + (td_attr ? ' '+td_attr[col%td_attr.length] : '') + '>' + (idx < loop.length ? loop[idx] : trailpad) + '</td>\n';
2720 if (colNames.length)
2722 sHead = '\n<thead><tr>';
2723 for (var i=0; i<colNames.length; ++i)
2725 sHead += '\n<th' + (th_attr ? ' '+th_attr[i%th_attr.length] : '') + '>' + colNames[hdir=='right'?i:colNames.length-1-i] + '</th>';
2727 sHead += '\n</tr></thead>';
2730 return '<table ' + table_attr + '>' + (caption?'\n<caption>'+caption+'</caption>':'') + sHead + '\n<tbody>\n' + s + '</tbody>\n</table>\n';
2734 jSmart.prototype.registerPlugin(
2737 function(params, data)
2739 var file = params.__get('file',null,0);
2740 var incData = obMerge({},data,params);
2741 incData.smarty.template = file;
2742 var s = process(getTemplate(file,[],findInArray(params,'nocache')>=0), incData);
2743 if ('assign' in params)
2745 assignVar(params.assign, s, data);
2752 jSmart.prototype.registerPlugin(
2754 'include_javascript',
2755 function(params, data)
2757 var file = params.__get('file',null,0);
2758 if (params.__get('once',true) && file in scripts)
2762 scripts[file] = true;
2763 var s = execute(jSmart.prototype.getJavascript(file), {'$this':data});
2764 if ('assign' in params)
2766 assignVar(params.assign, s, data);
2773 jSmart.prototype.registerPlugin(
2776 function(params, data)
2778 return plugins['include_javascript'].process(params,data);
2782 jSmart.prototype.registerPlugin(
2785 function(params, data)
2788 for (var nm in params)
2790 if (params.hasOwnProperty(nm) && isNaN(nm) && params[nm] && typeof params[nm] == 'string' && nm != 'name' && nm != 'assign' && nm != 'script')
2792 fparams[nm] = params[nm];
2795 var prefix = 'insert_';
2796 if ('script' in params)
2798 eval(jSmart.prototype.getJavascript(params.script));
2799 prefix = 'smarty_insert_';
2801 var func = eval(prefix+params.__get('name',null,0));
2802 var s = func(fparams, data);
2803 if ('assign' in params)
2805 assignVar(params.assign, s, data);
2812 jSmart.prototype.registerPlugin(
2815 function(params, content, data, repeat)
2817 data['$this'] = data;
2818 execute(content,data);
2819 delete data['$this'];
2824 jSmart.prototype.registerPlugin(
2827 function(params, data)
2829 jSmart.prototype.configLoad(jSmart.prototype.getConfig(params.__get('file',null,0)), params.__get('section','',1), data);
2834 jSmart.prototype.registerPlugin(
2837 function(params, data)
2839 var address = params.__get('address',null);
2840 var encode = params.__get('encode','none');
2841 var text = params.__get('text',address);
2842 var cc = jSmart.prototype.PHPJS('rawurlencode','mailto').rawurlencode(params.__get('cc','')).replace('%40','@');
2843 var bcc = jSmart.prototype.PHPJS('rawurlencode','mailto').rawurlencode(params.__get('bcc','')).replace('%40','@');
2844 var followupto = jSmart.prototype.PHPJS('rawurlencode','mailto').rawurlencode(params.__get('followupto','')).replace('%40','@');
2845 var subject = jSmart.prototype.PHPJS('rawurlencode','mailto').rawurlencode( params.__get('subject','') );
2846 var newsgroups = jSmart.prototype.PHPJS('rawurlencode','mailto').rawurlencode(params.__get('newsgroups',''));
2847 var extra = params.__get('extra','');
2849 address += (cc?'?cc='+cc:'');
2850 address += (bcc?(cc?'&':'?')+'bcc='+bcc:'');
2851 address += (subject ? ((cc||bcc)?'&':'?') + 'subject='+subject : '');
2852 address += (newsgroups ? ((cc||bcc||subject)?'&':'?') + 'newsgroups='+newsgroups : '');
2853 address += (followupto ? ((cc||bcc||subject||newsgroups)?'&':'?') + 'followupto='+followupto : '');
2855 s = '<a href="mailto:' + address + '" ' + extra + '>' + text + '</a>';
2857 if (encode == 'javascript')
2859 s = "document.write('" + s + "');";
2861 for (var i=0; i<s.length; ++i)
2863 sEncoded += '%' + jSmart.prototype.PHPJS('bin2hex','mailto').bin2hex(s.substr(i,1));
2865 return '<script type="text/javascript">eval(unescape(\'' + sEncoded + "'))</script>";
2867 else if (encode == 'javascript_charcode')
2870 for (var i=0; i<s.length; ++i)
2872 codes.push(jSmart.prototype.PHPJS('ord','mailto').ord(s.substr(i,1)));
2874 return '<script type="text/javascript" language="javascript">\n<!--\n{document.write(String.fromCharCode('
2875 + codes.join(',') + '))}\n//-->\n</script>\n';
2877 else if (encode == 'hex')
2879 if (address.match(/^.+\?.+$/))
2881 throw new Error('mailto: hex encoding does not work with extra attributes. Try javascript.');
2884 for (var i=0; i<address.length; ++i)
2886 if (address.substr(i,1).match(/\w/))
2888 aEncoded += '%' + jSmart.prototype.PHPJS('bin2hex','mailto').bin2hex(address.substr(i,1));
2892 aEncoded += address.substr(i,1);
2895 aEncoded = aEncoded.toLowerCase();
2897 for (var i=0; i<text.length; ++i)
2899 tEncoded += '&#x' + jSmart.prototype.PHPJS('bin2hex','mailto').bin2hex(text.substr(i,1)) + ';';
2901 tEncoded = tEncoded.toLowerCase();
2902 return '<a href="mailto:' + aEncoded + '" ' + extra + '>' + tEncoded + '</a>';
2908 jSmart.prototype.registerPlugin(
2911 function(params, data)
2917 var res = eval(params.__get('equation',null).replace(/pi\(\s*\)/g,'PI'));
2921 if ('format' in params)
2923 res = jSmart.prototype.PHPJS('sprintf','math').sprintf(params.format,res);
2926 if ('assign' in params)
2928 assignVar(params.assign, res, data);
2935 jSmart.prototype.registerPlugin(
2938 function(params, content, data, repeat)
2944 jSmart.prototype.registerPlugin(
2947 function(params, content, data, repeat)
2953 content = new String(content);
2955 var wrap = params.__get('wrap',80);
2956 var wrap_char = params.__get('wrap_char','\n');
2957 var wrap_cut = params.__get('wrap_cut',false);
2958 var indent_char = params.__get('indent_char',' ');
2959 var indent = params.__get('indent',0);
2960 var indentStr = (new Array(indent+1)).join(indent_char);
2961 var indent_first = params.__get('indent_first',0);
2962 var indentFirstStr = (new Array(indent_first+1)).join(indent_char);
2964 var style = params.__get('style','');
2966 if (style == 'email') {
2970 var paragraphs = content.split(/[\r\n]{2}/);
2971 for (var i=0; i<paragraphs.length; ++i) {
2972 var p = paragraphs[i];
2976 p = p.replace(/^\s+|\s+$/,'').replace(/\s+/g,' ');
2977 if (indent_first> 0 ) {
2978 p = indentFirstStr + p;
2980 p = modifiers.wordwrap(p, wrap-indent, wrap_char, wrap_cut);
2982 p = p.replace(/^/mg, indentStr);
2986 var s = paragraphs.join(wrap_char+wrap_char);
2987 if ('assign' in params)
2989 assignVar(params.assign, s, data);
3000 jSmart.prototype.registerPlugin(
3003 function(s, upDigits, lcRest) {
3004 if (typeof s != 'string') {
3007 var re = new RegExp(upDigits ? '[^a-zA-Z_\u00E0-\u00FC]+' : '[^a-zA-Z0-9_\u00E0-\u00FC]');
3011 s = s.toLowerCase();
3013 for (found=s.match(re); found; found=s.match(re))
3015 var word = s.slice(0,found.index);
3016 if (word.match(/\d/))
3022 res += word.charAt(0).toUpperCase() + word.slice(1);
3024 res += s.slice(found.index, found.index+found[0].length);
3025 s = s.slice(found.index+found[0].length);
3031 return res + s.charAt(0).toUpperCase() + s.slice(1);
3035 jSmart.prototype.registerPlugin(
3040 value = value ? value : '';
3041 return new String(s) + value;
3045 jSmart.prototype.registerPlugin(
3048 function(v, recursive)
3050 if (v === null || typeof v === 'undefined') {
3052 } else if (v.constructor !== Array && v.constructor !== Object) {
3056 recursive = Boolean(recursive);
3060 if (v.hasOwnProperty(k))
3063 if (recursive && v[k] && (v[k].constructor === Array || v[k].constructor === Object)) {
3064 cnt += modifiers.count(v[k], true);
3072 jSmart.prototype.registerPlugin(
3075 function(s, includeWhitespaces)
3078 return includeWhitespaces ? s.length : s.replace(/\s/g,'').length;
3082 jSmart.prototype.registerPlugin(
3087 var found = (new String(s)).match(/\n+/g);
3090 return found.length+1;
3096 jSmart.prototype.registerPlugin(
3101 if (typeof s == 'string')
3103 var found = s.match(/[^\s]\.(?!\w)/g);
3106 return found.length;
3113 jSmart.prototype.registerPlugin(
3118 if (typeof s == 'string')
3120 var found = s.match(/\w+/g);
3123 return found.length;
3130 jSmart.prototype.registerPlugin(
3133 function(s, fmt, defaultDate)
3135 return jSmart.prototype.PHPJS('strftime','date_format').strftime(fmt?fmt:'%b %e, %Y', jSmart.prototype.makeTimeStamp(s?s:defaultDate));
3139 jSmart.prototype.registerPlugin(
3144 return (s && s!='null' && s!='undefined') ? s : (value ? value : '');
3148 jSmart.prototype.registerPlugin(
3151 function(s, esc_type, char_set)
3154 esc_type = esc_type || 'html';
3155 char_set = char_set || 'UTF-8';
3160 return s.replace(/</g, '<').replace(/>/g,'>').replace(/'/g,"'").replace(/"/g,'"');
3163 return jSmart.prototype.PHPJS('html_entity_decode','unescape').html_entity_decode(s, 1);
3165 return jSmart.prototype.PHPJS('rawurldecode','unescape').rawurldecode(s);
3171 jSmart.prototype.registerPlugin(
3174 function(s, esc_type, char_set, double_encode)
3177 esc_type = esc_type || 'html';
3178 char_set = char_set || 'UTF-8';
3179 double_encode = (typeof double_encode != 'undefined') ? Boolean(double_encode) : true;
3184 if (double_encode) {
3185 s = s.replace(/&/g, '&');
3187 return s.replace(/</g,'<').replace(/>/g,'>').replace(/'/g,''').replace(/"/g,'"');
3189 return jSmart.prototype.PHPJS('htmlentities','escape').htmlentities(s, 3, char_set);
3191 return jSmart.prototype.PHPJS('rawurlencode','escape').rawurlencode(s);
3193 return jSmart.prototype.PHPJS('rawurlencode','escape').rawurlencode(s).replace(/%2F/g, '/');
3195 return s.replace(/(^|[^\\])'/g, "$1\\'");
3198 for (var i=0; i<s.length; ++i)
3200 res += '%' + jSmart.prototype.PHPJS('bin2hex','escape').bin2hex(s.substr(i,1)).toLowerCase();
3205 for (var i=0; i<s.length; ++i) {
3206 res += '&#x' + jSmart.prototype.PHPJS('bin2hex','escape').bin2hex(s.substr(i,1)) + ';';
3211 for (var i=0; i<s.length; ++i) {
3212 res += '&#' + jSmart.prototype.PHPJS('ord','escape').ord(s.substr(i,1)) + ';';
3216 return s.replace(/@/g,' [AT] ').replace(/[.]/g,' [DOT] ');
3219 for (var i=0; i<s.length; ++i)
3221 var _ord = jSmart.prototype.PHPJS('ord','escape').ord(s.substr(i,1));
3223 res += '&#' + _ord + ';';
3225 res += s.substr(i, 1);
3231 return s.replace(/\\/g,'\\\\').replace(/'/g,"\\'").replace(/"/g,'\\"').replace(/\r/g,'\\r').replace(/\n/g,'\\n').replace(/<\//g,'<\/');
3237 jSmart.prototype.registerPlugin(
3240 function(s, repeat, indentWith)
3243 repeat = repeat ? repeat : 4;
3244 indentWith = indentWith ? indentWith : ' ';
3249 indentStr += indentWith;
3252 var tail = s.match(/\n+$/);
3253 return indentStr + s.replace(/\n+$/,'').replace(/\n/g,'\n'+indentStr) + (tail ? tail[0] : '');
3257 jSmart.prototype.registerPlugin(
3262 return new String(s).toLowerCase();
3266 jSmart.prototype.registerPlugin(
3271 return new String(s).replace(/\n/g,'<br />\n');
3276 only modifiers (flags) 'i' and 'm' are supported
3277 backslashes should be escaped e.g. \\s
3279 jSmart.prototype.registerPlugin(
3282 function(s, re, replaceWith)
3284 var pattern = re.match(/^ *\/(.*)\/(.*) *$/);
3285 return (new String(s)).replace(new RegExp(pattern[1],'g'+(pattern.length>1?pattern[2]:'')), replaceWith);
3289 jSmart.prototype.registerPlugin(
3292 function(s, search, replaceWith)
3299 search = new String(search);
3300 replaceWith = new String(replaceWith);
3303 for (pos=s.indexOf(search); pos>=0; pos=s.indexOf(search))
3305 res += s.slice(0,pos) + replaceWith;
3306 pos += search.length;
3313 jSmart.prototype.registerPlugin(
3322 return (new String(s)).replace(/(\n|.)(?!$)/g,'$1'+space);
3326 jSmart.prototype.registerPlugin(
3335 jSmart.prototype.registerPlugin(
3340 return jSmart.prototype.PHPJS('sprintf','string_format').sprintf(fmt,s);
3344 jSmart.prototype.registerPlugin(
3347 function(s, replaceWith)
3349 replaceWith = replaceWith ? replaceWith : ' ';
3350 return (new String(s)).replace(/[\s]+/g, replaceWith);
3354 jSmart.prototype.registerPlugin(
3357 function(s, addSpace)
3359 addSpace = (addSpace==null) ? true : addSpace;
3360 return (new String(s)).replace(/<[^>]*?>/g, addSpace ? ' ' : '');
3364 jSmart.prototype.registerPlugin(
3367 function(s, length, etc, breakWords, middle)
3370 length = length ? length : 80;
3371 etc = (etc!=null) ? etc : '...';
3373 if (s.length <= length)
3378 length -= Math.min(length,etc.length);
3381 //one of floor()'s should be replaced with ceil() but it so in Smarty
3382 return s.slice(0,Math.floor(length/2)) + etc + s.slice(s.length-Math.floor(length/2));
3387 s = s.slice(0,length+1).replace(/\s+?(\S+)?$/,'');
3390 return s.slice(0,length) + etc;
3394 jSmart.prototype.registerPlugin(
3399 return (new String(s)).toUpperCase();
3403 jSmart.prototype.registerPlugin(
3406 function(s, width, wrapWith, breakWords)
3408 width = width || 80;
3409 wrapWith = wrapWith || '\n';
3411 var lines = (new String(s)).split('\n');
3412 for (var i=0; i<lines.length; ++i)
3414 var line = lines[i];
3416 while (line.length > width)
3419 var found = line.slice(pos).match(/\s+/);
3420 for (;found && (pos+found.index)<=width; found=line.slice(pos).match(/\s+/))
3422 pos += found.index + found[0].length;
3424 pos = pos || (breakWords ? width : (found ? found.index+found[0].length : line.length));
3425 parts += line.slice(0,pos).replace(/\s+$/,'');// + wrapWith;
3426 if (pos < line.length)
3430 line = line.slice(pos);
3432 lines[i] = parts + line;
3434 return lines.join('\n');
3439 String.prototype.fetch = function(data)
3441 var tpl = new jSmart(this);
3442 return tpl.fetch(data);
3445 if (typeof module === "object" && module && typeof module.exports === "object") {
3446 module.exports = jSmart;
3448 if (typeof global !== "undefined") {
3449 global.jSmart = jSmart;
3452 if (typeof define === "function" && define.amd) {
3453 define("jSmart", [], function () { return jSmart; });