2 * cOpyright (c) 2004 Teodor Sigaev <teodor@sigaev.ru>
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
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.
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.
30 #include <sys/types.h>
37 #define MAXDEPTH (128)
39 typedef struct ParseState {
46 getFilename(ParseState *state, char *filename) {
47 int len = 1 /* \0 */ + 1 /* / */ + strlen(filename);
50 if ( *filename == '/' )
53 if ( state->basename && *state->basename )
54 len += strlen(state->basename);
56 res = mcalloc(state->tmpl->templateContext, sizeof(char) * len);
59 if ( state->basename && *state->basename ) {
60 len = strlen(state->basename);
61 memcpy( res, state->basename, len );
65 memcpy( res+len, filename, strlen(filename));
66 res[ len + strlen(filename) ] = '\0';
71 static int recursiveReadTemplate( ParseState *state, Template tmptmpl, char *filename );
74 findIncludes(ParseState *state, TemplateNode *node) {
78 tmp.templateContext = state->tmpl->templateContext;
80 if (node == NULL || *node == NULL)
83 switch((*node)->type) {
86 if ( recursiveReadTemplate(state, &tmp, (*node)->nodeData.includeFile) != 0 )
91 if ( findIncludes(state, &( (*node)->nodeData.loop.bodyNode )) != 0 )
95 if ( findIncludes(state, &( (*node)->nodeData.condition.ifNode )) != 0 ||
96 findIncludes(state, &( (*node)->nodeData.condition.elseNode )) != 0 )
100 GListForeach(cell, (*node)->nodeData.children) {
101 TemplateNode chld = (TemplateNode)GLCELL_DATA(cell);
103 if ( findIncludes(state, &chld) != 0 )
106 GLCELL_DATA(cell) = chld;
113 tlog(TL_CRIT|TL_EXIT, "unknown node type: %d", (*node)->type);
120 recursiveReadTemplate( ParseState *state, Template tmptmpl, char *filename ) {
122 char *fn = getFilename(state, filename);
124 if ( state->depth > MAXDEPTH ) {
125 tlog(TL_ALARM, "too many depth of included templates");
129 if ( tmptmpl == NULL )
130 tmptmpl = state->tmpl;
132 if ( (err=parseTemplateFile( tmptmpl, fn )) != 0 )
137 if ( (err=findIncludes(state, &(tmptmpl->tree))) != 0 )
146 qualifyVarname(Template tmpl, TemplateNode loopParentNode, char *varName, int *varNameLength) {
150 if ( ! loopParentNode )
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);
158 memcpy( tmp + len, varName, *varNameLength);
162 *varNameLength = len;
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 */
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 */
180 } else if ( strcmp(varName, "__even") == 0 ) {
181 flags &= ~TND_GLOBAL; /* special vars cannot be global */
183 } else if ( strcmp(varName, "__size") == 0 ) {
184 flags &= ~TND_GLOBAL; /* special vars cannot be global */
192 findVariable( Template tmpl, TemplateNode loopParentNode, int flags, char *varName, int varNameLength ) {
193 VariableValue *pvarval;
195 if ( (pvarval = SFSFindData(&tmpl->variables, varName, varNameLength)) == NULL ) {
196 VariableValue pdata = mc0alloc(tmpl->templateContext, sizeof(VariableValueData));
200 in.keylen = varNameLength;
203 SFSAdd(&tmpl->variables, &in);
205 if ( loopParentNode && (flags & TND_GLOBAL) == 0 ) {
207 * copy special flags to support new inform loopParentNode
209 pdata->flags |= flags & TND__SPECIALMASK;
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;
216 pdata->type = valuePointer;
218 loopParentNode->nodeData.loop.listVarValues =
219 GListPush( loopParentNode->nodeData.loop.listVarValues, pdata );
229 addLoop(Template tmpl, TemplateNode node, TemplateNode loopParentNode) {
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);
237 in.key = node->nodeData.loop.varName;
238 in.keylen = node->nodeData.loop.varNameLength;
241 SFSAdd(&tmpl->variables, &in);
243 if ( loopParentNode )
244 loopParentNode->nodeData.loop.childrenLoop =
245 GListPush( loopParentNode->nodeData.loop.childrenLoop, node );
251 compileTree( Template tmpl, TemplateNode node, TemplateNode loopParentNode ) {
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) )
265 if ( addLoop( tmpl, node, loopParentNode ) )
269 node->nodeData.condition.flags = checkSpecialVariable(
270 node->nodeData.condition.flags,
271 node->nodeData.condition.varName );
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);
278 node->nodeData.condition.value = findVariable( tmpl, loopParentNode,
279 node->nodeData.condition.flags,
280 node->nodeData.condition.varName,
281 node->nodeData.condition.varNameLength );
283 if ( compileTree(tmpl, node->nodeData.condition.ifNode, loopParentNode) )
285 if ( compileTree(tmpl, node->nodeData.condition.elseNode, loopParentNode) )
289 GListForeach(cell, node->nodeData.children) {
290 TemplateNode chld = (TemplateNode)GLCELL_DATA(cell);
292 if ( compileTree(tmpl, chld, loopParentNode) )
297 node->nodeData.variable.flags = checkSpecialVariable(
298 node->nodeData.variable.flags,
299 node->nodeData.variable.varName );
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);
306 node->nodeData.variable.value = findVariable( tmpl, loopParentNode,
307 node->nodeData.variable.flags,
308 node->nodeData.variable.varName,
309 node->nodeData.variable.varNameLength );
312 tlog(TL_CRIT|TL_EXIT, "unexpected IncludeNode");
317 tlog(TL_CRIT|TL_EXIT, "unknown node type: %d", node->type);
324 initTemplate( Template tmpl, MemoryContext *mc, char *basedir, char *filename ) {
330 state.basename = basedir;
332 memset(tmpl, 0, sizeof(TemplateData));
333 tmpl->templateContext = mc;
334 SFSInit_dp(&tmpl->variables, sizeof(void*), NULL);
336 if ( (err=recursiveReadTemplate(&state, NULL, filename))!=0 )
338 if ( (err=compileTree( tmpl, tmpl->tree, NULL ))!=0 )
345 resetTemplate( Template tmpl ) {
349 SFSIteratorStart( &tmpl->variables );
351 while( SFSIterate( &tmpl->variables, &out ) ) {
352 VariableValue varval = *(VariableValue*) out.data;
354 if ( varval->type >= valueInt )
355 varval->flags &= ~TND_DEFINED;
356 else if ( varval->type == LoopNode ) {
357 TemplateNode node = (TemplateNode) varval;
359 GListForeach( cell, node->nodeData.loop.listVarValues ) {
360 varval = (VariableValue) GLCELL_DATA(cell);
362 if ( varval->type == valuePointer )
363 varval->value.ptrValue = NULL;
366 GListForeach( cell, node->nodeData.loop.listInstance ) {
367 LoopInstance instance = GLCELL_DATA(cell);
369 GListFree(instance->rowValues );
371 GListTruncate( node->nodeData.loop.listInstance, 0 );
377 freeNode( TemplateNode node ) {
383 switch (node->type) {
385 freeNode( node->nodeData.loop.bodyNode );
386 GListFree( node->nodeData.loop.childrenLoop );
387 GListFree( node->nodeData.loop.listVarValues );
389 GListForeach( cell, node->nodeData.loop.listInstance ) {
390 LoopInstance instance = GLCELL_DATA(cell);
392 GListFree(instance->rowValues );
394 GListFree( node->nodeData.loop.listInstance );
397 GListForeach( cell, node->nodeData.children )
398 freeNode( (TemplateNode)GLCELL_DATA(cell) );
399 GListFree( node->nodeData.children );
402 freeNode( node->nodeData.condition.ifNode );
403 freeNode( node->nodeData.condition.elseNode );
414 freeTemplate( Template tmpl ) {
415 SFSFree( &tmpl->variables, NULL );
416 freeNode( tmpl->tree );
420 newLoopInstance( Template tmpl, TemplateNode node ) {
421 node->nodeData.loop.listInstance = GListPush(
422 node->nodeData.loop.listInstance,
423 mc0alloc(tmpl->templateContext, sizeof(LoopInstanceData) ) );
427 addTemplateRow( Template tmpl, char * key ) {
428 TemplateNode *pnode, node;
429 char *lkey = strlower(mcstrdup(tmpl->templateContext, key));
431 VariableValue varvals;
433 LoopInstance instance;
435 pnode = SFSFindData(&tmpl->variables, lkey, 0);
439 return TVAR_NOTFOUND;
443 if ( node->type != LoopNode )
444 return TVAR_FORBIDDEN;
446 nvar = GLIST_LENGTH( node->nodeData.loop.listVarValues );
448 /* loop without vars can not be looped */
451 if ( GLIST_LENGTH(node->nodeData.loop.listInstance) == 0 )
452 newLoopInstance(tmpl, node);
454 GListForeach( cell, node->nodeData.loop.childrenLoop )
455 newLoopInstance( tmpl, GLCELL_DATA(cell) );
457 varvals = mcalloc( tmpl->templateContext, sizeof(VariableValueData) * nvar );
458 GListForeach( cell, node->nodeData.loop.listVarValues ) {
459 VariableValue vv = GLCELL_DATA(cell);
461 vv->value.ptrValue = varvals + i;
462 varvals[i].flags = 0;
466 instance = GLCELL_DATA(GLIST_TAIL(node->nodeData.loop.listInstance));
469 instance->rowValues = GListPush( instance->rowValues, varvals );
474 static VariableValueData storage;
477 setTemplateValue( Template tmpl, char *key) {
478 VariableValue *pvarval, varval;
479 char *lkey = strlower(mcstrdup(tmpl->templateContext, key));
481 pvarval = SFSFindData(&tmpl->variables, lkey, 0);
484 if ( pvarval == NULL )
485 return TVAR_NOTFOUND;
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;
495 if ( varval == NULL )
498 tassert( (varval->type & TND_GLOBAL) == 0 );
501 if ( varval->flags & TND__SPECIALMASK )
502 return TVAR_FORBIDDEN;
504 if ( storage.flags & TND_DEFINED ) {
505 varval->flags |= TND_DEFINED;
506 varval->type = storage.type;
507 varval->value = storage.value;
509 varval->flags &= ~TND_DEFINED;
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 );
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 );
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 );
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 );
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 );
557 setTemplateValueUndefined( Template tmpl, char * key ) {
559 return setTemplateValue( tmpl, key );
563 printVal( Template tmpl, VariableValue value, int *len, char *format ) {
569 if ( value->type == valueTime ) {
571 res = mcalloc( tmpl->templateContext, printedlen+1 );
573 while ( (printedlen = strftime(NULL, 0, (format) ? format : "%Y-%m-%d %H:%M:%S",
574 localtime(&value->value.timeValue))) == 0 ) {
576 res=mcrealloc(res, printedlen);
583 switch (value->type) {
585 printedlen = snprintf(NULL, 0, (format) ? format : "%d", value->value.intValue);
588 if ( value->value.stringValue == NULL || *value->value.stringValue == '\0' )
590 printedlen = snprintf(NULL, 0, (format) ? format : "%s", value->value.stringValue);
593 printedlen = snprintf(NULL, 0, "%s", (value->value.boolValue) ? "true" : "false" );
596 printedlen = snprintf(NULL, 0, (format) ? format : "%f", value->value.doubleValue);
604 res = mcalloc( tmpl->templateContext, printedlen+1 );
606 switch (value->type) {
608 printedlen = snprintf(res, printedlen+1, (format) ? format : "%d", value->value.intValue);
611 printedlen = snprintf(res, printedlen+1, (format) ? format : "%s", value->value.stringValue);
614 printedlen = snprintf(res, printedlen+1, "%s", (value->value.boolValue) ? "true" : "false" );
617 printedlen = snprintf(res, printedlen+1, (format) ? format : "%f", value->value.doubleValue);
631 isVariable(VariableValue value, int flags) {
635 if ( flags & TND_DEFINED ) {
636 return (value->flags & TND_DEFINED);
638 switch (value->type) {
640 return value->value.intValue;
642 if ( value->value.stringValue == NULL || *value->value.stringValue == '\0' )
646 return ( value->value.timeValue > 0 ) ? 1 : 0;
648 return value->value.boolValue;
650 return (value->value.doubleValue == 0) ? 0 : 1;
661 printNode( Template tmpl, TemplateNode node ) {
668 switch (node->type) {
671 GListCell *cell = GListShift( node->nodeData.loop.listInstance );
672 LoopInstance instance;
678 instance = GLCELL_DATA(cell);
679 GListFreeCell( node->nodeData.loop.listInstance, cell );
681 for(i=0; i<instance->nrow;i++) {
682 VariableValue varvals;
685 cell = GListShift( instance->rowValues );
686 varvals = GLCELL_DATA(cell);
687 GListFreeCell( instance->rowValues, cell );
689 GListForeach( cell, node->nodeData.loop.listVarValues ) {
690 value = (VariableValue)GLCELL_DATA(cell);
692 if ( value->flags & TND___FIRST ) {
694 value->flags |= TND_DEFINED;
695 value->value.intValue = 1;
697 value->flags &= ~TND_DEFINED;
698 value->value.intValue = 0;
700 } else if ( value->flags & TND___LAST ) {
701 if ( i==instance->nrow - 1 ) {
702 value->flags |= TND_DEFINED;
703 value->value.intValue = 1;
705 value->flags &= ~TND_DEFINED;
706 value->value.intValue = 0;
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 ;
721 tassert( value->type == valuePointer );
722 value->value.ptrValue = varvals+j;
727 printNode( tmpl, node->nodeData.loop.bodyNode );
732 value = node->nodeData.condition.value;
733 if ( value->type == valuePointer )
734 value = value->value.ptrValue;
736 if ( node->nodeData.condition.flags & TND_NOT ) {
737 if ( isVariable(value, node->nodeData.condition.flags) )
738 printNode( tmpl, node->nodeData.condition.elseNode );
740 printNode( tmpl, node->nodeData.condition.ifNode );
742 if ( isVariable(value, node->nodeData.condition.flags) )
743 printNode( tmpl, node->nodeData.condition.ifNode );
745 printNode( tmpl, node->nodeData.condition.elseNode );
749 GListForeach( cell, node->nodeData.children )
750 printNode( tmpl, (TemplateNode)GLCELL_DATA(cell) );
753 value = node->nodeData.variable.value;
754 if ( value->type == valuePointer )
755 value = value->value.ptrValue;
757 if ( value && (value->flags & TND_DEFINED) != 0 ) {
761 res = printVal(tmpl, value, &len, node->nodeData.variable.formatValue);
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);
768 if ( res && len>0 ) {
769 tmpl->printString( res, len );
772 } else if ( node->nodeData.variable.defaultValue ) {
773 tmpl->printString( node->nodeData.variable.defaultValue,
774 strlen( node->nodeData.variable.defaultValue ) );
778 tmpl->printString( node->nodeData.text.value, node->nodeData.text.valueLength );
787 printTemplate( Template tmpl ) {
788 if (!tmpl->printString)
791 printNode(tmpl, tmpl->tree);
797 recursiveDump(Template tmpl, TemplateNode node, int level) {
801 printf("%d void\n", level);
807 printf("%d IncludeNode\n", level);
810 printf("%d LoopNode\n", level);
811 recursiveDump(tmpl, node->nodeData.loop.bodyNode, level+1);
814 printf("%d ConditionNode\n", level);
815 recursiveDump(tmpl, node->nodeData.condition.ifNode, level+1);
816 recursiveDump(tmpl, node->nodeData.condition.elseNode, level+1);
819 printf("%d CollectionNode\n", level);
820 GListForeach(cell, node->nodeData.children)
821 recursiveDump(tmpl, (TemplateNode)GLCELL_DATA(cell), level+1);
824 printf("%d TextNode len:%d\n", level, node->nodeData.text.valueLength);
827 printf("%d VariableNode '%s'\n", level, node->nodeData.variable.varName);
830 tlog(TL_CRIT|TL_EXIT, "unknown node type: %d", node->type);
835 dumpTemplate( Template tmpl ) {
836 recursiveDump(tmpl, tmpl->tree, 0);