Fix bug in GListTruncate, extends templates tests
[tedtools.git] / template.c
1 /*
2  * cOpyright (c) 2004 Teodor Sigaev <teodor@sigaev.ru>
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *        notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *        notice, this list of conditions and the following disclaimer in the
12  *        documentation and/or other materials provided with the distribution.
13  * 3. Neither the name of the author nor the names of any co-contributors
14  *        may be used to endorse or promote products derived from this software
15  *        without specific prior written permission.
16  *
17  * THIS SOFTWARE IS PROVIDED BY CONTRIBUTORS ``AS IS'' AND ANY EXPRESS
18  * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20  * ARE DISCLAIMED. IN NO EVENT SHALL CONTRIBUTORS BE LIABLE FOR ANY
21  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
23  * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
25  * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
26  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
27  * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28  */
29
30 #include <sys/types.h>
31 #include <string.h>
32
33 #include "tmalloc.h"
34 #include "tlog.h"
35 #include "template.h"
36
37 #define MAXDEPTH        (128)
38
39 typedef struct ParseState {
40         int                     depth;
41         char            *basename;
42         Template        tmpl;
43 } ParseState;
44
45 static  char *
46 getFilename(ParseState *state, char *filename) {
47         int     len = 1 /* \0 */ + 1 /* / */ + strlen(filename);
48         char    *res;
49
50         if ( *filename == '/' )
51                 return filename;
52
53         if ( state->basename && *state->basename ) 
54                 len += strlen(state->basename);
55
56         res = mcalloc(state->tmpl->templateContext, sizeof(char) * len);
57         len = 0;
58
59         if ( state->basename && *state->basename ) {
60                 len = strlen(state->basename);
61                 memcpy( res, state->basename, len );
62                 res[len++] = '/';
63         }
64
65         memcpy( res+len, filename, strlen(filename));
66         res[ len + strlen(filename) ] = '\0';
67
68         return res;
69 }
70
71 static int recursiveReadTemplate( ParseState *state, Template  tmptmpl, char *filename );
72
73 static int
74 findIncludes(ParseState *state, TemplateNode *node) {
75         TemplateData    tmp;
76         GListCell               *cell;
77
78         tmp.templateContext = state->tmpl->templateContext;
79
80         if (node == NULL || *node == NULL)
81                 return 0;
82
83         switch((*node)->type) {
84                 case    IncludeNode:
85                         tmp.tree = NULL;
86                         if ( recursiveReadTemplate(state, &tmp, (*node)->nodeData.includeFile) != 0 )
87                                 return 1;
88                         *node = tmp.tree;
89                         break;
90                 case    LoopNode:
91                         if ( findIncludes(state, &( (*node)->nodeData.loop.bodyNode )) != 0 )
92                                 return 1;
93                         break;
94                 case    ConditionNode:
95                         if ( findIncludes(state, &( (*node)->nodeData.condition.ifNode )) != 0 ||
96                                         findIncludes(state, &( (*node)->nodeData.condition.elseNode )) != 0 )
97                                 return 1;
98                         break;
99                 case    CollectionNode:
100                         GListForeach(cell, (*node)->nodeData.children) {
101                                 TemplateNode    chld = (TemplateNode)GLCELL_DATA(cell);
102
103                                 if ( findIncludes(state, &chld) != 0 )
104                                         return 1;
105
106                                 GLCELL_DATA(cell) = chld;
107                         }
108                         break;
109                 case    TextNode:
110                 case    VariableNode:
111                         break;
112                 default:
113                         tlog(TL_CRIT|TL_EXIT, "unknown node type: %d", (*node)->type);
114         }
115
116         return 0;
117 }
118
119 static int
120 recursiveReadTemplate( ParseState *state, Template      tmptmpl, char *filename ) {
121         int     err;
122         char    *fn = getFilename(state, filename);
123
124         if ( state->depth > MAXDEPTH ) {
125                 tlog(TL_ALARM, "too many depth of included templates");
126                 return 4;
127         }
128
129         if ( tmptmpl == NULL )
130                 tmptmpl = state->tmpl;
131
132         if ( (err=parseTemplateFile( tmptmpl, fn )) != 0 )
133                 return err;
134
135         state->depth++;
136
137         if ( (err=findIncludes(state, &(tmptmpl->tree))) != 0 )
138                 return err;
139
140         state->depth--;
141
142         return 0;
143 }
144
145 static char*
146 qualifyVarname(Template tmpl, TemplateNode loopParentNode, char *varName, int *varNameLength) {
147         int len;
148         char *tmp;
149
150         if ( ! loopParentNode )
151                 return varName;
152
153         len = loopParentNode->nodeData.loop.varNameLength + *varNameLength + 2;
154         tmp = mcalloc(tmpl->templateContext, len);
155         len = loopParentNode->nodeData.loop.varNameLength;
156         memcpy( tmp, loopParentNode->nodeData.loop.varName, len);
157         tmp[ len++ ] = '.';
158         memcpy( tmp + len, varName, *varNameLength);
159         len+=*varNameLength;
160         tmp[ len ] = '\0';
161
162         *varNameLength = len;
163         return tmp;
164 }
165
166 static int
167 checkSpecialVariable(int flags, char *varName) {
168         if ( strcmp(varName, "__first") == 0 ) {
169                 flags &= ~TND_GLOBAL; /* special vars cannot be global */
170                 flags |= TND___FIRST;
171         } else if ( strcmp(varName, "__last") == 0 ) {
172                 flags &= ~TND_GLOBAL; /* special vars cannot be global */
173                 flags |= TND___LAST;
174         } else if ( strcmp(varName, "__counter") == 0 ) {
175                 flags &= ~TND_GLOBAL; /* special vars cannot be global */
176                 flags |= TND___COUNTER;
177         } else if ( strcmp(varName, "__odd") == 0 ) {
178                 flags &= ~TND_GLOBAL; /* special vars cannot be global */
179                 flags |= TND___ODD;
180         } else if ( strcmp(varName, "__even") == 0 ) {
181                 flags &= ~TND_GLOBAL; /* special vars cannot be global */
182                 flags |= TND___EVEN;
183         } else if ( strcmp(varName, "__size") == 0 ) {
184                 flags &= ~TND_GLOBAL; /* special vars cannot be global */
185                 flags |= TND___SIZE;
186         }
187
188         return flags;
189 }
190
191 static VariableValue
192 findVariable( Template tmpl, TemplateNode loopParentNode, int flags, char *varName, int varNameLength ) {
193         VariableValue   *pvarval;
194
195         if ( (pvarval = SFSFindData(&tmpl->variables, varName, varNameLength)) == NULL ) {
196                 VariableValue           pdata = mc0alloc(tmpl->templateContext, sizeof(VariableValueData));
197                 SFSDataIO                       in;
198
199                 in.key = varName;
200                 in.keylen = varNameLength;
201                 in.data = &pdata;
202
203                 SFSAdd(&tmpl->variables, &in);
204
205                 if ( loopParentNode && (flags & TND_GLOBAL) == 0 ) {
206                         /*
207                          * copy special flags to support new inform loopParentNode
208                          */
209                         pdata->flags |=  flags & TND__SPECIALMASK;
210
211                         if ( flags & ( TND___FIRST | TND___LAST | TND___ODD | TND___EVEN ) )
212                                 pdata->type = valueBool;
213                         else if ( flags & (TND___COUNTER | TND___SIZE) )
214                                 pdata->type = valueInt;
215                         else
216                                 pdata->type = valuePointer;
217
218                         loopParentNode->nodeData.loop.listVarValues = 
219                                         GListPush( loopParentNode->nodeData.loop.listVarValues, pdata ); 
220                 }
221
222                 return pdata;
223         }
224
225         return *pvarval;
226 }
227
228 static int
229 addLoop(Template tmpl, TemplateNode node, TemplateNode loopParentNode) {
230         SFSDataIO               in;
231
232         if ( SFSFindData(&tmpl->variables, node->nodeData.loop.varName, node->nodeData.loop.varNameLength) != NULL ) {
233                 tlog(TL_CRIT,"Loop marked '%s' is already defined", node->nodeData.loop.varName);
234                 return 1;
235         }
236
237         in.key = node->nodeData.loop.varName;
238         in.keylen = node->nodeData.loop.varNameLength;
239         in.data = &node;
240
241         SFSAdd(&tmpl->variables, &in);
242
243         if ( loopParentNode ) 
244                 loopParentNode->nodeData.loop.childrenLoop = 
245                         GListPush( loopParentNode->nodeData.loop.childrenLoop, node );
246
247         return 0;
248 }
249
250 static int 
251 compileTree( Template tmpl, TemplateNode node, TemplateNode loopParentNode ) {
252         GListCell               *cell;
253
254         if ( !node )
255                 return 0;
256
257         switch(node->type) {
258                 case LoopNode:
259                         node->nodeData.loop.varName = qualifyVarname(tmpl, loopParentNode, 
260                                                                                         node->nodeData.loop.varName,
261                                                                                         &node->nodeData.loop.varNameLength);
262                         if ( compileTree(tmpl, node->nodeData.loop.bodyNode, node) )
263                                         return 1;
264
265                         if ( addLoop( tmpl, node, loopParentNode ) )
266                                 return 1;
267                         break;
268                 case ConditionNode:
269                         node->nodeData.condition.flags = checkSpecialVariable(
270                                                                                                 node->nodeData.condition.flags,
271                                                                                                 node->nodeData.condition.varName );
272
273                         if ( (node->nodeData.condition.flags & TND_GLOBAL) == 0 )
274                                 node->nodeData.condition.varName = qualifyVarname(tmpl, loopParentNode,
275                                                                                                         node->nodeData.condition.varName,
276                                                                                                         &node->nodeData.condition.varNameLength);
277
278                         node->nodeData.condition.value = findVariable( tmpl, loopParentNode,
279                                                                                                 node->nodeData.condition.flags,
280                                                                                                 node->nodeData.condition.varName, 
281                                                                                                 node->nodeData.condition.varNameLength );
282
283                         if ( compileTree(tmpl, node->nodeData.condition.ifNode, loopParentNode) )
284                                         return 1;
285                         if ( compileTree(tmpl, node->nodeData.condition.elseNode, loopParentNode) )
286                                         return 1;
287                         break;
288                 case CollectionNode:
289                         GListForeach(cell, node->nodeData.children) {
290                                 TemplateNode    chld = (TemplateNode)GLCELL_DATA(cell);
291
292                                 if ( compileTree(tmpl, chld, loopParentNode) )
293                                         return 1;
294                         }
295                         break;
296                 case VariableNode:
297                         node->nodeData.variable.flags = checkSpecialVariable(
298                                                                                                 node->nodeData.variable.flags,
299                                                                                                 node->nodeData.variable.varName );
300
301                         if ( (node->nodeData.variable.flags & TND_GLOBAL) == 0 )
302                                 node->nodeData.variable.varName = qualifyVarname(tmpl, loopParentNode,
303                                                                                                         node->nodeData.variable.varName,
304                                                                                                         &node->nodeData.variable.varNameLength);
305
306                         node->nodeData.variable.value = findVariable( tmpl, loopParentNode,
307                                                                                                 node->nodeData.variable.flags,
308                                                                                                 node->nodeData.variable.varName, 
309                                                                                                 node->nodeData.variable.varNameLength );
310                         break;
311                 case IncludeNode:
312                         tlog(TL_CRIT|TL_EXIT, "unexpected IncludeNode");
313                         break;
314                 case TextNode:
315                         break;
316                 default:
317                         tlog(TL_CRIT|TL_EXIT, "unknown node type: %d", node->type);
318         }
319         
320         return 0;
321 }
322
323 int
324 initTemplate( Template tmpl, MemoryContext *mc, char *basedir, char *filename ) {
325         ParseState      state;
326         int err;
327
328         state.depth = 0;
329         state.tmpl = tmpl;
330         state.basename = basedir;
331
332         memset(tmpl, 0, sizeof(TemplateData));
333         tmpl->templateContext = mc;
334         SFSInit_dp(&tmpl->variables, sizeof(void*), NULL);
335
336         if ( (err=recursiveReadTemplate(&state, NULL, filename))!=0 )
337                 return err;
338         if ( (err=compileTree( tmpl, tmpl->tree, NULL ))!=0 )
339                 return err;
340
341         return 0;
342 }
343
344 void
345 resetTemplate( Template tmpl ) {
346         SFSDataIO       out;
347         GListCell       *cell;
348
349         SFSIteratorStart( &tmpl->variables );
350
351         while( SFSIterate( &tmpl->variables, &out ) ) {
352                 VariableValue   varval = *(VariableValue*) out.data;
353
354                 if ( varval->type >= valueInt ) 
355                         varval->flags &= ~TND_DEFINED;
356                 else if ( varval->type == LoopNode ) {
357                         TemplateNode    node = (TemplateNode) varval; 
358
359                         GListForeach( cell, node->nodeData.loop.listVarValues ) {
360                                 varval = (VariableValue) GLCELL_DATA(cell);
361
362                                 if ( varval->type == valuePointer )
363                                         varval->value.ptrValue = NULL;
364                         }
365
366                         GListForeach( cell, node->nodeData.loop.listInstance ) {
367                                 LoopInstance instance = GLCELL_DATA(cell);
368
369                                 GListFree(instance->rowValues );
370                         }
371                         GListTruncate( node->nodeData.loop.listInstance, 0 ); 
372                 }
373         }
374 }
375
376 static void
377 freeNode( TemplateNode node ) {
378         GListCell   *cell;
379
380         if (!node)
381                 return;
382
383         switch (node->type) {
384                 case    LoopNode:
385                         freeNode( node->nodeData.loop.bodyNode );
386                         GListFree( node->nodeData.loop.childrenLoop );
387                         GListFree( node->nodeData.loop.listVarValues );
388
389                         GListForeach( cell, node->nodeData.loop.listInstance ) {
390                                 LoopInstance instance = GLCELL_DATA(cell);
391
392                                 GListFree(instance->rowValues );
393                         }
394                         GListFree( node->nodeData.loop.listInstance );
395                         break;
396                 case    CollectionNode:
397                         GListForeach( cell, node->nodeData.children ) 
398                                 freeNode( (TemplateNode)GLCELL_DATA(cell) );
399                         GListFree( node->nodeData.children );
400                         break;
401                 case    ConditionNode:
402                         freeNode( node->nodeData.condition.ifNode );
403                         freeNode( node->nodeData.condition.elseNode );
404                         break;
405                 case    IncludeNode:
406                 case    VariableNode:
407                 case    TextNode:
408                 default:
409                         break;
410         }
411 }
412
413 void
414 freeTemplate( Template tmpl ) {
415         SFSFree( &tmpl->variables, NULL );
416         freeNode( tmpl->tree );
417 }
418
419 static void
420 newLoopInstance( Template tmpl, TemplateNode node ) {
421         node->nodeData.loop.listInstance = GListPush( 
422                                 node->nodeData.loop.listInstance,
423                                 mc0alloc(tmpl->templateContext, sizeof(LoopInstanceData) ) ); 
424 }
425
426 int
427 addTemplateRow( Template tmpl, char * key ) {
428         TemplateNode    *pnode, node;
429         char            *lkey = strlower(mcstrdup(tmpl->templateContext, key));
430         GListCell               *cell;
431         VariableValue   varvals;
432         int                             i=0, nvar;
433         LoopInstance    instance;
434
435         pnode = SFSFindData(&tmpl->variables, lkey, 0);
436         mcfree(lkey);
437
438         if ( pnode == NULL )
439                 return TVAR_NOTFOUND;
440
441         node = *pnode;
442
443         if ( node->type != LoopNode )
444                 return TVAR_FORBIDDEN;
445
446         nvar = GLIST_LENGTH( node->nodeData.loop.listVarValues );
447         if ( nvar == 0 )
448                 /* loop without vars can not be looped */
449                 return TVAR_NOROW;
450
451         if ( GLIST_LENGTH(node->nodeData.loop.listInstance) == 0 )
452                 newLoopInstance(tmpl, node);
453
454         GListForeach( cell, node->nodeData.loop.childrenLoop )
455                 newLoopInstance( tmpl, GLCELL_DATA(cell) );
456
457         varvals = mcalloc( tmpl->templateContext, sizeof(VariableValueData) * nvar );
458         GListForeach( cell, node->nodeData.loop.listVarValues ) {       
459                 VariableValue   vv = GLCELL_DATA(cell);
460
461                 vv->value.ptrValue = varvals + i;
462                 varvals[i].flags = 0;
463                 i++;
464         }
465
466         instance = GLCELL_DATA(GLIST_TAIL(node->nodeData.loop.listInstance));
467
468         instance->nrow++;
469         instance->rowValues = GListPush( instance->rowValues, varvals );
470
471         return TVAR_OK;
472 }
473
474 static VariableValueData storage;
475         
476 static int
477 setTemplateValue( Template tmpl, char *key) {
478         VariableValue   *pvarval, varval;
479         char                    *lkey = strlower(mcstrdup(tmpl->templateContext, key));
480         
481         pvarval = SFSFindData(&tmpl->variables, lkey, 0);
482         mcfree(lkey);
483
484         if ( pvarval == NULL )
485                 return TVAR_NOTFOUND;
486
487         varval = *pvarval;
488
489         if ( varval->type != 0 && varval->type < valueInt ) {
490                 return TVAR_LOOPMARK;
491         } else if ( varval->type == valuePointer ) {
492                 /* looped variable */
493                 varval = varval->value.ptrValue;
494         
495                 if ( varval == NULL )
496                         return TVAR_NOROW;
497
498                 tassert( (varval->type & TND_GLOBAL) == 0 );
499         } 
500
501         if ( varval->flags & TND__SPECIALMASK )
502                 return TVAR_FORBIDDEN;
503
504         if ( storage.flags & TND_DEFINED ) {
505                 varval->flags |= TND_DEFINED;
506                 varval->type = storage.type;
507                 varval->value = storage.value;
508         } else {
509                 varval->flags &= ~TND_DEFINED;
510         }
511
512         return TVAR_OK;
513 }
514
515
516 int
517 setTemplateValueInt( Template tmpl, char * key, int val ) {
518         storage.flags = TND_DEFINED;
519         storage.type = valueInt;
520         storage.value.intValue = val;
521         return setTemplateValue( tmpl, key );
522 }
523
524 int
525 setTemplateValueString( Template tmpl, char * key, char * val ) {
526         storage.flags = TND_DEFINED;
527         storage.type = valueString;
528         storage.value.stringValue = val;
529         return setTemplateValue( tmpl, key );
530 }
531
532 int
533 setTemplateValueTime( Template tmpl, char * key, time_t val ) {
534         storage.flags = TND_DEFINED;
535         storage.type = valueTime;
536         storage.value.timeValue = val;
537         return setTemplateValue( tmpl, key );
538 }
539
540 int
541 setTemplateValueBool( Template tmpl, char * key, int val ) {
542         storage.flags = TND_DEFINED;
543         storage.type = valueBool;
544         storage.value.boolValue = val;
545         return setTemplateValue( tmpl, key );
546 }
547
548 int
549 setTemplateValueDouble( Template tmpl, char * key, double val ) {
550         storage.flags = TND_DEFINED;
551         storage.type = valueDouble;
552         storage.value.boolValue = val;
553         return setTemplateValue( tmpl, key );
554 }
555
556 int
557 setTemplateValueUndefined( Template tmpl, char * key ) {
558         storage.flags = 0;
559         return setTemplateValue( tmpl, key );
560 }
561
562 static char *
563 printVal( Template tmpl, VariableValue value, int *len, char *format ) {
564         int             printedlen;
565         char    *res;
566
567         *len = 0;
568
569         if ( value->type == valueTime ) {
570                 printedlen = 64;
571                 res = mcalloc( tmpl->templateContext, printedlen+1 );
572
573                 while ( (printedlen = strftime(NULL, 0, (format) ? format : "%Y-%m-%d %H:%M:%S", 
574                                                                                                 localtime(&value->value.timeValue))) == 0 ) {
575                         printedlen *= 2;
576                         res=mcrealloc(res, printedlen);
577                 }
578
579                 *len = printedlen;
580                 return res;
581         }
582
583         switch (value->type) {
584                 case valueInt:
585                         printedlen = snprintf(NULL, 0, (format) ? format : "%d", value->value.intValue);
586                         break;
587                 case valueString:
588                         if ( value->value.stringValue == NULL || *value->value.stringValue == '\0' )
589                                 return NULL;
590                         printedlen = snprintf(NULL, 0, (format) ? format : "%s", value->value.stringValue);
591                         break;
592                 case valueBool:
593                         printedlen = snprintf(NULL, 0, "%s", (value->value.boolValue) ? "true" : "false" );
594                         break;
595                 case valueDouble:
596                         printedlen = snprintf(NULL, 0, (format) ? format : "%f", value->value.doubleValue);
597                         break;
598                 case valuePointer:
599                 case valueTime:
600                 default:
601                         return NULL;
602         }
603
604         res = mcalloc( tmpl->templateContext, printedlen+1 );
605
606         switch (value->type) {
607                 case valueInt:
608                         printedlen = snprintf(res, printedlen+1, (format) ? format : "%d", value->value.intValue);
609                         break;
610                 case valueString:
611                         printedlen = snprintf(res, printedlen+1, (format) ? format : "%s", value->value.stringValue);
612                         break;
613                 case valueBool:
614                         printedlen = snprintf(res, printedlen+1, "%s", (value->value.boolValue) ? "true" : "false" );
615                         break;
616                 case valueDouble:
617                         printedlen = snprintf(res, printedlen+1, (format) ? format : "%f", value->value.doubleValue);
618                         break;
619                 case valuePointer:
620                 case valueTime:
621                 default:
622                         return NULL;
623         }
624
625         *len = printedlen;
626
627         return res;
628 }
629
630 static int
631 isVariable(VariableValue value, int flags) {
632         if ( value == NULL )
633                 return 0;
634
635         if ( flags & TND_DEFINED ) {
636                 return (value->flags & TND_DEFINED);
637         } else {
638                 switch (value->type) {
639                         case valueInt:
640                                 return value->value.intValue;
641                         case valueString:
642                                 if ( value->value.stringValue == NULL || *value->value.stringValue == '\0' )
643                                         return 0;
644                                 return 1;
645                         case valueTime:
646                                 return ( value->value.timeValue > 0 ) ? 1 : 0;
647                         case valueBool:
648                                 return value->value.boolValue;
649                         case valueDouble:
650                                 return (value->value.doubleValue == 0) ? 0 : 1;
651                         case valuePointer:
652                         default:
653                                 return 0;
654                 }
655         }
656
657         return 0;
658 }
659
660 static void
661 printNode( Template tmpl, TemplateNode node ) {
662         GListCell   *cell;
663         VariableValue   value;
664
665         if (!node)
666                 return;
667
668         switch (node->type) {
669                 case    LoopNode:
670                         {
671                                 GListCell               *cell = GListShift( node->nodeData.loop.listInstance );
672                                 LoopInstance    instance;
673                                 int                             i;
674
675                                 if ( cell == NULL )
676                                         return;
677
678                                 instance = GLCELL_DATA(cell);
679                                 GListFreeCell( node->nodeData.loop.listInstance, cell );
680
681                                 for(i=0; i<instance->nrow;i++) {
682                                         VariableValue   varvals;
683                                         int                             j = 0;
684
685                                         cell = GListShift( instance->rowValues );
686                                         varvals = GLCELL_DATA(cell);
687                                         GListFreeCell( instance->rowValues, cell );
688
689                                         GListForeach( cell, node->nodeData.loop.listVarValues ) {
690                                                 value = (VariableValue)GLCELL_DATA(cell);
691                                 
692                                                 if ( value->flags & TND___FIRST ) {
693                                                         if ( i==0 ) {
694                                                                 value->flags |= TND_DEFINED;
695                                                                 value->value.intValue = 1; 
696                                                         }  else {
697                                                                 value->flags &= ~TND_DEFINED;
698                                                                 value->value.intValue = 0;
699                                                         }
700                                                 } else if ( value->flags & TND___LAST ) {
701                                                         if ( i==instance->nrow - 1 ) {
702                                                                 value->flags |= TND_DEFINED;
703                                                                 value->value.intValue = 1; 
704                                                         }  else {
705                                                                 value->flags &= ~TND_DEFINED;
706                                                                 value->value.intValue = 0;
707                                                         }
708                                                 } else if ( value->flags & TND___COUNTER ) {
709                                                         value->flags |= TND_DEFINED;
710                                                         value->value.intValue = i+1;
711                                                 } else if ( value->flags & TND___SIZE ) {
712                                                         value->flags |= TND_DEFINED;
713                                                         value->value.intValue = instance->nrow;
714                                                 } else if ( value->flags & TND___ODD ) {
715                                                         value->flags |= TND_DEFINED;
716                                                         value->value.boolValue = (i%2) ? 0 : 1 ;
717                                                 } else if ( value->flags & TND___EVEN ) {
718                                                         value->flags |= TND_DEFINED;
719                                                         value->value.boolValue = (i%2) ? 1 : 0 ;
720                                                 } else {
721                                                         tassert( value->type == valuePointer );
722                                                         value->value.ptrValue = varvals+j;
723                                                 }
724
725                                                 j++;
726                                         }
727                                         printNode( tmpl, node->nodeData.loop.bodyNode );
728                                 }
729                         }
730                         break;
731                 case    ConditionNode:
732                         value = node->nodeData.condition.value;
733                         if ( value->type == valuePointer )
734                                 value = value->value.ptrValue;
735
736                         if ( node->nodeData.condition.flags & TND_NOT ) { 
737                                 if ( isVariable(value, node->nodeData.condition.flags) ) 
738                                         printNode( tmpl, node->nodeData.condition.elseNode );
739                                 else
740                                         printNode( tmpl, node->nodeData.condition.ifNode );
741                         } else {
742                                 if ( isVariable(value, node->nodeData.condition.flags) ) 
743                                         printNode( tmpl, node->nodeData.condition.ifNode );
744                                 else
745                                         printNode( tmpl, node->nodeData.condition.elseNode );
746                         }
747                         break;
748                 case    CollectionNode:
749                         GListForeach( cell, node->nodeData.children ) 
750                                 printNode( tmpl, (TemplateNode)GLCELL_DATA(cell) );
751                         break;
752                 case    VariableNode:
753                         value = node->nodeData.variable.value;
754                         if ( value->type == valuePointer )
755                                 value = value->value.ptrValue;
756
757                         if ( value && (value->flags & TND_DEFINED) != 0 ) {
758                                 int len;
759                                 char *res;
760
761                                 res = printVal(tmpl, value, &len, node->nodeData.variable.formatValue);
762
763                                 if ( (node->nodeData.variable.flags & TND_HTMLESCAPE) && tmpl->htmlEscape )
764                                         res = tmpl->htmlEscape(res, &len);
765                                 if ( (node->nodeData.variable.flags & TND_URLESCAPE) && tmpl->urlEscape )
766                                         res = tmpl->urlEscape(res, &len);
767
768                                 if ( res && len>0  ) {
769                                         tmpl->printString( res, len );
770                                         mcfree(res);
771                                 }
772                         } else if ( node->nodeData.variable.defaultValue ) {
773                                 tmpl->printString( node->nodeData.variable.defaultValue,
774                                                                         strlen( node->nodeData.variable.defaultValue ) );
775                         }
776                         break;
777                 case    TextNode:
778                         tmpl->printString( node->nodeData.text.value,  node->nodeData.text.valueLength );
779                         break;
780                 case    IncludeNode:
781                 default:
782                         break;
783         }
784 }
785
786 int
787 printTemplate( Template tmpl ) {
788         if (!tmpl->printString)
789                 return 1;
790
791         printNode(tmpl, tmpl->tree);
792
793         return 0;
794 }
795
796 static void
797 recursiveDump(Template tmpl,  TemplateNode node, int level) {
798         GListCell               *cell;
799
800         if (node == NULL ) {
801                 printf("%d void\n", level);
802                 return;
803         }
804
805         switch(node->type) {
806                 case    IncludeNode:
807                         printf("%d IncludeNode\n", level);
808                         break;
809                 case    LoopNode:
810                         printf("%d LoopNode\n", level);
811                         recursiveDump(tmpl, node->nodeData.loop.bodyNode, level+1);
812                         break;
813                 case    ConditionNode:
814                         printf("%d ConditionNode\n", level);
815                         recursiveDump(tmpl, node->nodeData.condition.ifNode, level+1);
816                         recursiveDump(tmpl, node->nodeData.condition.elseNode, level+1);
817                         break;
818                 case    CollectionNode:
819                         printf("%d CollectionNode\n", level);
820                         GListForeach(cell, node->nodeData.children) 
821                                 recursiveDump(tmpl, (TemplateNode)GLCELL_DATA(cell), level+1);
822                         break;
823                 case    TextNode:
824                         printf("%d TextNode len:%d\n", level, node->nodeData.text.valueLength);
825                         break;
826                 case    VariableNode:
827                         printf("%d VariableNode '%s'\n", level, node->nodeData.variable.varName);
828                         break;
829                 default:
830                         tlog(TL_CRIT|TL_EXIT, "unknown node type: %d", node->type);
831         }
832 }
833
834 void
835 dumpTemplate( Template tmpl ) {
836         recursiveDump(tmpl, tmpl->tree, 0);
837 }