]> git.mxchange.org Git - friendica.git/blob - frameworks/jsmart/jsmart.js
little js change for network colorbox images
[friendica.git] / frameworks / jsmart / jsmart.js
1 /*!
2  * jSmart Javascript template engine
3  * https://github.com/umakantp/jsmart
4  *
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
9  */
10
11
12 (function() {
13
14     /**
15        merges two or more objects into one
16        shallow copy for objects
17     */
18     function obMerge(ob1, ob2 /*, ...*/)
19     {
20         for (var i=1; i<arguments.length; ++i)
21         {
22            for (var nm in arguments[i])
23            {
24               ob1[nm] = arguments[i][nm];
25            }
26         }
27         return ob1;
28     }
29
30     /**
31        @return  number of own properties in ob
32     */
33     function countProperties(ob)
34     {
35         var count = 0;
36         for (var nm in ob)
37         {
38             if (ob.hasOwnProperty(nm))
39             {
40                 count++;
41             }
42         }
43         return count;
44     }
45
46     /**
47        IE workaround
48     */
49     function findInArray(a, v)
50     {
51         if (Array.prototype.indexOf) {
52             return a.indexOf(v);
53         }
54         for (var i=0; i < a.length; ++i)
55         {
56             if (a[i] === v)
57             {
58                 return i;
59             }
60         }
61         return -1;
62     }
63
64     function evalString(s)
65     {
66         return s.replace(/\\t/,'\t').replace(/\\n/,'\n').replace(/\\(['"\\])/g,'$1');
67     }
68
69     /**
70        @return  s trimmed and without quotes
71     */
72     function trimQuotes(s)
73     {
74         return evalString(s.replace(/^['"](.*)['"]$/,'$1')).replace(/^\s+|\s+$/g,'');
75     }
76
77     /**
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
84     */
85     function findTag(re,s)
86     {
87         var openCount = 0;
88         var offset = 0;
89         var ldelim = jSmart.prototype.left_delimiter;
90         var rdelim = jSmart.prototype.right_delimiter;
91         var skipInWS = jSmart.prototype.auto_literal;
92
93         var reAny = /^\s*(.+)\s*$/i;
94         var reTag = re ? new RegExp('^\\s*('+re+')\\s*$','i') : reAny;
95
96         for (var i=0; i<s.length; ++i)
97         {
98             if (s.substr(i,ldelim.length) == ldelim)
99             {
100                 if (skipInWS && i+1 < s.length && s.substr(i+1,1).match(/\s/))
101                 {
102                     continue;
103                 }
104                 if (!openCount)
105                 {
106                     s = s.slice(i);
107                     offset += parseInt(i);
108                     i = 0;
109                 }
110                 ++openCount;
111             }
112             else if (s.substr(i,rdelim.length) == rdelim)
113             {
114                 if (skipInWS && i-1 >= 0 && s.substr(i-1,1).match(/\s/))
115                 {
116                     continue;
117                 }
118                 if (!--openCount)
119                 {
120                     var sTag = s.slice(ldelim.length,i).replace(/[\r\n]/g, ' ');
121                     var found = sTag.match(reTag);
122                     if (found)
123                     {
124                         found.index = offset;
125                         found[0] = s.slice(0,i+rdelim.length);
126                         return found;
127                     }
128                 }
129                 if (openCount < 0) //ignore any number of unmatched right delimiters
130                 {
131                     openCount = 0;
132                 }
133             }
134         }
135         return null;
136     }
137
138     function findCloseTag(reClose,reOpen,s)
139     {
140         var sInner = '';
141         var closeTag = null;
142         var openTag = null;
143         var findIndex = 0;
144
145         do
146         {
147             if (closeTag)
148             {
149                 findIndex += closeTag[0].length;
150             }
151             closeTag = findTag(reClose,s);
152             if (!closeTag)
153             {
154                 throw new Error('Unclosed {'+reOpen+'}');
155             }
156             sInner += s.slice(0,closeTag.index);
157             findIndex += closeTag.index;
158             s = s.slice(closeTag.index+closeTag[0].length);
159
160             openTag = findTag(reOpen,sInner);
161             if (openTag)
162             {
163                 sInner = sInner.slice(openTag.index+openTag[0].length);
164             }
165         }
166         while (openTag);
167
168         closeTag.index = findIndex;
169         return closeTag;
170     }
171
172     function findElseTag(reOpen, reClose, reElse, s)
173     {
174         var offset = 0;
175         for (var elseTag=findTag(reElse,s); elseTag; elseTag=findTag(reElse,s))
176         {
177             var openTag = findTag(reOpen,s);
178             if (!openTag || openTag.index > elseTag.index)
179             {
180                 elseTag.index += offset;
181                 return elseTag;
182             }
183             else
184             {
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;
190             }
191         }
192         return null;
193     }
194
195     function execute(code, data)
196     {
197         if (typeof(code) == 'string')
198         {
199             with ({'__code':code})
200             {
201                 with (modifiers)
202                 {
203                     with (data)
204                     {
205                         try {
206                             return eval(__code);
207                         }
208                         catch(e)
209                         {
210                             throw new Error(e.message + ' in \n' + code);
211                         }
212                     }
213                 }
214             }
215         }
216         return code;
217     }
218
219     /**
220      * Execute function when we have a object.
221      *
222      * @param object obj  Object of the function to be called.
223      * @param array  args Arguments to pass to a function.
224      *
225      * @return
226      * @throws Error If function obj does not exists.
227      */
228     function executeByFuncObject(obj, args) {
229         try {
230             return obj.apply(this, args);
231         } catch (e) {
232             throw new Error(e.message);
233         }
234     }
235
236     function assignVar(nm, val, data)
237     {
238         if (nm.match(/\[\]$/))  //ar[] =
239         {
240             data[ nm.replace(/\[\]$/,'') ].push(val);
241         }
242         else
243         {
244             data[nm] = val;
245         }
246     }
247
248     var buildInFunctions =
249         {
250             expression:
251             {
252                 parse: function(s, tree)
253                 {
254                     var e = parseExpression(s);
255
256                     tree.push({
257                         type: 'build-in',
258                         name: 'expression',
259                         expression: e.tree,
260                         params: parseParams(s.slice(e.value.length).replace(/^\s+|\s+$/g,''))
261                     });
262
263                     return e.tree;
264
265                 },
266                 process: function(node, data)
267                 {
268                     var params = getActualParamValues(node.params, data);
269                     var res = process([node.expression],data);
270
271                     if (findInArray(params, 'nofilter') < 0)
272                     {
273                         for (var i=0; i<default_modifiers.length; ++i)
274                         {
275                             var m = default_modifiers[i];
276                             m.params.__parsed[0] = {type:'text', data:res};
277                             res = process([m],data);
278                         }
279                         if (escape_html)
280                         {
281                             res = modifiers.escape(res);
282                         }
283                         res = applyFilters(varFilters,res);
284
285                         if (tpl_modifiers.length) {
286                             __t = function(){ return res; }
287                             res = process(tpl_modifiers,data);
288                         }
289                     }
290                     return res;
291                 }
292             },
293
294             operator:
295             {
296                 process: function(node, data)
297                 {
298                     var params = getActualParamValues(node.params, data);
299                     var arg1 = params[0];
300
301                     if (node.optype == 'binary')
302                     {
303                         var arg2 = params[1];
304                         if (node.op == '=')
305                         {
306                             getVarValue(node.params.__parsed[0], data, arg2);
307                             return '';
308                         }
309                         else if (node.op.match(/(\+=|-=|\*=|\/=|%=)/))
310                         {
311                             arg1 = getVarValue(node.params.__parsed[0], data);
312                             switch (node.op)
313                             {
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;
319                             }
320                             return getVarValue(node.params.__parsed[0], data, arg1);
321                         }
322                         else if (node.op.match(/div/))
323                         {
324                             return (node.op!='div')^(arg1%arg2==0);
325                         }
326                         else if (node.op.match(/even/))
327                         {
328                             return (node.op!='even')^((arg1/arg2)%2==0);
329                         }
330                         else if (node.op.match(/xor/))
331                         {
332                             return (arg1||arg2) && !(arg1&&arg2);
333                         }
334
335                         switch (node.op)
336                         {
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;
352                         }
353                     }
354                     else if (node.op == '!')
355                     {
356                         return !arg1;
357                     }
358                     else
359                     {
360                         var isVar = node.params.__parsed[0].type == 'var';
361                         if (isVar)
362                         {
363                             arg1 = getVarValue(node.params.__parsed[0], data);
364                         }
365                         var v = arg1;
366                         if (node.optype == 'pre-unary')
367                         {
368                             switch (node.op)
369                             {
370                             case '-':  v=-arg1;  break;
371                             case '++': v=++arg1; break;
372                             case '--': v=--arg1; break;
373                             }
374                             if (isVar)
375                             {
376                                 getVarValue(node.params.__parsed[0], data, arg1);
377                             }
378                         }
379                         else
380                         {
381                             switch (node.op)
382                             {
383                             case '++': arg1++; break;
384                             case '--': arg1--; break;
385                             }
386                             getVarValue(node.params.__parsed[0], data, arg1);
387                         }
388                         return v;
389                     }
390                 }
391             },
392
393             section:
394             {
395                 type: 'block',
396                 parse: function(params, tree, content)
397                 {
398                     var subTree = [];
399                     var subTreeElse = [];
400                     tree.push({
401                         type: 'build-in',
402                         name: 'section',
403                         params: params,
404                         subTree: subTree,
405                         subTreeElse: subTreeElse
406                     });
407
408                     var findElse = findElseTag('section [^}]+', '\/section', 'sectionelse', content);
409                     if (findElse)
410                     {
411                         parse(content.slice(0,findElse.index),subTree);
412                         parse(content.slice(findElse.index+findElse[0].length).replace(/^[\r\n]/,''), subTreeElse);
413                     }
414                     else
415                     {
416                         parse(content, subTree);
417                     }
418                 },
419
420                 process: function(node, data)
421                 {
422                     var params = getActualParamValues(node.params, data);
423
424                     var props = {};
425                     data.smarty.section[params.__get('name',null,0)] = props;
426
427                     var show = params.__get('show',true);
428                     props.show = show;
429                     if (!show)
430                     {
431                         return process(node.subTreeElse, data);
432                     }
433
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'));
438                     if (isNaN(max))
439                     {
440                         max = Number.MAX_VALUE;
441                     }
442
443                     if (from < 0)
444                     {
445                         from += to;
446                         if (from < 0)
447                         {
448                             from = 0;
449                         }
450                     }
451                     else if (from >= to)
452                     {
453                         from = to ? to-1 : 0;
454                     }
455
456                     var count = 0;
457                     var loop = 0;
458                     var i = from;
459                     for (; i>=0 && i<to && count<max; i+=step,++count)
460                     {
461                         loop = i;
462                     }
463                     props.total = count;
464                     props.loop = count;  //? - because it is so in Smarty
465
466                     count = 0;
467                     var s = '';
468                     for (i=from; i>=0 && i<to && count<max; i+=step,++count)
469                     {
470                         if (data.smarty['break'])
471                         {
472                             break;
473                         }
474
475                         props.first = (i==from);
476                         props.last = ((i+step)<0 || (i+step)>=to);
477                         props.index = i;
478                         props.index_prev = i-step;
479                         props.index_next = i+step;
480                         props.iteration = props.rownum = count+1;
481
482                         s += process(node.subTree, data);
483                         data.smarty['continue'] = false;
484                     }
485                     data.smarty['break'] = false;
486
487                     if (count)
488                     {
489                         return s;
490                     }
491                     return process(node.subTreeElse, data);
492                 }
493             },
494
495             setfilter:
496             {
497                 type: 'block',
498                 parseParams: function(paramStr)
499                 {
500                     return [parseExpression('__t()|' + paramStr).tree];
501                 },
502
503                 parse: function(params, tree, content)
504                 {
505                     tree.push({
506                         type: 'build-in',
507                         name: 'setfilter',
508                         params: params,
509                         subTree: parse(content,[])
510                     });
511                 },
512
513                 process: function(node, data)
514                 {
515                     tpl_modifiers = node.params;
516                     var s = process(node.subTree, data);
517                     tpl_modifiers = [];
518                     return s;
519                 }
520             },
521
522             'for':
523             {
524                 type: 'block',
525                 parseParams: function(paramStr)
526                 {
527                     var res = paramStr.match(/^\s*\$(\w+)\s*=\s*([^\s]+)\s*to\s*([^\s]+)\s*(?:step\s*([^\s]+))?\s*(.*)$/);
528                     if (!res)
529                     {
530                         throw new Error('Invalid {for} parameters: '+paramStr);
531                     }
532                     return parseParams("varName='"+res[1]+"' from="+res[2]+" to="+res[3]+" step="+(res[4]?res[4]:'1')+" "+res[5]);
533                 },
534
535                 parse: function(params, tree, content)
536                 {
537                     var subTree = [];
538                     var subTreeElse = [];
539                     tree.push({
540                         type: 'build-in',
541                         name: 'for',
542                         params: params,
543                         subTree: subTree,
544                         subTreeElse: subTreeElse
545                     });
546
547                     var findElse = findElseTag('for\\s[^}]+', '\/for', 'forelse', content);
548                     if (findElse)
549                     {
550                         parse(content.slice(0,findElse.index),subTree);
551                         parse(content.slice(findElse.index+findElse[0].length), subTreeElse);
552                     }
553                     else
554                     {
555                         parse(content, subTree);
556                     }
557                 },
558
559                 process: function(node, data)
560                 {
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'));
565                     if (isNaN(step))
566                     {
567                         step = 1;
568                     }
569                     var max = parseInt(params.__get('max'));
570                     if (isNaN(max))
571                     {
572                         max = Number.MAX_VALUE;
573                     }
574
575                     var count = 0;
576                     var s = '';
577                     var total = Math.min( Math.ceil( ((step > 0 ? to-from : from-to)+1) / Math.abs(step)  ), max);
578
579                     for (var i=parseInt(params.from); count<total; i+=step,++count)
580                     {
581                         if (data.smarty['break'])
582                         {
583                             break;
584                         }
585                         data[params.varName] = i;
586                         s += process(node.subTree, data);
587                         data.smarty['continue'] = false;
588                     }
589                     data.smarty['break'] = false;
590
591                     if (!count)
592                     {
593                         s = process(node.subTreeElse, data);
594                     }
595                     return s;
596                 }
597             },
598
599             'if':
600             {
601                 type: 'block',
602                 parse: function(params, tree, content)
603                 {
604                     var subTreeIf = [];
605                     var subTreeElse = [];
606                     tree.push({
607                         type: 'build-in',
608                         name: 'if',
609                         params: params,
610                         subTreeIf: subTreeIf,
611                         subTreeElse: subTreeElse
612                     });
613
614                     var findElse = findElseTag('if\\s+[^}]+', '\/if', 'else[^}]*', content);
615                     if (findElse)
616                     {
617                         parse(content.slice(0,findElse.index),subTreeIf);
618
619                         content = content.slice(findElse.index+findElse[0].length);
620                         var findElseIf = findElse[1].match(/^else\s*if(.*)/);
621                         if (findElseIf)
622                         {
623                             buildInFunctions['if'].parse(parseParams(findElseIf[1]), subTreeElse, content.replace(/^\n/,''));
624                         }
625                         else
626                         {
627                             parse(content.replace(/^\n/,''), subTreeElse);
628                         }
629                     }
630                     else
631                     {
632                         parse(content, subTreeIf);
633                     }
634                 },
635
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)))
641                     ) {
642                         return process(node.subTreeIf, data);
643                     } else {
644                         return process(node.subTreeElse, data);
645                     }
646                 }
647             },
648
649             foreach:
650             {
651                 type: 'block',
652                 parseParams: function(paramStr)
653                 {
654                     var params = {};
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
657                     {
658                         paramStr = 'from='+res[1] + ' item='+(res[4]||res[2]);
659                         if (res[4])
660                         {
661                             paramStr += ' key='+res[2];
662                         }
663                     }
664                     return parseParams(paramStr);
665                 },
666
667                 parse: function(params, tree, content)
668                 {
669                     var subTree = [];
670                     var subTreeElse = [];
671                     tree.push({
672                         type: 'build-in',
673                         name: 'foreach',
674                         params: params,
675                         subTree: subTree,
676                         subTreeElse: subTreeElse
677                     });
678
679                     var findElse = findElseTag('foreach\\s[^}]+', '\/foreach', 'foreachelse', content);
680                     if (findElse)
681                     {
682                         parse(content.slice(0,findElse.index),subTree);
683                         parse(content.slice(findElse.index+findElse[0].length).replace(/^[\r\n]/,''), subTreeElse);
684                     }
685                     else
686                     {
687                         parse(content, subTree);
688                     }
689                 },
690
691                 process: function(node, data)
692                 {
693                     var params = getActualParamValues(node.params, data);
694                     var a = params.from;
695                     if (typeof a == 'undefined')
696                     {
697                         a = [];
698                     }
699                     if (typeof a != 'object')
700                     {
701                         a = [a];
702                     }
703
704                     var total = countProperties(a);
705
706                     data[params.item+'__total'] = total;
707                     if ('name' in params)
708                     {
709                         data.smarty.foreach[params.name] = {};
710                         data.smarty.foreach[params.name].total = total;
711                     }
712
713                     var s = '';
714                     var i=0;
715                     for (var key in a)
716                     {
717                         if (!a.hasOwnProperty(key))
718                         {
719                             continue;
720                         }
721
722                         if (data.smarty['break'])
723                         {
724                             break;
725                         }
726
727                         data[params.item+'__key'] = isNaN(key) ? key : parseInt(key);
728                         if ('key' in params)
729                         {
730                             data[params.key] = data[params.item+'__key'];
731                         }
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);
737
738                         if ('name' in params)
739                         {
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 : '';
744                         }
745
746                         ++i;
747
748                         s += process(node.subTree, data);
749                         data.smarty['continue'] = false;
750                     }
751                     data.smarty['break'] = false;
752
753                     data[params.item+'__show'] = (i>0);
754                     if (params.name)
755                     {
756                         data.smarty.foreach[params.name].show = (i>0) ? 1 : '';
757                     }
758                     if (i>0)
759                     {
760                         return s;
761                     }
762                     return process(node.subTreeElse, data);
763                 }
764             },
765
766             'function':
767             {
768                 type: 'block',
769                 parse: function(params, tree, content)
770                 {
771                     var subTree = [];
772                     plugins[trimQuotes(params.name?params.name:params[0])] =
773                         {
774                             type: 'function',
775                             subTree: subTree,
776                             defautParams: params,
777                             process: function(params, data)
778                             {
779                                 var defaults = getActualParamValues(this.defautParams,data);
780                                 delete defaults.name;
781                                 return process(this.subTree, obMerge({},data,defaults,params));
782                             }
783                         };
784                     parse(content, subTree);
785                 }
786             },
787
788             php:
789             {
790                 type: 'block',
791                 parse: function(params, tree, content) {}
792             },
793
794             'extends':
795             {
796                 type: 'function',
797                 parse: function(params, tree)
798                 {
799                     tree.splice(0,tree.length);
800                     getTemplate(trimQuotes(params.file?params.file:params[0]),tree);
801                 }
802             },
803
804             block:
805             {
806                 type: 'block',
807                 parse: function(params, tree, content)
808                 {
809                     tree.push({
810                         type: 'build-in',
811                         name: 'block',
812                         params: params
813                     });
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;
818
819                     onParseVar = function(nm)
820                     {
821                         if (nm.match(/^\s*[$]smarty.block.child\s*$/))
822                         {
823                             params.hasChild = true;
824                         }
825                         if (nm.match(/^\s*[$]smarty.block.parent\s*$/))
826                         {
827                             params.hasParent = true;
828                         }
829                     }
830                     var tree = parse(content, []);
831                     onParseVar = function(nm) {}
832
833                     var blockName = trimQuotes(params.name?params.name:params[0]);
834                     if (!(blockName in blocks))
835                     {
836                         blocks[blockName] = [];
837                     }
838                     blocks[blockName].push({tree:tree, params:params});
839                 },
840
841                 process: function(node, data)
842                 {
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;
847                 },
848
849                 processBlocks: function(blockAncestry, i, data)
850                 {
851                     if (!i && blockAncestry[i].params.hide) {
852                         data.smarty.block.child = '';
853                         return;
854                     }
855                     var append = true;
856                     var prepend = false;
857                     for (; i>=0; --i)
858                     {
859                         if (blockAncestry[i].params.hasParent)
860                         {
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;
866                         }
867
868                         var tmpChild = data.smarty.block.child;
869                         var s = process(blockAncestry[i].tree, data);
870                         data.smarty.block.child = tmpChild;
871
872                         if (blockAncestry[i].params.hasChild)
873                         {
874                             data.smarty.block.child = s;
875                         }
876                         else if (append)
877                         {
878                             data.smarty.block.child = s + data.smarty.block.child;
879                         }
880                         else if (prepend)
881                         {
882                             data.smarty.block.child += s;
883                         }
884                         append = blockAncestry[i].params.append;
885                         prepend = blockAncestry[i].params.prepend;
886                     }
887                 }
888             },
889
890             strip:
891             {
892                 type: 'block',
893                 parse: function(params, tree, content)
894                 {
895                     parse(content.replace(/[ \t]*[\r\n]+[ \t]*/g, ''), tree);
896                 }
897             },
898
899             literal:
900             {
901                 type: 'block',
902                 parse: function(params, tree, content)
903                 {
904                     parseText(content, tree);
905                 }
906             },
907
908             ldelim:
909             {
910                 type: 'function',
911                 parse: function(params, tree)
912                 {
913                     parseText(jSmart.prototype.left_delimiter, tree);
914                 }
915             },
916
917             rdelim:
918             {
919                 type: 'function',
920                 parse: function(params, tree)
921                 {
922                     parseText(jSmart.prototype.right_delimiter, tree);
923                 }
924             },
925
926             'while':
927             {
928                 type: 'block',
929                 parse: function(params, tree, content)
930                 {
931                     tree.push({
932                         type: 'build-in',
933                         name: 'while',
934                         params: params,
935                         subTree: parse(content, [])
936                     });
937                 },
938
939                 process: function(node, data)
940                 {
941                     var s = '';
942                     while (getActualParamValues(node.params,data)[0])
943                     {
944                         if (data.smarty['break'])
945                         {
946                             break;
947                         }
948                         s += process(node.subTree, data);
949                         data.smarty['continue'] = false;
950                     }
951                     data.smarty['break'] = false;
952                     return s;
953                 }
954             }
955         };
956
957     var plugins = {};
958     var modifiers = {};
959     var files = {};
960     var blocks = null;
961     var scripts = null;
962     var tpl_modifiers = [];
963
964     function parse(s, tree)
965     {
966         for (var openTag=findTag('',s); openTag; openTag=findTag('',s))
967         {
968             if (openTag.index)
969             {
970                 parseText(s.slice(0,openTag.index),tree);
971             }
972             s = s.slice(openTag.index + openTag[0].length);
973
974             var res = openTag[1].match(/^\s*(\w+)(.*)$/);
975             if (res)         //function
976             {
977                 var nm = res[1];
978                 var paramStr = (res.length>2) ? res[2].replace(/^\s+|\s+$/g,'') : '';
979
980                 if (nm in buildInFunctions)
981                 {
982                     var buildIn = buildInFunctions[nm];
983                     var params = ('parseParams' in buildIn ? buildIn.parseParams : parseParams)(paramStr);
984                     if (buildIn.type == 'block')
985                     {
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);
990                     }
991                     else
992                     {
993                         buildIn.parse(params, tree);
994                         if (nm == 'extends')
995                         {
996                             tree = []; //throw away further parsing except for {block}
997                         }
998                     }
999                     s = s.replace(/^\n/,'');
1000                 }
1001                 else if (nm in plugins)
1002                 {
1003                     var plugin = plugins[nm];
1004                     if (plugin.type == 'block')
1005                     {
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);
1009                     }
1010                     else if (plugin.type == 'function')
1011                     {
1012                         parsePluginFunc(nm, parseParams(paramStr), tree);
1013                     }
1014                     if (nm=='append' || nm=='assign' || nm=='capture' || nm=='eval' || nm=='include')
1015                     {
1016                         s = s.replace(/^\n/,'');
1017                     }
1018                 }
1019                 else   //variable
1020                 {
1021                     buildInFunctions.expression.parse(openTag[1],tree);
1022                 }
1023             }
1024             else         //variable
1025             {
1026                 var node = buildInFunctions.expression.parse(openTag[1],tree);
1027                 if (node.type=='build-in' && node.name=='operator' && node.op == '=')
1028                 {
1029                     s = s.replace(/^\n/,'');
1030                 }
1031             }
1032         }
1033         if (s)
1034         {
1035             parseText(s, tree);
1036         }
1037         return tree;
1038     }
1039
1040     function parseText(text, tree)
1041     {
1042         if (parseText.parseEmbeddedVars)
1043         {
1044             var re = /([$][\w@]+)|`([^`]*)`/;
1045             for (var found=re.exec(text); found; found=re.exec(text))
1046             {
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);
1050             }
1051         }
1052         tree.push({type: 'text', data: text});
1053         return tree;
1054     }
1055
1056     function parseFunc(name, params, tree)
1057     {
1058         params.__parsed.name = parseText(name,[])[0];
1059         tree.push({
1060             type: 'plugin',
1061             name: '__func',
1062             params: params
1063         });
1064         return tree;
1065     }
1066
1067     function parseOperator(op, type, precedence, tree)
1068     {
1069         tree.push({
1070             type: 'build-in',
1071             name: 'operator',
1072             op: op,
1073             optype: type,
1074             precedence: precedence,
1075             params: {}
1076         });
1077     }
1078
1079     function parseVar(s, e, nm)
1080     {
1081         var rootName = e.token;
1082         var parts = [{type:'text', data:nm.replace(/^(\w+)@(key|index|iteration|first|last|show|total)/gi, "$1__$2")}];
1083
1084         var re = /^(?:\.|\s*->\s*|\[\s*)/;
1085         for (var op=s.match(re); op; op=s.match(re))
1086         {
1087             e.token += op[0];
1088             s = s.slice(op[0].length);
1089
1090             var eProp = {value:'', tree:[]};
1091             if (op[0].match(/\[/))
1092             {
1093                 eProp = parseExpression(s);
1094                 if (eProp)
1095                 {
1096                     e.token += eProp.value;
1097                     parts.push( eProp.tree );
1098                     s = s.slice(eProp.value.length);
1099                 }
1100
1101                 var closeOp = s.match(/\s*\]/);
1102                 if (closeOp)
1103                 {
1104                     e.token += closeOp[0];
1105                     s = s.slice(closeOp[0].length);
1106                 }
1107             }
1108             else
1109             {
1110                 var parseMod = parseModifiers.stop;
1111                 parseModifiers.stop = true;
1112                 if (lookUp(s,eProp))
1113                 {
1114                     e.token += eProp.value;
1115                     var part = eProp.tree[0];
1116                     if (part.type == 'plugin' && part.name == '__func')
1117                     {
1118                         part.hasOwner = true;
1119                     }
1120                     parts.push( part );
1121                     s = s.slice(eProp.value.length);
1122                 }
1123                 else
1124                 {
1125                     eProp = false;
1126                 }
1127                 parseModifiers.stop = parseMod;
1128             }
1129
1130             if (!eProp)
1131             {
1132                 parts.push({type:'text', data:''});
1133             }
1134         }
1135
1136         e.tree.push({type: 'var', parts: parts});
1137
1138         e.value += e.token.substr(rootName.length);
1139
1140         onParseVar(e.token);
1141
1142         return s;
1143     }
1144
1145     function onParseVar(nm)  {}
1146
1147
1148     var tokens =
1149         [
1150             {
1151                 re: /^\$([\w@]+)/,   //var
1152                 parse: function(e, s)
1153                 {
1154                     parseModifiers(parseVar(s, e, RegExp.$1), e);
1155                 }
1156             },
1157             {
1158                 re: /^(true|false)/i,  //bool
1159                 parse: function(e, s)
1160                 {
1161                     parseText(e.token.match(/true/i) ? '1' : '', e.tree);
1162                 }
1163             },
1164             {
1165                 re: /^'([^'\\]*(?:\\.[^'\\]*)*)'/, //single quotes
1166                 parse: function(e, s)
1167                 {
1168                     parseText(evalString(RegExp.$1), e.tree);
1169                     parseModifiers(s, e);
1170                 }
1171             },
1172             {
1173                 re: /^"([^"\\]*(?:\\.[^"\\]*)*)"/,  //double quotes
1174                 parse: function(e, s)
1175                 {
1176                     var v = evalString(RegExp.$1);
1177                     var isVar = v.match(tokens[0].re);
1178                     if (isVar)
1179                     {
1180                         var eVar = {token:isVar[0], tree:[]};
1181                         parseVar(v, eVar, isVar[1]);
1182                         if (eVar.token.length == v.length)
1183                         {
1184                             e.tree.push( eVar.tree[0] );
1185                             return;
1186                         }
1187                     }
1188                     parseText.parseEmbeddedVars = true;
1189                     e.tree.push({
1190                         type: 'plugin',
1191                         name: '__quoted',
1192                         params: {__parsed: parse(v,[])}
1193                     });
1194                     parseText.parseEmbeddedVars = false;
1195                     parseModifiers(s, e);
1196                 }
1197             },
1198             {
1199                 re: /^(\w+)\s*[(]([)]?)/,  //func()
1200                 parse: function(e, s)
1201                 {
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);
1208                 }
1209             },
1210             {
1211                 re: /^\s*\(\s*/,  //expression in parentheses
1212                 parse: function(e, s)
1213                 {
1214                     var parens = [];
1215                     e.tree.push(parens);
1216                     parens.parent = e.tree;
1217                     e.tree = parens;
1218                 }
1219             },
1220             {
1221                 re: /^\s*\)\s*/,
1222                 parse: function(e, s)
1223                 {
1224                     if (e.tree.parent) //it may be the end of func() or (expr)
1225                     {
1226                         e.tree = e.tree.parent;
1227                     }
1228                 }
1229             },
1230             {
1231                 re: /^\s*(\+\+|--)\s*/,
1232                 parse: function(e, s)
1233                 {
1234                     if (e.tree.length && e.tree[e.tree.length-1].type == 'var')
1235                     {
1236                         parseOperator(RegExp.$1, 'post-unary', 1, e.tree);
1237                     }
1238                     else
1239                     {
1240                         parseOperator(RegExp.$1, 'pre-unary', 1, e.tree);
1241                     }
1242                 }
1243             },
1244             {
1245                 re: /^\s*(===|!==|==|!=)\s*/,
1246                 parse: function(e, s)
1247                 {
1248                     parseOperator(RegExp.$1, 'binary', 6, e.tree);
1249                 }
1250             },
1251             {
1252                 re: /^\s+(eq|ne|neq)\s+/i,
1253                 parse: function(e, s)
1254                 {
1255                     var op = RegExp.$1.replace(/ne(q)?/,'!=').replace(/eq/,'==');
1256                     parseOperator(op, 'binary', 6, e.tree);
1257                 }
1258             },
1259             {
1260                 re: /^\s*!\s*/,
1261                 parse: function(e, s)
1262                 {
1263                     parseOperator('!', 'pre-unary', 2, e.tree);
1264                 }
1265             },
1266             {
1267                 re: /^\s+not\s+/i,
1268                 parse: function(e, s)
1269                 {
1270                     parseOperator('!', 'pre-unary', 2, e.tree);
1271                 }
1272             },
1273             {
1274                 re: /^\s*(=|\+=|-=|\*=|\/=|%=)\s*/,
1275                 parse: function(e, s)
1276                 {
1277                     parseOperator(RegExp.$1, 'binary', 10, e.tree);
1278                 }
1279             },
1280             {
1281                 re: /^\s*(\*|\/|%)\s*/,
1282                 parse: function(e, s)
1283                 {
1284                     parseOperator(RegExp.$1, 'binary', 3, e.tree);
1285                 }
1286             },
1287             {
1288                 re: /^\s+mod\s+/i,
1289                 parse: function(e, s)
1290                 {
1291                     parseOperator('%', 'binary', 3, e.tree);
1292                 }
1293             },
1294             {
1295                 re: /^\s*(\+|-)\s*/,
1296                 parse: function(e, s)
1297                 {
1298                     if (!e.tree.length || e.tree[e.tree.length-1].name == 'operator')
1299                     {
1300                         parseOperator(RegExp.$1, 'pre-unary', 4, e.tree);
1301                     }
1302                     else
1303                     {
1304                         parseOperator(RegExp.$1, 'binary', 4, e.tree);
1305                     }
1306                 }
1307             },
1308             {
1309                 re: /^\s*(<=|>=|<>|<|>)\s*/,
1310                 parse: function(e, s)
1311                 {
1312                     parseOperator(RegExp.$1.replace(/<>/,'!='), 'binary', 5, e.tree);
1313                 }
1314             },
1315             {
1316                 re: /^\s+(lt|lte|le|gt|gte|ge)\s+/i,
1317                 parse: function(e, s)
1318                 {
1319                     var op = RegExp.$1.replace(/lt/,'<').replace(/l(t)?e/,'<=').replace(/gt/,'>').replace(/g(t)?e/,'>=');
1320                     parseOperator(op, 'binary', 5, e.tree);
1321                 }
1322             },
1323             {
1324                 re: /^\s+(is\s+(not\s+)?div\s+by)\s+/i,
1325                 parse: function(e, s)
1326                 {
1327                     parseOperator(RegExp.$2?'div_not':'div', 'binary', 7, e.tree);
1328                 }
1329             },
1330             {
1331                 re: /^\s+is\s+(not\s+)?(even|odd)(\s+by\s+)?\s*/i,
1332                 parse: function(e, s)
1333                 {
1334                     var op = RegExp.$1 ? ((RegExp.$2=='odd')?'even':'even_not') : ((RegExp.$2=='odd')?'even_not':'even');
1335                     parseOperator(op, 'binary', 7, e.tree);
1336                     if (!RegExp.$3)
1337                     {
1338                         parseText('1', e.tree);
1339                     }
1340                 }
1341             },
1342             {
1343                 re: /^\s*(&&)\s*/,
1344                 parse: function(e, s)
1345                 {
1346                     parseOperator(RegExp.$1, 'binary', 8, e.tree);
1347                 }
1348             },
1349             {
1350                 re: /^\s*(\|\|)\s*/,
1351                 parse: function(e, s)
1352                 {
1353                     parseOperator(RegExp.$1, 'binary', 9, e.tree);
1354                 }
1355             },
1356             {
1357                 re: /^\s+and\s+/i,
1358                 parse: function(e, s)
1359                 {
1360                     parseOperator('&&', 'binary', 11, e.tree);
1361                 }
1362             },
1363             {
1364                 re: /^\s+xor\s+/i,
1365                 parse: function(e, s)
1366                 {
1367                     parseOperator('xor', 'binary', 12, e.tree);
1368                 }
1369             },
1370             {
1371                 re: /^\s+or\s+/i,
1372                 parse: function(e, s)
1373                 {
1374                     parseOperator('||', 'binary', 13, e.tree);
1375                 }
1376             },
1377             {
1378                 re: /^#(\w+)#/,  //config variable
1379                 parse: function(e, s)
1380                 {
1381                     var eVar = {token:'$smarty',tree:[]};
1382                     parseVar('.config.'+RegExp.$1, eVar, 'smarty');
1383                     e.tree.push( eVar.tree[0] );
1384                     parseModifiers(s, e);
1385                 }
1386             },
1387             {
1388                 re: /^\s*\[\s*/,   //array
1389                 parse: function(e, s)
1390                 {
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*\]/);
1395                     if (paren)
1396                     {
1397                         e.value += paren[0];
1398                     }
1399                 }
1400             },
1401             {
1402                 re: /^[\d.]+/, //number
1403                 parse: function(e, s)
1404                 {
1405                     if (e.token.indexOf('.') > -1) {
1406                         e.token = parseFloat(e.token);
1407                     } else {
1408                         e.token = parseInt(e.token, 10);
1409                     }
1410                     parseText(e.token, e.tree);
1411                     parseModifiers(s, e);
1412                 }
1413             },
1414             {
1415                 re: /^\w+/, //static
1416                 parse: function(e, s)
1417                 {
1418                     parseText(e.token, e.tree);
1419                     parseModifiers(s, e);
1420                 }
1421             }
1422         ];
1423
1424     function parseModifiers(s, e)
1425     {
1426         if (parseModifiers.stop)
1427         {
1428             return;
1429         }
1430
1431         var modifier = s.match(/^\|(\w+)/);
1432         if (!modifier)
1433         {
1434             return;
1435         }
1436
1437         e.value += modifier[0];
1438
1439         var fnm = modifier[1]=='default' ? 'defaultValue' : modifier[1];
1440         s = s.slice(modifier[0].length).replace(/^\s+/,'');
1441
1442         parseModifiers.stop = true;
1443         var params = [];
1444         for (var colon=s.match(/^\s*:\s*/); colon; colon=s.match(/^\s*:\s*/))
1445         {
1446             e.value += s.slice(0,colon[0].length);
1447             s = s.slice(colon[0].length);
1448
1449             var param = {value:'', tree:[]};
1450             if (lookUp(s, param))
1451             {
1452                 e.value += param.value;
1453                 params.push(param.tree[0]);
1454                 s = s.slice(param.value.length);
1455             }
1456             else
1457             {
1458                 parseText('',params);
1459             }
1460         }
1461         parseModifiers.stop = false;
1462
1463         params.unshift(e.tree.pop());  //modifiers have the highest priority
1464         e.tree.push(parseFunc(fnm,{__parsed:params},[])[0]);
1465
1466         parseModifiers(s, e);  //modifiers can be combined
1467     }
1468
1469     function lookUp(s,e)
1470     {
1471         if (!s)
1472         {
1473             return false;
1474         }
1475
1476         if (s.substr(0,jSmart.prototype.left_delimiter.length)==jSmart.prototype.left_delimiter)
1477         {
1478             var tag = findTag('',s);
1479             if (tag)
1480             {
1481                 e.token = tag[0];
1482                 e.value += tag[0];
1483                 parse(tag[0], e.tree);
1484                 parseModifiers(s.slice(e.value.length), e);
1485                 return true;
1486             }
1487         }
1488
1489         for (var i=0; i<tokens.length; ++i)
1490         {
1491             if (s.match(tokens[i].re))
1492             {
1493                 e.token = RegExp.lastMatch;
1494                 e.value += RegExp.lastMatch;
1495                 tokens[i].parse(e, s.slice(e.token.length));
1496                 return true;
1497             }
1498         }
1499         return false;
1500     }
1501
1502     function bundleOp(i, tree, precedence)
1503     {
1504         var op = tree[i];
1505         if (op.name == 'operator' && op.precedence == precedence && !op.params.__parsed)
1506         {
1507             if (op.optype == 'binary')
1508             {
1509                 op.params.__parsed = [tree[i-1],tree[i+1]];
1510                 tree.splice(i-1,3,op);
1511                 return true;
1512             }
1513             else if (op.optype == 'post-unary')
1514             {
1515                 op.params.__parsed = [tree[i-1]];
1516                 tree.splice(i-1,2,op);
1517                 return true;
1518             }
1519
1520             op.params.__parsed = [tree[i+1]];
1521             tree.splice(i,2,op);
1522         }
1523         return false;
1524     }
1525
1526     function composeExpression(tree)
1527     {
1528         var i = 0;
1529         for (i=0; i<tree.length; ++i)
1530         {
1531             if (tree[i] instanceof Array)
1532             {
1533                 tree[i] = composeExpression(tree[i])
1534             }
1535         }
1536
1537         for (var precedence=1; precedence<14; ++precedence)
1538         {
1539             if (precedence==2 || precedence==10)
1540             {
1541                 for (i=tree.length; i>0; --i)
1542                 {
1543                     i -= bundleOp(i-1, tree, precedence);
1544                 }
1545             }
1546             else
1547             {
1548                 for (i=0; i<tree.length; ++i)
1549                 {
1550                     i -= bundleOp(i, tree, precedence);
1551                 }
1552             }
1553         }
1554         return tree[0]; //only one node must be left
1555     }
1556
1557     function parseExpression(s)
1558     {
1559         var e = { value:'', tree:[] };
1560         while (lookUp(s.slice(e.value.length), e)){}
1561         if (!e.tree.length)
1562         {
1563             return false;
1564         }
1565         e.tree = composeExpression(e.tree);
1566         return e;
1567     }
1568
1569     function parseParams(paramsStr, reDelim, reName)
1570     {
1571         var s = paramsStr.replace(/\n/g,' ').replace(/^\s+|\s+$/g,'');
1572         var params = [];
1573         params.__parsed = [];
1574         var paramsStr = '';
1575
1576         if (!s)
1577         {
1578             return params;
1579         }
1580
1581         if (!reDelim)
1582         {
1583             reDelim = /^\s+/;
1584             reName = /^(\w+)\s*=\s*/;
1585         }
1586
1587         while (s)
1588         {
1589             var nm = null;
1590             if (reName)
1591             {
1592                 var foundName = s.match(reName);
1593                 if (foundName)
1594                 {
1595                     nm = trimQuotes(foundName[1]);
1596                     paramsStr += s.slice(0,foundName[0].length);
1597                     s = s.slice(foundName[0].length);
1598                 }
1599             }
1600
1601             var param = parseExpression(s);
1602             if (!param)
1603             {
1604                 break;
1605             }
1606
1607             if (nm)
1608             {
1609                 params[nm] = param.value;
1610                 params.__parsed[nm] = param.tree;
1611             }
1612             else
1613             {
1614                 params.push(param.value);
1615                 params.__parsed.push(param.tree);
1616             }
1617
1618             paramsStr += s.slice(0,param.value.length);
1619             s = s.slice(param.value.length);
1620
1621             var foundDelim = s.match(reDelim);
1622             if (foundDelim)
1623             {
1624                 paramsStr += s.slice(0,foundDelim[0].length);
1625                 s = s.slice(foundDelim[0].length);
1626             }
1627             else
1628             {
1629                 break;
1630             }
1631         }
1632         params.toString = function() { return paramsStr; }
1633         return params;
1634     }
1635
1636     function parsePluginBlock(name, params, tree, content)
1637     {
1638         tree.push({
1639             type: 'plugin',
1640             name: name,
1641             params: params,
1642             subTree: parse(content,[])
1643         });
1644     }
1645
1646     function parsePluginFunc(name, params, tree)
1647     {
1648         tree.push({
1649             type: 'plugin',
1650             name: name,
1651             params: params
1652         });
1653     }
1654
1655     function getActualParamValues(params,data)
1656     {
1657         var actualParams = [];
1658         for (var nm in params.__parsed)
1659         {
1660             if (params.__parsed.hasOwnProperty(nm))
1661             {
1662                 var v = process([params.__parsed[nm]], data);
1663                 actualParams[nm] = v;
1664             }
1665         }
1666
1667         actualParams.__get = function(nm,defVal,id)
1668         {
1669             if (nm in actualParams && typeof(actualParams[nm]) != 'undefined')
1670             {
1671                 return actualParams[nm];
1672             }
1673             if (typeof(id)!='undefined' && typeof(actualParams[id]) != 'undefined')
1674             {
1675                 return actualParams[id];
1676             }
1677             if (defVal === null)
1678             {
1679                 throw new Error("The required attribute '"+nm+"' is missing");
1680             }
1681             return defVal;
1682         };
1683         return actualParams;
1684     }
1685
1686     /**
1687      * Returns boolean true if object is empty otherwise false.
1688      *
1689      * @param object hash Object you are testing against.
1690      *
1691      * @return boolean
1692      */
1693     function isEmptyObject(hash) {
1694         for (var i in hash) {
1695             if (hash.hasOwnProperty(i)) {
1696                 return false;
1697             }
1698         }
1699         return true;
1700     }
1701
1702     function getVarValue(node, data, val)
1703     {
1704         var v = data;
1705         var nm = '';
1706         for (var i=0; i<node.parts.length; ++i)
1707         {
1708             var part = node.parts[i];
1709             if (part.type == 'plugin' && part.name == '__func' && part.hasOwner)
1710             {
1711                 data.__owner = v;
1712                 v = process([node.parts[i]],data);
1713                 delete data.__owner;
1714             }
1715             else
1716             {
1717                 nm = process([part],data);
1718
1719                 //section name
1720                 if (nm in data.smarty.section && part.type=='text' && process([node.parts[0]],data)!='smarty')
1721                 {
1722                     nm = data.smarty.section[nm].index;
1723                 }
1724
1725                 //add to array
1726                 if (!nm && typeof val != 'undefined' && v instanceof Array)
1727                 {
1728                     nm = v.length;
1729                 }
1730
1731                 //set new value
1732                 if (typeof val != 'undefined' && i==node.parts.length-1)
1733                 {
1734                     v[nm] = val;
1735                 }
1736
1737                 if (typeof v == 'object' && v !== null && nm in v)
1738                 {
1739                     v = v[nm];
1740                 }
1741                 else
1742                 {
1743                     if (typeof val == 'undefined')
1744                     {
1745                         return val;
1746                     }
1747                     v[nm] = {};
1748                     v = v[nm];
1749                 }
1750             }
1751         }
1752         return v;
1753     }
1754
1755     function process(tree, data)
1756     {
1757         var res = '';
1758         for (var i=0; i<tree.length; ++i)
1759         {
1760             var s = '';
1761             var node = tree[i];
1762             if (node.type == 'text')
1763             {
1764                 s = node.data;
1765             }
1766             else if (node.type == 'var')
1767             {
1768                 s = getVarValue(node,data);
1769             }
1770             else if (node.type == 'build-in')
1771             {
1772                 s = buildInFunctions[node.name].process(node,data);
1773             }
1774             else if (node.type == 'plugin')
1775             {
1776                 var plugin = plugins[node.name];
1777                 if (plugin.type == 'block')
1778                 {
1779                     var repeat = {value:true};
1780                     plugin.process(getActualParamValues(node.params,data), '', data, repeat);
1781                     while (repeat.value)
1782                     {
1783                         repeat.value = false;
1784                         s += plugin.process(
1785                             getActualParamValues(node.params,data),
1786                             process(node.subTree, data),
1787                             data,
1788                             repeat
1789                         );
1790                     }
1791                 }
1792                 else if (plugin.type == 'function')
1793                 {
1794                     s = plugin.process(getActualParamValues(node.params,data), data);
1795                 }
1796             }
1797             if (typeof s == 'boolean')
1798             {
1799                 s = s ? '1' : '';
1800             }
1801             if (s == null) {
1802                 s = '';
1803             }
1804             if (tree.length == 1)
1805             {
1806                 return s;
1807             }
1808             res += s!==null ? s : '';
1809
1810             if (data.smarty['continue'] || data.smarty['break'])
1811             {
1812                 return res;
1813             }
1814         }
1815         return res;
1816     }
1817
1818     function getTemplate(name, tree, nocache)
1819     {
1820         if (nocache || !(name in files))
1821         {
1822             var tpl = jSmart.prototype.getTemplate(name);
1823             if (typeof(tpl) != 'string')
1824             {
1825                 throw new Error('No template for '+ name);
1826             }
1827             parse(applyFilters(jSmart.prototype.filters_global.pre, stripComments(tpl.replace(/\r\n/g,'\n'))), tree);
1828             files[name] = tree;
1829         }
1830         else
1831         {
1832             tree = files[name];
1833         }
1834         return tree;
1835     }
1836
1837     function stripComments(s)
1838     {
1839         var sRes = '';
1840         for (var openTag=s.match(/{\*/); openTag; openTag=s.match(/{\*/))
1841         {
1842             sRes += s.slice(0,openTag.index);
1843             s = s.slice(openTag.index+openTag[0].length);
1844             var closeTag = s.match(/\*}/);
1845             if (!closeTag)
1846             {
1847                 throw new Error('Unclosed {*');
1848             }
1849             s = s.slice(closeTag.index+closeTag[0].length);
1850         }
1851         return sRes + s;
1852     }
1853
1854     function applyFilters(filters, s)
1855     {
1856         for (var i=0; i<filters.length; ++i)
1857         {
1858             s = filters[i](s);
1859         }
1860         return s;
1861     }
1862
1863
1864     jSmart = function(tpl)
1865     {
1866         this.tree = [];
1867         this.tree.blocks = {};
1868         this.scripts = {};
1869         this.default_modifiers = [];
1870         this.filters = {'variable':[], 'post':[]};
1871         this.smarty = {
1872             'smarty': {
1873                 block: {},
1874                 'break': false,
1875                 capture: {},
1876                 'continue': false,
1877                 counter: {},
1878                 cycle: {},
1879                 foreach: {},
1880                 section: {},
1881                 now: Math.floor( (new Date()).getTime()/1000 ),
1882                 'const': {},
1883                 config: {},
1884                 current_dir: '/',
1885                 template: '',
1886                 ldelim: jSmart.prototype.left_delimiter,
1887                 rdelim: jSmart.prototype.right_delimiter,
1888                 version: '2.15.0'
1889             }
1890         };
1891         blocks = this.tree.blocks;
1892         parse(
1893             applyFilters(jSmart.prototype.filters_global.pre, stripComments((new String(tpl?tpl:'')).replace(/\r\n/g,'\n'))),
1894             this.tree
1895         );
1896     };
1897
1898     jSmart.prototype.fetch = function(data)
1899     {
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)
1908         {
1909             plugins.debug.process([],this.data);
1910         }
1911         return applyFilters(jSmart.prototype.filters_global.post.concat(this.filters.post), res);
1912     };
1913
1914     jSmart.prototype.escape_html = false;
1915
1916     /**
1917        @param type  valid values are 'function', 'block', 'modifier'
1918        @param callback  func(params,data)  or  block(params,content,data,repeat)
1919     */
1920     jSmart.prototype.registerPlugin = function(type, name, callback)
1921     {
1922         if (type == 'modifier')
1923         {
1924             modifiers[name] = callback;
1925         }
1926         else
1927         {
1928             plugins[name] = {'type': type, 'process': callback};
1929         }
1930     };
1931
1932     /**
1933        @param type  valid values are 'pre', 'variable', 'post'
1934        @param callback function(textValue) { ... }
1935     */
1936     jSmart.prototype.registerFilter = function(type, callback)
1937     {
1938         (this.tree ? this.filters : jSmart.prototype.filters_global)[type=='output'?'post':type].push(callback);
1939     }
1940
1941     jSmart.prototype.filters_global = {'pre':[],'variable':[],'post':[]};
1942
1943     jSmart.prototype.configLoad = function(confValues, section, data)
1944     {
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;
1948         var currSect = '';
1949         for (var f=s.match(re); f; f=s.match(re))
1950         {
1951                  s = s.slice(f.index+f[0].length);
1952                  if (f[1])
1953                  {
1954                           currSect = f[1];
1955                  }
1956                  else if ((!currSect || currSect == section) && currSect.substr(0,1) != '.')
1957                  {
1958                           if (f[3] == '"""')
1959                           {
1960                                    var triple = s.match(/"""/);
1961                                    if (triple)
1962                                    {
1963                                             data.smarty.config[f[2]] = s.slice(0,triple.index);
1964                                             s = s.slice(triple.index + triple[0].length);
1965                                    }
1966                           }
1967                           else
1968                           {
1969                                    data.smarty.config[f[2]] = trimQuotes(f[3]);
1970                           }
1971                  }
1972                  var newln = s.match(/\n+/);
1973                  if (newln)
1974                  {
1975                           s = s.slice(newln.index + newln[0].length);
1976                  }
1977                  else
1978                  {
1979                           break;
1980                  }
1981         }
1982     }
1983
1984     jSmart.prototype.clearConfig = function(varName)
1985     {
1986         if (varName)
1987         {
1988             delete this.data.smarty.config[varName];
1989         }
1990         else
1991         {
1992             this.data.smarty.config = {};
1993         }
1994     }
1995
1996     /**
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'"])
2000      */
2001     jSmart.prototype.addDefaultModifier = function(modifiers)
2002     {
2003         if (!(modifiers instanceof Array))
2004         {
2005             modifiers = [modifiers];
2006         }
2007
2008         for (var i=0; i<modifiers.length; ++i)
2009         {
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] );
2013         }
2014     }
2015
2016     jSmart.prototype.default_modifiers_global = [];
2017
2018     /**
2019        override this function
2020        @param name  value of 'file' parameter in {include} and {extends}
2021        @return template text
2022     */
2023     jSmart.prototype.getTemplate = function(name)
2024     {
2025         throw new Error('No template for ' + name);
2026     }
2027
2028     /**
2029        override this function
2030        @param name  value of 'file' parameter in {fetch}
2031        @return file content
2032     */
2033     jSmart.prototype.getFile = function(name)
2034     {
2035         throw new Error('No file for ' + name);
2036     }
2037
2038     /**
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
2043     */
2044     jSmart.prototype.getJavascript = function(name)
2045     {
2046         throw new Error('No Javascript for ' + name);
2047     }
2048
2049     /**
2050        override this function
2051        @param name  value of 'file' parameter in {config_load}
2052        @return config file content
2053     */
2054     jSmart.prototype.getConfig = function(name)
2055     {
2056         throw new Error('No config for ' + name);
2057     }
2058
2059
2060
2061     /**
2062        whether to skip tags in open brace { followed by white space(s) and close brace } with white space(s) before
2063     */
2064     jSmart.prototype.auto_literal = true;
2065
2066     jSmart.prototype.left_delimiter = '{';
2067     jSmart.prototype.right_delimiter = '}';
2068
2069     /** enables the debugging console */
2070     jSmart.prototype.debugging = false;
2071
2072
2073     jSmart.prototype.PHPJS = function(fnm, modifier)
2074     {
2075         if (eval('typeof '+fnm) == 'function')
2076         {
2077             return (typeof window == 'object') ? window : global;
2078         }
2079         else if (typeof(PHP_JS) == 'function')
2080         {
2081             return new PHP_JS();
2082         }
2083         throw new Error("Modifier '" + modifier + "' uses JavaScript port of PHP function '" + fnm + "'. You can find one at http://phpjs.org");
2084     }
2085
2086     jSmart.prototype.makeTimeStamp = function(s)
2087     {
2088         if (!s)
2089         {
2090             return Math.floor( (new Date()).getTime()/1000 );
2091         }
2092         if (isNaN(s))
2093         {
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 );
2097             }
2098             return tm;
2099         }
2100         s = new String(s);
2101         if (s.length == 14) //mysql timestamp format of YYYYMMDDHHMMSS
2102         {
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 ) );
2104         }
2105         return parseInt(s);
2106     }
2107
2108
2109
2110     /**
2111        register custom functions
2112     */
2113     jSmart.prototype.registerPlugin(
2114         'function',
2115         '__array',
2116         function(params, data)
2117         {
2118             var a = [];
2119             for (var nm in params)
2120             {
2121                 if (params.hasOwnProperty(nm) && params[nm] && typeof params[nm] != 'function')
2122                 {
2123                     a[nm] = params[nm];
2124                 }
2125             }
2126             return a;
2127         }
2128     );
2129
2130     jSmart.prototype.registerPlugin(
2131         'function',
2132         '__func',
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];
2139             }
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);
2147             } else {
2148                 fname = params.name;
2149                 return execute(fname + '(' + paramNames.join(',') + ')', mergedParams);
2150             }
2151         }
2152     );
2153
2154     jSmart.prototype.registerPlugin(
2155         'function',
2156         '__quoted',
2157         function(params, data)
2158         {
2159             return params.join('');
2160         }
2161     );
2162
2163     jSmart.prototype.registerPlugin(
2164         'function',
2165         'append',
2166         function(params, data)
2167         {
2168             var varName = params.__get('var',null,0);
2169             if (!(varName in data) || !(data[varName] instanceof Array))
2170             {
2171                 data[varName] = [];
2172             }
2173             var index = params.__get('index',false);
2174             var val = params.__get('value',null,1);
2175             if (index === false)
2176             {
2177                 data[varName].push(val);
2178             }
2179             else
2180             {
2181                 data[varName][index] = val;
2182             }
2183             return '';
2184         }
2185     );
2186
2187     jSmart.prototype.registerPlugin(
2188         'function',
2189         'assign',
2190         function(params, data)
2191         {
2192             assignVar(params.__get('var',null,0), params.__get('value',null,1), data);
2193             return '';
2194         }
2195     );
2196
2197     jSmart.prototype.registerPlugin(
2198         'function',
2199         'break',
2200         function(params, data)
2201         {
2202             data.smarty['break'] = true;
2203             return '';
2204         }
2205     );
2206
2207     jSmart.prototype.registerPlugin(
2208         'function',
2209         'call',
2210         function(params, data)
2211         {
2212             var fname = params.__get('name',null,0);
2213             delete params.name;
2214             var assignTo = params.__get('assign',false);
2215             delete params.assign;
2216             var s = plugins[fname].process(params, data);
2217             if (assignTo)
2218             {
2219                 assignVar(assignTo, s, data);
2220                 return '';
2221             }
2222             return s;
2223         }
2224     );
2225
2226     jSmart.prototype.registerPlugin(
2227         'block',
2228         'capture',
2229         function(params, content, data, repeat)
2230         {
2231             if (content)
2232             {
2233                 content = content.replace(/^\n/,'');
2234                 data.smarty.capture[params.__get('name','default',0)] = content;
2235
2236                 if ('assign' in params)
2237                 {
2238                     assignVar(params.assign, content, data);
2239                 }
2240
2241                 var append = params.__get('append',false);
2242                 if (append)
2243                 {
2244                     if (append in data)
2245                     {
2246                         if (data[append] instanceof Array)
2247                         {
2248                             data[append].push(content);
2249                         }
2250                     }
2251                                         else
2252                                         {
2253                                                  data[append] = [content];
2254                                         }
2255                 }
2256             }
2257             return '';
2258         }
2259     );
2260
2261     jSmart.prototype.registerPlugin(
2262         'function',
2263         'continue',
2264         function(params, data)
2265         {
2266             data.smarty['continue'] = true;
2267             return '';
2268         }
2269     );
2270
2271     jSmart.prototype.registerPlugin(
2272         'function',
2273         'counter',
2274         function(params, data)
2275         {
2276             var name = params.__get('name','default');
2277             if (name in data.smarty.counter)
2278             {
2279                 var counter = data.smarty.counter[name];
2280                 if ('start' in params)
2281                 {
2282                     counter.value = parseInt(params['start']);
2283                 }
2284                 else
2285                 {
2286                     counter.value = parseInt(counter.value);
2287                     counter.skip = parseInt(counter.skip);
2288                     if ('down' == counter.direction)
2289                     {
2290                         counter.value -= counter.skip;
2291                     }
2292                     else
2293                     {
2294                         counter.value += counter.skip;
2295                     }
2296                 }
2297                 counter.skip = params.__get('skip',counter.skip);
2298                 counter.direction = params.__get('direction',counter.direction);
2299                 counter.assign = params.__get('assign',counter.assign);
2300             }
2301             else
2302             {
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)
2308                 };
2309             }
2310
2311             if (data.smarty.counter[name].assign)
2312             {
2313                 data[data.smarty.counter[name].assign] = data.smarty.counter[name].value;
2314                 return '';
2315             }
2316
2317             if (params.__get('print',true))
2318             {
2319                 return data.smarty.counter[name].value;
2320             }
2321
2322             return '';
2323         }
2324     );
2325
2326     jSmart.prototype.registerPlugin(
2327         'function',
2328         'cycle',
2329         function(params, data)
2330         {
2331             var name = params.__get('name','default');
2332             var reset = params.__get('reset',false);
2333             if (!(name in data.smarty.cycle))
2334             {
2335                 data.smarty.cycle[name] = {arr: [''], delimiter: params.__get('delimiter',','), index: 0};
2336                 reset = true;
2337             }
2338
2339             if (params.__get('delimiter',false))
2340             {
2341                 data.smarty.cycle[name].delimiter = params.delimiter;
2342             }
2343             var values = params.__get('values',false);
2344             if (values)
2345             {
2346                 var arr = [];
2347                 if (values instanceof Object)
2348                 {
2349                     for (nm in values)
2350                     {
2351                         arr.push(values[nm]);
2352                     }
2353                 }
2354                 else
2355                 {
2356                     arr = values.split(data.smarty.cycle[name].delimiter);
2357                 }
2358
2359                 if (arr.length != data.smarty.cycle[name].arr.length || arr[0] != data.smarty.cycle[name].arr[0])
2360                 {
2361                     data.smarty.cycle[name].arr = arr;
2362                     data.smarty.cycle[name].index = 0;
2363                     reset = true;
2364                 }
2365             }
2366
2367             if (params.__get('advance','true'))
2368             {
2369                 data.smarty.cycle[name].index += 1;
2370             }
2371             if (data.smarty.cycle[name].index >= data.smarty.cycle[name].arr.length || reset)
2372             {
2373                 data.smarty.cycle[name].index = 0;
2374             }
2375
2376             if (params.__get('assign',false))
2377             {
2378                 assignVar(params.assign, data.smarty.cycle[name].arr[ data.smarty.cycle[name].index ], data);
2379                 return '';
2380             }
2381
2382             if (params.__get('print',true))
2383             {
2384                 return data.smarty.cycle[name].arr[ data.smarty.cycle[name].index ];
2385             }
2386
2387             return '';
2388         }
2389     );
2390
2391     jSmart.prototype.print_r = function(v,indent)
2392     {
2393         if (v instanceof Object)
2394         {
2395             var s = ((v instanceof Array) ? 'Array['+v.length+']' : 'Object') + '<br>';
2396             for (var nm in v)
2397             {
2398                 if (v.hasOwnProperty(nm))
2399                 {
2400                     s += indent + '&nbsp;&nbsp;<strong>' + nm + '</strong> : ' + jSmart.prototype.print_r(v[nm],indent+'&nbsp;&nbsp;&nbsp;') + '<br>';
2401                 }
2402             }
2403             return s;
2404         }
2405         return v;
2406     }
2407
2408     jSmart.prototype.registerPlugin(
2409         'function',
2410         'debug',
2411         function(params, data)
2412         {
2413             if (typeof dbgWnd != 'undefined')
2414             {
2415                 dbgWnd.close();
2416             }
2417             dbgWnd = window.open('','','width=680,height=600,resizable,scrollbars=yes');
2418             var sVars = '';
2419             var i=0;
2420             for (var nm in data)
2421             {
2422                 sVars += '<tr class=' + (++i%2?'odd':'even') + '><td><strong>' + nm + '</strong></td><td>' + jSmart.prototype.print_r(data[nm],'') + '</td></tr>';
2423             }
2424             dbgWnd.document.write(" \
2425                <html xmlns='http://www.w3.org/1999/xhtml' xml:lang='en'> \
2426                <head> \
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;} \
2432                   </style> \
2433                </head> \
2434                <body> \
2435                   <h1>jSmart Debug Console</h1> \
2436                   <h2>assigned template variables</h2> \
2437                   <table>" + sVars + "</table> \
2438                </body> \
2439                </html> \
2440             ");
2441             return '';
2442         }
2443     );
2444
2445     jSmart.prototype.registerPlugin(
2446         'function',
2447         'eval',
2448         function(params, data)
2449         {
2450             var tree = [];
2451             parse(params.__get('var','',0), tree);
2452             var s = process(tree, data);
2453             if ('assign' in params)
2454             {
2455                 assignVar(params.assign, s, data);
2456                 return '';
2457             }
2458             return s;
2459         }
2460     );
2461
2462     jSmart.prototype.registerPlugin(
2463         'function',
2464         'fetch',
2465         function(params, data)
2466         {
2467             var s = jSmart.prototype.getFile(params.__get('file',null,0));
2468             if ('assign' in params)
2469             {
2470                 assignVar(params.assign, s, data);
2471                 return '';
2472             }
2473             return s;
2474         }
2475     );
2476
2477     jSmart.prototype.registerPlugin(
2478         'function',
2479         'html_checkboxes',
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)),
2491                 p,
2492                 res = [],
2493                 i = 0,
2494                 s = '',
2495                 value,
2496                 id;
2497
2498             if (type == 'checkbox') {
2499                 name += '[]';
2500             }
2501             if (!useName) {
2502                 for (p in params.output) {
2503                     output.push(params.output[p]);
2504                 }
2505             }
2506
2507             for (p in values) {
2508                 if (values.hasOwnProperty(p)) {
2509                     value = (useName ? p : values[p]);
2510                     id = realName + '_' + value;
2511                     s = (labels ? ( label_ids ? '<label for="'+id+'">' : '<label>') : '');
2512
2513                     s += '<input type="' + type + '" name="' + name + '" value="' + value + '" ';
2514                     if (label_ids) {
2515                         s += 'id="'+id+'" ';
2516                     }
2517                     if (selected == (useName ? p : values[p])) {
2518                         s += 'checked="checked" ';
2519                     }
2520                     s += '/>' + output[useName?p:i++];
2521                     s += (labels ? '</label>' : '');
2522                     s += separator;
2523                     res.push(s);
2524                 }
2525             }
2526             if ('assign' in params) {
2527                 assignVar(params.assign, res, data);
2528                 return '';
2529             }
2530             return res.join('\n');
2531         }
2532     );
2533
2534     jSmart.prototype.registerPlugin(
2535         'function',
2536         'html_image',
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+'"':''),
2546                 p;
2547
2548             for (p in params) {
2549                 if (params.hasOwnProperty(p) && typeof(params[p]) == 'string') {
2550                     if (!(p in paramNames)) {
2551                         s += ' ' + p + '="' + params[p] + '"';
2552                     }
2553                 }
2554             }
2555             s += ' />';
2556             return href ? '<a href="'+href+'">'+s+'</a>' : s;
2557         }
2558     );
2559
2560     jSmart.prototype.registerPlugin(
2561         'function',
2562         'html_options',
2563         function(params, data)
2564         {
2565             var values = params.__get('values',params.options);
2566             var output = params.__get('options',[]);
2567             var useName = ('options' in params);
2568             var p;
2569             if (!useName)
2570             {
2571                 for (p in params.output)
2572                 {
2573                     output.push(params.output[p]);
2574                 }
2575             }
2576             var selected = params.__get('selected',false);
2577
2578             var res = [];
2579             var s = '';
2580             var i = 0;
2581             for (p in values)
2582             {
2583                 if (values.hasOwnProperty(p))
2584                 {
2585                     s = '<option value="' + (useName ? p : values[p]) + '"';
2586                     if (selected == (useName ? p : values[p]))
2587                     {
2588                         s += ' selected="selected"';
2589                     }
2590                     s += '>' + output[useName ? p : i++] + '</option>';
2591                     res.push(s);
2592                 }
2593             }
2594             var name = params.__get('name',false);
2595             return (name ? ('<select name="' + name + '">\n' + res.join('\n') + '\n</select>') : res.join('\n')) + '\n';
2596         }
2597     );
2598
2599     jSmart.prototype.registerPlugin(
2600         'function',
2601         'html_radios',
2602         function(params, data)
2603         {
2604             params.type = 'radio';
2605             return plugins.html_checkboxes.process(params,data);
2606         }
2607     );
2608
2609     jSmart.prototype.registerPlugin(
2610         'function',
2611         'html_select_date',
2612         function(params, data)
2613         {
2614             var prefix = params.__get('prefix','Date_');
2615             var months = ['January','February','March','April','May','June','July','August','September','October','November','December'];
2616
2617             var s = '';
2618             s += '<select name="'+prefix+'Month">\n';
2619             var i=0;
2620             for (i=0; i<months.length; ++i)
2621             {
2622                 s += '<option value="' + i + '">' + months[i] + '</option>\n';
2623             }
2624             s += '</select>\n'
2625
2626             s += '<select name="'+prefix+'Day">\n';
2627             for (i=0; i<31; ++i)
2628             {
2629                 s += '<option value="' + i + '">' + i + '</option>\n';
2630             }
2631             s += '</select>\n'
2632             return s;
2633         }
2634     );
2635
2636     jSmart.prototype.registerPlugin(
2637         'function',
2638         'html_table',
2639         function(params, data)
2640         {
2641             var loop = [];
2642             var p;
2643             if (params.loop instanceof Array)
2644             {
2645                 loop = params.loop
2646             }
2647             else
2648             {
2649                 for (p in params.loop)
2650                 {
2651                     if (params.loop.hasOwnProperty(p))
2652                     {
2653                         loop.push( params.loop[p] );
2654                     }
2655                 }
2656             }
2657             var rows = params.__get('rows',false);
2658             var cols = params.__get('cols',false);
2659             if (!cols)
2660             {
2661                 cols = rows ? Math.ceil(loop.length/rows) : 3;
2662             }
2663             var colNames = [];
2664             if (isNaN(cols))
2665             {
2666                 if (typeof cols == 'object')
2667                 {
2668                     for (p in cols)
2669                     {
2670                         if (cols.hasOwnProperty(p))
2671                         {
2672                             colNames.push(cols[p]);
2673                         }
2674                     }
2675                 }
2676                 else
2677                 {
2678                     colNames = cols.split(/\s*,\s*/);
2679                 }
2680                 cols = colNames.length;
2681             }
2682             rows = rows ? rows : Math.ceil(loop.length/cols);
2683
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')
2689             {
2690                 th_attr = [th_attr];
2691             }
2692             var tr_attr = params.__get('tr_attr',false);
2693             if (tr_attr && typeof tr_attr != 'object')
2694             {
2695                 tr_attr = [tr_attr];
2696             }
2697             var td_attr = params.__get('td_attr',false);
2698             if (td_attr && typeof td_attr != 'object')
2699             {
2700                 td_attr = [td_attr];
2701             }
2702             var trailpad = params.__get('trailpad','&nbsp;');
2703             var hdir = params.__get('hdir','right');
2704             var vdir = params.__get('vdir','down');
2705
2706             var s = '';
2707             for (var row=0; row<rows; ++row)
2708             {
2709                 s += '<tr' + (tr_attr ? ' '+tr_attr[row%tr_attr.length] : '') + '>\n';
2710                 for (var col=0; col<cols; ++col)
2711                 {
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));
2713
2714                     s += '<td' + (td_attr ? ' '+td_attr[col%td_attr.length] : '') + '>' + (idx < loop.length ? loop[idx] : trailpad) + '</td>\n';
2715                 }
2716                 s += '</tr>\n';
2717             }
2718
2719             var sHead = '';
2720             if (colNames.length)
2721             {
2722                 sHead = '\n<thead><tr>';
2723                 for (var i=0; i<colNames.length; ++i)
2724                 {
2725                     sHead += '\n<th' + (th_attr ? ' '+th_attr[i%th_attr.length] : '') + '>' + colNames[hdir=='right'?i:colNames.length-1-i] + '</th>';
2726                 }
2727                 sHead += '\n</tr></thead>';
2728             }
2729
2730             return '<table ' + table_attr + '>' + (caption?'\n<caption>'+caption+'</caption>':'') + sHead + '\n<tbody>\n' + s + '</tbody>\n</table>\n';
2731         }
2732     );
2733
2734     jSmart.prototype.registerPlugin(
2735         'function',
2736         'include',
2737         function(params, data)
2738         {
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)
2744             {
2745                 assignVar(params.assign, s, data);
2746                 return '';
2747             }
2748             return s;
2749         }
2750     );
2751
2752     jSmart.prototype.registerPlugin(
2753         'function',
2754         'include_javascript',
2755         function(params, data)
2756         {
2757             var file = params.__get('file',null,0);
2758             if (params.__get('once',true) && file in scripts)
2759             {
2760                 return '';
2761             }
2762             scripts[file] = true;
2763             var s = execute(jSmart.prototype.getJavascript(file), {'$this':data});
2764             if ('assign' in params)
2765             {
2766                 assignVar(params.assign, s, data);
2767                 return '';
2768             }
2769             return s;
2770         }
2771     );
2772
2773     jSmart.prototype.registerPlugin(
2774         'function',
2775         'include_php',
2776         function(params, data)
2777         {
2778             return plugins['include_javascript'].process(params,data);
2779         }
2780     );
2781
2782     jSmart.prototype.registerPlugin(
2783         'function',
2784         'insert',
2785         function(params, data)
2786         {
2787             var fparams = {};
2788             for (var nm in params)
2789             {
2790                 if (params.hasOwnProperty(nm) && isNaN(nm) && params[nm] && typeof params[nm] == 'string' && nm != 'name' && nm != 'assign' && nm != 'script')
2791                 {
2792                     fparams[nm] = params[nm];
2793                 }
2794             }
2795             var prefix = 'insert_';
2796             if ('script' in params)
2797             {
2798                 eval(jSmart.prototype.getJavascript(params.script));
2799                 prefix = 'smarty_insert_';
2800             }
2801             var func = eval(prefix+params.__get('name',null,0));
2802             var s = func(fparams, data);
2803             if ('assign' in params)
2804             {
2805                 assignVar(params.assign, s, data);
2806                 return '';
2807             }
2808             return s;
2809         }
2810     );
2811
2812     jSmart.prototype.registerPlugin(
2813         'block',
2814         'javascript',
2815         function(params, content, data, repeat)
2816         {
2817             data['$this'] = data;
2818             execute(content,data);
2819             delete data['$this'];
2820             return '';
2821         }
2822     );
2823
2824     jSmart.prototype.registerPlugin(
2825         'function',
2826         'config_load',
2827         function(params, data)
2828         {
2829             jSmart.prototype.configLoad(jSmart.prototype.getConfig(params.__get('file',null,0)), params.__get('section','',1), data);
2830             return '';
2831         }
2832     );
2833
2834     jSmart.prototype.registerPlugin(
2835         'function',
2836         'mailto',
2837         function(params, data)
2838         {
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','');
2848
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 : '');
2854
2855             s = '<a href="mailto:' + address + '" ' + extra + '>' + text + '</a>';
2856
2857             if (encode == 'javascript')
2858             {
2859                 s = "document.write('" + s + "');";
2860                 var sEncoded = '';
2861                 for (var i=0; i<s.length; ++i)
2862                 {
2863                     sEncoded += '%' + jSmart.prototype.PHPJS('bin2hex','mailto').bin2hex(s.substr(i,1));
2864                 }
2865                 return '<script type="text/javascript">eval(unescape(\'' + sEncoded + "'))</script>";
2866             }
2867             else if (encode == 'javascript_charcode')
2868             {
2869                 var codes = [];
2870                 for (var i=0; i<s.length; ++i)
2871                 {
2872                     codes.push(jSmart.prototype.PHPJS('ord','mailto').ord(s.substr(i,1)));
2873                 }
2874                 return '<script type="text/javascript" language="javascript">\n<!--\n{document.write(String.fromCharCode('
2875                     + codes.join(',') + '))}\n//-->\n</script>\n';
2876             }
2877             else if (encode == 'hex')
2878             {
2879                 if (address.match(/^.+\?.+$/))
2880                 {
2881                     throw new Error('mailto: hex encoding does not work with extra attributes. Try javascript.');
2882                 }
2883                 var aEncoded = '';
2884                 for (var i=0; i<address.length; ++i)
2885                 {
2886                     if (address.substr(i,1).match(/\w/))
2887                     {
2888                         aEncoded += '%' + jSmart.prototype.PHPJS('bin2hex','mailto').bin2hex(address.substr(i,1));
2889                     }
2890                     else
2891                     {
2892                         aEncoded += address.substr(i,1);
2893                     }
2894                 }
2895                 aEncoded = aEncoded.toLowerCase();
2896                 var tEncoded = '';
2897                 for (var i=0; i<text.length; ++i)
2898                 {
2899                     tEncoded += '&#x' + jSmart.prototype.PHPJS('bin2hex','mailto').bin2hex(text.substr(i,1)) + ';';
2900                 }
2901                 tEncoded = tEncoded.toLowerCase();
2902                 return '<a href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;' + aEncoded + '" ' + extra + '>' + tEncoded + '</a>';
2903             }
2904             return s;
2905         }
2906     );
2907
2908     jSmart.prototype.registerPlugin(
2909         'function',
2910         'math',
2911         function(params, data)
2912         {
2913             with (Math)
2914             {
2915                 with (params)
2916                 {
2917                     var res = eval(params.__get('equation',null).replace(/pi\(\s*\)/g,'PI'));
2918                 }
2919             }
2920
2921             if ('format' in params)
2922             {
2923                 res = jSmart.prototype.PHPJS('sprintf','math').sprintf(params.format,res);
2924             }
2925
2926             if ('assign' in params)
2927             {
2928                 assignVar(params.assign, res, data);
2929                 return '';
2930             }
2931             return res;
2932         }
2933     );
2934
2935     jSmart.prototype.registerPlugin(
2936         'block',
2937         'nocache',
2938         function(params, content, data, repeat)
2939         {
2940             return content;
2941         }
2942     );
2943
2944     jSmart.prototype.registerPlugin(
2945         'block',
2946         'textformat',
2947         function(params, content, data, repeat)
2948         {
2949             if (!content) {
2950                 return '';
2951             }
2952
2953             content = new String(content);
2954
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);
2963
2964             var style = params.__get('style','');
2965
2966             if (style == 'email') {
2967                 wrap = 72;
2968             }
2969
2970             var paragraphs = content.split(/[\r\n]{2}/);
2971             for (var i=0; i<paragraphs.length; ++i) {
2972                 var p = paragraphs[i];
2973                 if (!p) {
2974                     continue;
2975                 }
2976                 p = p.replace(/^\s+|\s+$/,'').replace(/\s+/g,' ');
2977                 if (indent_first> 0 ) {
2978                     p = indentFirstStr + p;
2979                 }
2980                 p = modifiers.wordwrap(p, wrap-indent, wrap_char, wrap_cut);
2981                 if (indent > 0) {
2982                     p = p.replace(/^/mg, indentStr);
2983                 }
2984                 paragraphs[i] = p;
2985             }
2986             var s = paragraphs.join(wrap_char+wrap_char);
2987             if ('assign' in params)
2988             {
2989                 assignVar(params.assign, s, data);
2990                 return '';
2991             }
2992             return s;
2993         }
2994     );
2995
2996
2997     /**
2998        register modifiers
2999     */
3000     jSmart.prototype.registerPlugin(
3001         'modifier',
3002         'capitalize',
3003         function(s, upDigits, lcRest) {
3004             if (typeof s != 'string') {
3005                 return s;
3006             }
3007             var re = new RegExp(upDigits ? '[^a-zA-Z_\u00E0-\u00FC]+' : '[^a-zA-Z0-9_\u00E0-\u00FC]');
3008             var found = null;
3009             var res = '';
3010             if (lcRest) {
3011                 s = s.toLowerCase();
3012             }
3013             for (found=s.match(re); found; found=s.match(re))
3014             {
3015                      var word = s.slice(0,found.index);
3016                 if (word.match(/\d/))
3017                 {
3018                     res += word;
3019                 }
3020                 else
3021                 {
3022                          res += word.charAt(0).toUpperCase() + word.slice(1);
3023                 }
3024                 res += s.slice(found.index, found.index+found[0].length);
3025                      s = s.slice(found.index+found[0].length);
3026             }
3027             if (s.match(/\d/))
3028             {
3029                 return res + s;
3030             }
3031             return res + s.charAt(0).toUpperCase() + s.slice(1);
3032         }
3033     );
3034
3035     jSmart.prototype.registerPlugin(
3036         'modifier',
3037         'cat',
3038         function(s, value)
3039         {
3040             value = value ? value : '';
3041             return new String(s) + value;
3042         }
3043     );
3044
3045     jSmart.prototype.registerPlugin(
3046         'modifier',
3047         'count',
3048         function(v, recursive)
3049         {
3050             if (v === null || typeof v === 'undefined') {
3051                 return 0;
3052             } else if (v.constructor !== Array && v.constructor !== Object) {
3053                 return 1;
3054             }
3055
3056             recursive = Boolean(recursive);
3057             var k, cnt = 0;
3058             for (k in v)
3059             {
3060                 if (v.hasOwnProperty(k))
3061                 {
3062                     cnt++;
3063                     if (recursive && v[k] && (v[k].constructor === Array || v[k].constructor === Object)) {
3064                         cnt += modifiers.count(v[k], true);
3065                     }
3066                 }
3067             }
3068             return cnt;
3069         }
3070     );
3071
3072     jSmart.prototype.registerPlugin(
3073         'modifier',
3074         'count_characters',
3075         function(s, includeWhitespaces)
3076         {
3077             s = new String(s);
3078             return includeWhitespaces ? s.length : s.replace(/\s/g,'').length;
3079         }
3080     );
3081
3082     jSmart.prototype.registerPlugin(
3083         'modifier',
3084         'count_paragraphs',
3085         function(s)
3086         {
3087             var found = (new String(s)).match(/\n+/g);
3088             if (found)
3089             {
3090                      return found.length+1;
3091             }
3092             return 1;
3093         }
3094     );
3095
3096     jSmart.prototype.registerPlugin(
3097         'modifier',
3098         'count_sentences',
3099         function(s)
3100         {
3101             if (typeof s == 'string')
3102             {
3103                 var found = s.match(/[^\s]\.(?!\w)/g);
3104                 if (found)
3105                 {
3106                          return found.length;
3107                 }
3108             }
3109             return 0;
3110         }
3111     );
3112
3113     jSmart.prototype.registerPlugin(
3114         'modifier',
3115         'count_words',
3116         function(s)
3117         {
3118             if (typeof s == 'string')
3119             {
3120                 var found = s.match(/\w+/g);
3121                 if (found)
3122                 {
3123                          return found.length;
3124                 }
3125             }
3126             return 0;
3127         }
3128     );
3129
3130     jSmart.prototype.registerPlugin(
3131         'modifier',
3132         'date_format',
3133         function(s, fmt, defaultDate)
3134         {
3135             return jSmart.prototype.PHPJS('strftime','date_format').strftime(fmt?fmt:'%b %e, %Y', jSmart.prototype.makeTimeStamp(s?s:defaultDate));
3136         }
3137     );
3138
3139     jSmart.prototype.registerPlugin(
3140         'modifier',
3141         'defaultValue',
3142         function(s, value)
3143         {
3144             return (s && s!='null' && s!='undefined') ? s : (value ? value : '');
3145         }
3146     );
3147
3148     jSmart.prototype.registerPlugin(
3149         'modifier',
3150         'unescape',
3151         function(s, esc_type, char_set)
3152         {
3153             s = new String(s);
3154             esc_type = esc_type || 'html';
3155             char_set = char_set || 'UTF-8';
3156
3157             switch (esc_type)
3158             {
3159             case 'html':
3160                 return s.replace(/&lt;/g, '<').replace(/&gt;/g,'>').replace(/&#039;/g,"'").replace(/&quot;/g,'"');
3161             case 'entity':
3162             case 'htmlall':
3163                 return jSmart.prototype.PHPJS('html_entity_decode','unescape').html_entity_decode(s, 1);
3164             case 'url':
3165                 return jSmart.prototype.PHPJS('rawurldecode','unescape').rawurldecode(s);
3166             };
3167             return s;
3168         }
3169     );
3170
3171     jSmart.prototype.registerPlugin(
3172         'modifier',
3173         'escape',
3174         function(s, esc_type, char_set, double_encode)
3175         {
3176             s = new String(s);
3177             esc_type = esc_type || 'html';
3178             char_set = char_set || 'UTF-8';
3179             double_encode = (typeof double_encode != 'undefined') ? Boolean(double_encode) : true;
3180
3181             switch (esc_type)
3182             {
3183             case 'html':
3184                 if (double_encode) {
3185                                    s = s.replace(/&/g, '&amp;');
3186                           }
3187                 return s.replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/'/g,'&#039;').replace(/"/g,'&quot;');
3188             case 'htmlall':
3189                 return jSmart.prototype.PHPJS('htmlentities','escape').htmlentities(s, 3, char_set);
3190             case 'url':
3191                 return jSmart.prototype.PHPJS('rawurlencode','escape').rawurlencode(s);
3192             case 'urlpathinfo':
3193                 return jSmart.prototype.PHPJS('rawurlencode','escape').rawurlencode(s).replace(/%2F/g, '/');
3194             case 'quotes':
3195                 return s.replace(/(^|[^\\])'/g, "$1\\'");
3196             case 'hex':
3197                 var res = '';
3198                 for (var i=0; i<s.length; ++i)
3199                 {
3200                     res += '%' + jSmart.prototype.PHPJS('bin2hex','escape').bin2hex(s.substr(i,1)).toLowerCase();
3201                 }
3202                 return res;
3203             case 'hexentity':
3204                 var res = '';
3205                 for (var i=0; i<s.length; ++i) {
3206                     res += '&#x' + jSmart.prototype.PHPJS('bin2hex','escape').bin2hex(s.substr(i,1)) + ';';
3207                 }
3208                 return res;
3209             case 'decentity':
3210                 var res = '';
3211                 for (var i=0; i<s.length; ++i) {
3212                     res += '&#' + jSmart.prototype.PHPJS('ord','escape').ord(s.substr(i,1)) + ';';
3213                 }
3214                 return res;
3215             case 'mail':
3216                 return s.replace(/@/g,' [AT] ').replace(/[.]/g,' [DOT] ');
3217             case 'nonstd':
3218                 var res = '';
3219                 for (var i=0; i<s.length; ++i)
3220                 {
3221                     var _ord = jSmart.prototype.PHPJS('ord','escape').ord(s.substr(i,1));
3222                     if (_ord >= 126) {
3223                         res += '&#' + _ord + ';';
3224                     } else {
3225                         res += s.substr(i, 1);
3226                     }
3227
3228                 }
3229                 return res;
3230             case 'javascript':
3231                 return s.replace(/\\/g,'\\\\').replace(/'/g,"\\'").replace(/"/g,'\\"').replace(/\r/g,'\\r').replace(/\n/g,'\\n').replace(/<\//g,'<\/');
3232             };
3233             return s;
3234         }
3235     );
3236
3237     jSmart.prototype.registerPlugin(
3238         'modifier',
3239         'indent',
3240         function(s, repeat, indentWith)
3241         {
3242             s = new String(s);
3243             repeat = repeat ? repeat : 4;
3244             indentWith = indentWith ? indentWith : ' ';
3245
3246             var indentStr = '';
3247             while (repeat--)
3248             {
3249                 indentStr += indentWith;
3250             }
3251
3252             var tail = s.match(/\n+$/);
3253             return indentStr + s.replace(/\n+$/,'').replace(/\n/g,'\n'+indentStr) + (tail ? tail[0] : '');
3254         }
3255     );
3256
3257     jSmart.prototype.registerPlugin(
3258         'modifier',
3259         'lower',
3260         function(s)
3261         {
3262             return new String(s).toLowerCase();
3263         }
3264     );
3265
3266     jSmart.prototype.registerPlugin(
3267         'modifier',
3268         'nl2br',
3269         function(s)
3270         {
3271             return new String(s).replace(/\n/g,'<br />\n');
3272         }
3273     );
3274
3275     /**
3276         only modifiers (flags) 'i' and 'm' are supported
3277         backslashes should be escaped e.g. \\s
3278     */
3279     jSmart.prototype.registerPlugin(
3280         'modifier',
3281         'regex_replace',
3282         function(s, re, replaceWith)
3283         {
3284             var pattern = re.match(/^ *\/(.*)\/(.*) *$/);
3285             return (new String(s)).replace(new RegExp(pattern[1],'g'+(pattern.length>1?pattern[2]:'')), replaceWith);
3286         }
3287     );
3288
3289     jSmart.prototype.registerPlugin(
3290         'modifier',
3291         'replace',
3292         function(s, search, replaceWith)
3293         {
3294             if (!search)
3295             {
3296                 return s;
3297             }
3298             s = new String(s);
3299             search = new String(search);
3300             replaceWith = new String(replaceWith);
3301             var res = '';
3302             var pos = -1;
3303             for (pos=s.indexOf(search); pos>=0; pos=s.indexOf(search))
3304             {
3305                 res += s.slice(0,pos) + replaceWith;
3306                 pos += search.length;
3307                 s = s.slice(pos);
3308             }
3309             return res + s;
3310         }
3311     );
3312
3313     jSmart.prototype.registerPlugin(
3314         'modifier',
3315         'spacify',
3316         function(s, space)
3317         {
3318             if (!space)
3319             {
3320                 space = ' ';
3321             }
3322             return (new String(s)).replace(/(\n|.)(?!$)/g,'$1'+space);
3323         }
3324     );
3325
3326     jSmart.prototype.registerPlugin(
3327         'modifier',
3328         'noprint',
3329         function(s)
3330         {
3331             return '';
3332         }
3333     );
3334
3335     jSmart.prototype.registerPlugin(
3336         'modifier',
3337         'string_format',
3338         function(s, fmt)
3339         {
3340             return jSmart.prototype.PHPJS('sprintf','string_format').sprintf(fmt,s);
3341         }
3342     );
3343
3344     jSmart.prototype.registerPlugin(
3345         'modifier',
3346         'strip',
3347         function(s, replaceWith)
3348         {
3349             replaceWith = replaceWith ? replaceWith : ' ';
3350             return (new String(s)).replace(/[\s]+/g, replaceWith);
3351         }
3352     );
3353
3354     jSmart.prototype.registerPlugin(
3355         'modifier',
3356         'strip_tags',
3357         function(s, addSpace)
3358         {
3359             addSpace = (addSpace==null) ? true : addSpace;
3360             return (new String(s)).replace(/<[^>]*?>/g, addSpace ? ' ' : '');
3361         }
3362     );
3363
3364     jSmart.prototype.registerPlugin(
3365         'modifier',
3366         'truncate',
3367         function(s, length, etc, breakWords, middle)
3368         {
3369             s = new String(s);
3370             length = length ? length : 80;
3371             etc = (etc!=null) ? etc : '...';
3372
3373             if (s.length <= length)
3374             {
3375                 return s;
3376             }
3377
3378             length -= Math.min(length,etc.length);
3379             if (middle)
3380             {
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));
3383             }
3384
3385             if (!breakWords)
3386             {
3387                 s = s.slice(0,length+1).replace(/\s+?(\S+)?$/,'');
3388             }
3389
3390             return s.slice(0,length) + etc;
3391         }
3392     );
3393
3394     jSmart.prototype.registerPlugin(
3395         'modifier',
3396         'upper',
3397         function(s)
3398         {
3399             return (new String(s)).toUpperCase();
3400         }
3401     );
3402
3403     jSmart.prototype.registerPlugin(
3404         'modifier',
3405         'wordwrap',
3406         function(s, width, wrapWith, breakWords)
3407         {
3408                 width = width || 80;
3409                 wrapWith = wrapWith || '\n';
3410
3411                 var lines = (new String(s)).split('\n');
3412                 for (var i=0; i<lines.length; ++i)
3413                 {
3414                         var line = lines[i];
3415                 var parts = ''
3416                         while (line.length > width)
3417                         {
3418                     var pos = 0;
3419                     var found = line.slice(pos).match(/\s+/);
3420                     for (;found && (pos+found.index)<=width; found=line.slice(pos).match(/\s+/))
3421                     {
3422                         pos += found.index + found[0].length;
3423                     }
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)
3427                     {
3428                         parts += wrapWith;
3429                     }
3430                     line = line.slice(pos);
3431                 }
3432                         lines[i] = parts + line;
3433                 }
3434                 return lines.join('\n');
3435         }
3436     );
3437
3438
3439     String.prototype.fetch = function(data)
3440     {
3441         var tpl = new jSmart(this);
3442         return tpl.fetch(data);
3443     };
3444
3445     if (typeof module === "object" && module && typeof module.exports === "object") {
3446         module.exports = jSmart;
3447     } else {
3448         if (typeof global !== "undefined") {
3449             global.jSmart = jSmart;
3450         }
3451
3452         if (typeof define === "function" && define.amd) {
3453             define("jSmart", [], function () { return jSmart; });
3454         }
3455     }
3456 })();