/* * cOpyright (c) 2004 Teodor Sigaev * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the author nor the names of any co-contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY CONTRIBUTORS ``AS IS'' AND ANY EXPRESS * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL CONTRIBUTORS BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include "tmalloc.h" #include "tlog.h" #include "template.h" #define MAXDEPTH (128) typedef struct ParseState { int depth; char *basename; Template tmpl; } ParseState; static char * getFilename(ParseState *state, char *filename) { int len = 1 /* \0 */ + 1 /* / */ + strlen(filename); char *res; if ( *filename == '/' ) return filename; if ( state->basename && *state->basename ) len += strlen(state->basename); res = mcalloc(state->tmpl->templateContext, sizeof(char) * len); len = 0; if ( state->basename && *state->basename ) { len = strlen(state->basename); memcpy( res, state->basename, len ); res[len++] = '/'; } memcpy( res+len, filename, strlen(filename)); res[ len + strlen(filename) ] = '\0'; return res; } static int recursiveReadTemplate( ParseState *state, Template tmptmpl, char *filename ); static int findIncludes(ParseState *state, TemplateNode *node) { TemplateData tmp; GListCell *cell; tmp.templateContext = state->tmpl->templateContext; if (node == NULL || *node == NULL) return 0; switch((*node)->type) { case IncludeNode: tmp.tree = NULL; if ( recursiveReadTemplate(state, &tmp, (*node)->nodeData.includeFile) != 0 ) return 1; *node = tmp.tree; break; case LoopNode: if ( findIncludes(state, &( (*node)->nodeData.loop.bodyNode )) != 0 ) return 1; break; case ConditionNode: if ( findIncludes(state, &( (*node)->nodeData.condition.ifNode )) != 0 || findIncludes(state, &( (*node)->nodeData.condition.elseNode )) != 0 ) return 1; break; case CollectionNode: GListForeach(cell, (*node)->nodeData.children) { TemplateNode chld = (TemplateNode)GLCELL_DATA(cell); if ( findIncludes(state, &chld) != 0 ) return 1; GLCELL_DATA(cell) = chld; } break; case TextNode: case VariableNode: break; default: tlog(TL_CRIT|TL_EXIT, "unknown node type: %d", (*node)->type); } return 0; } static int recursiveReadTemplate( ParseState *state, Template tmptmpl, char *filename ) { int err; char *fn = getFilename(state, filename); if ( state->depth > MAXDEPTH ) { tlog(TL_ALARM, "too many depth of included templates"); return 4; } if ( tmptmpl == NULL ) tmptmpl = state->tmpl; if ( (err=parseTemplateFile( tmptmpl, fn )) != 0 ) return err; state->depth++; if ( (err=findIncludes(state, &(tmptmpl->tree))) != 0 ) return err; state->depth--; return 0; } static char* qualifyVarname(Template tmpl, TemplateNode loopParentNode, char *varName, int *varNameLength) { int len; char *tmp; if ( ! loopParentNode ) return varName; len = loopParentNode->nodeData.loop.varNameLength + *varNameLength + 2; tmp = mcalloc(tmpl->templateContext, len); len = loopParentNode->nodeData.loop.varNameLength; memcpy( tmp, loopParentNode->nodeData.loop.varName, len); tmp[ len++ ] = '.'; memcpy( tmp + len, varName, *varNameLength); len+=*varNameLength; tmp[ len ] = '\0'; *varNameLength = len; return tmp; } static int checkSpecialVariable(int flags, char *varName) { if ( strcmp(varName, "__first") == 0 ) { flags &= ~TND_GLOBAL; /* special vars cannot be global */ flags |= TND___FIRST; } else if ( strcmp(varName, "__last") == 0 ) { flags &= ~TND_GLOBAL; /* special vars cannot be global */ flags |= TND___LAST; } else if ( strcmp(varName, "__counter") == 0 ) { flags &= ~TND_GLOBAL; /* special vars cannot be global */ flags |= TND___COUNTER; } else if ( strcmp(varName, "__odd") == 0 ) { flags &= ~TND_GLOBAL; /* special vars cannot be global */ flags |= TND___ODD; } else if ( strcmp(varName, "__even") == 0 ) { flags &= ~TND_GLOBAL; /* special vars cannot be global */ flags |= TND___EVEN; } else if ( strcmp(varName, "__size") == 0 ) { flags &= ~TND_GLOBAL; /* special vars cannot be global */ flags |= TND___SIZE; } return flags; } static VariableValue findVariable( Template tmpl, TemplateNode loopParentNode, int flags, char *varName, int varNameLength ) { VariableValue *pvarval; if ( (pvarval = SFSFindData(&tmpl->variables, varName, varNameLength)) == NULL ) { VariableValue pdata = mc0alloc(tmpl->templateContext, sizeof(VariableValueData)); SFSDataIO in; in.key = varName; in.keylen = varNameLength; in.data = &pdata; SFSAdd(&tmpl->variables, &in); if ( loopParentNode && (flags & TND_GLOBAL) == 0 ) { /* * copy special flags to support new inform loopParentNode */ pdata->flags |= flags & TND__SPECIALMASK; if ( flags & ( TND___FIRST | TND___LAST | TND___ODD | TND___EVEN ) ) pdata->type = valueBool; else if ( flags & (TND___COUNTER | TND___SIZE) ) pdata->type = valueInt; else pdata->type = valuePointer; loopParentNode->nodeData.loop.listVarValues = GListPush( loopParentNode->nodeData.loop.listVarValues, pdata ); } return pdata; } return *pvarval; } static int addLoop(Template tmpl, TemplateNode node, TemplateNode loopParentNode) { SFSDataIO in; if ( SFSFindData(&tmpl->variables, node->nodeData.loop.varName, node->nodeData.loop.varNameLength) != NULL ) { tlog(TL_CRIT,"Loop marked '%s' is already defined", node->nodeData.loop.varName); return 1; } in.key = node->nodeData.loop.varName; in.keylen = node->nodeData.loop.varNameLength; in.data = &node; SFSAdd(&tmpl->variables, &in); if ( loopParentNode ) loopParentNode->nodeData.loop.childrenLoop = GListPush( loopParentNode->nodeData.loop.childrenLoop, node ); return 0; } static int compileTree( Template tmpl, TemplateNode node, TemplateNode loopParentNode ) { GListCell *cell; if ( !node ) return 0; switch(node->type) { case LoopNode: node->nodeData.loop.varName = qualifyVarname(tmpl, loopParentNode, node->nodeData.loop.varName, &node->nodeData.loop.varNameLength); if ( compileTree(tmpl, node->nodeData.loop.bodyNode, node) ) return 1; if ( addLoop( tmpl, node, loopParentNode ) ) return 1; break; case ConditionNode: node->nodeData.condition.flags = checkSpecialVariable( node->nodeData.condition.flags, node->nodeData.condition.varName ); if ( (node->nodeData.condition.flags & TND_GLOBAL) == 0 ) node->nodeData.condition.varName = qualifyVarname(tmpl, loopParentNode, node->nodeData.condition.varName, &node->nodeData.condition.varNameLength); node->nodeData.condition.value = findVariable( tmpl, loopParentNode, node->nodeData.condition.flags, node->nodeData.condition.varName, node->nodeData.condition.varNameLength ); if ( compileTree(tmpl, node->nodeData.condition.ifNode, loopParentNode) ) return 1; if ( compileTree(tmpl, node->nodeData.condition.elseNode, loopParentNode) ) return 1; break; case CollectionNode: GListForeach(cell, node->nodeData.children) { TemplateNode chld = (TemplateNode)GLCELL_DATA(cell); if ( compileTree(tmpl, chld, loopParentNode) ) return 1; } break; case VariableNode: node->nodeData.variable.flags = checkSpecialVariable( node->nodeData.variable.flags, node->nodeData.variable.varName ); if ( (node->nodeData.variable.flags & TND_GLOBAL) == 0 ) node->nodeData.variable.varName = qualifyVarname(tmpl, loopParentNode, node->nodeData.variable.varName, &node->nodeData.variable.varNameLength); node->nodeData.variable.value = findVariable( tmpl, loopParentNode, node->nodeData.variable.flags, node->nodeData.variable.varName, node->nodeData.variable.varNameLength ); break; case IncludeNode: tlog(TL_CRIT|TL_EXIT, "unexpected IncludeNode"); break; case TextNode: break; default: tlog(TL_CRIT|TL_EXIT, "unknown node type: %d", node->type); } return 0; } int initTemplate( Template tmpl, MemoryContext *mc, char *basedir, char *filename ) { ParseState state; int err; state.depth = 0; state.tmpl = tmpl; state.basename = basedir; memset(tmpl, 0, sizeof(TemplateData)); tmpl->templateContext = mc; SFSInit_dp(&tmpl->variables, sizeof(void*), NULL); if ( (err=recursiveReadTemplate(&state, NULL, filename))!=0 ) return err; if ( (err=compileTree( tmpl, tmpl->tree, NULL ))!=0 ) return err; return 0; } void resetTemplate( Template tmpl ) { SFSDataIO out; GListCell *cell; SFSIteratorStart( &tmpl->variables ); while( SFSIterate( &tmpl->variables, &out ) ) { VariableValue varval = *(VariableValue*) out.data; if ( varval->type >= valueInt ) varval->flags &= ~TND_DEFINED; else if ( varval->type == LoopNode ) { TemplateNode node = (TemplateNode) varval; GListForeach( cell, node->nodeData.loop.listVarValues ) { varval = (VariableValue) GLCELL_DATA(cell); if ( varval->type == valuePointer ) varval->value.ptrValue = NULL; } GListForeach( cell, node->nodeData.loop.listInstance ) { LoopInstance instance = GLCELL_DATA(cell); GListFree(instance->rowValues ); } GListTruncate( node->nodeData.loop.listInstance, 0 ); } } } static void freeNode( TemplateNode node ) { GListCell *cell; if (!node) return; switch (node->type) { case LoopNode: freeNode( node->nodeData.loop.bodyNode ); GListFree( node->nodeData.loop.childrenLoop ); GListFree( node->nodeData.loop.listVarValues ); GListForeach( cell, node->nodeData.loop.listInstance ) { LoopInstance instance = GLCELL_DATA(cell); GListFree(instance->rowValues ); } GListFree( node->nodeData.loop.listInstance ); break; case CollectionNode: GListForeach( cell, node->nodeData.children ) freeNode( (TemplateNode)GLCELL_DATA(cell) ); GListFree( node->nodeData.children ); break; case ConditionNode: freeNode( node->nodeData.condition.ifNode ); freeNode( node->nodeData.condition.elseNode ); break; case IncludeNode: case VariableNode: case TextNode: default: break; } } void freeTemplate( Template tmpl ) { SFSFree( &tmpl->variables, NULL ); freeNode( tmpl->tree ); } static void newLoopInstance( Template tmpl, TemplateNode node ) { node->nodeData.loop.listInstance = GListPush( node->nodeData.loop.listInstance, mc0alloc(tmpl->templateContext, sizeof(LoopInstanceData) ) ); } int addTemplateRow( Template tmpl, char * key ) { TemplateNode *pnode, node; char *lkey = strlower(mcstrdup(tmpl->templateContext, key)); GListCell *cell; VariableValue varvals; int i=0, nvar; LoopInstance instance; pnode = SFSFindData(&tmpl->variables, lkey, 0); mcfree(lkey); if ( pnode == NULL ) return TVAR_NOTFOUND; node = *pnode; if ( node->type != LoopNode ) return TVAR_FORBIDDEN; nvar = GLIST_LENGTH( node->nodeData.loop.listVarValues ); if ( nvar == 0 ) /* loop without vars can not be looped */ return TVAR_NOROW; if ( GLIST_LENGTH(node->nodeData.loop.listInstance) == 0 ) newLoopInstance(tmpl, node); GListForeach( cell, node->nodeData.loop.childrenLoop ) newLoopInstance( tmpl, GLCELL_DATA(cell) ); varvals = mcalloc( tmpl->templateContext, sizeof(VariableValueData) * nvar ); GListForeach( cell, node->nodeData.loop.listVarValues ) { VariableValue vv = GLCELL_DATA(cell); vv->value.ptrValue = varvals + i; varvals[i].flags = 0; i++; } instance = GLCELL_DATA(GLIST_TAIL(node->nodeData.loop.listInstance)); instance->nrow++; instance->rowValues = GListPush( instance->rowValues, varvals ); return TVAR_OK; } static VariableValueData storage; static int setTemplateValue( Template tmpl, char *key) { VariableValue *pvarval, varval; char *lkey = strlower(mcstrdup(tmpl->templateContext, key)); pvarval = SFSFindData(&tmpl->variables, lkey, 0); mcfree(lkey); if ( pvarval == NULL ) return TVAR_NOTFOUND; varval = *pvarval; if ( varval->type != 0 && varval->type < valueInt ) { return TVAR_LOOPMARK; } else if ( varval->type == valuePointer ) { /* looped variable */ varval = varval->value.ptrValue; if ( varval == NULL ) return TVAR_NOROW; tassert( (varval->type & TND_GLOBAL) == 0 ); } if ( varval->flags & TND__SPECIALMASK ) return TVAR_FORBIDDEN; if ( storage.flags & TND_DEFINED ) { varval->flags |= TND_DEFINED; varval->type = storage.type; varval->value = storage.value; } else { varval->flags &= ~TND_DEFINED; } return TVAR_OK; } int setTemplateValueInt( Template tmpl, char * key, int val ) { storage.flags = TND_DEFINED; storage.type = valueInt; storage.value.intValue = val; return setTemplateValue( tmpl, key ); } int setTemplateValueString( Template tmpl, char * key, char * val ) { storage.flags = TND_DEFINED; storage.type = valueString; storage.value.stringValue = val; return setTemplateValue( tmpl, key ); } int setTemplateValueTime( Template tmpl, char * key, time_t val ) { storage.flags = TND_DEFINED; storage.type = valueTime; storage.value.timeValue = val; return setTemplateValue( tmpl, key ); } int setTemplateValueBool( Template tmpl, char * key, int val ) { storage.flags = TND_DEFINED; storage.type = valueBool; storage.value.boolValue = val; return setTemplateValue( tmpl, key ); } int setTemplateValueDouble( Template tmpl, char * key, double val ) { storage.flags = TND_DEFINED; storage.type = valueDouble; storage.value.boolValue = val; return setTemplateValue( tmpl, key ); } int setTemplateValueUndefined( Template tmpl, char * key ) { storage.flags = 0; return setTemplateValue( tmpl, key ); } static char * printVal( Template tmpl, VariableValue value, int *len, char *format ) { int printedlen; char *res; *len = 0; if ( value->type == valueTime ) { printedlen = 64; res = mcalloc( tmpl->templateContext, printedlen+1 ); while ( (printedlen = strftime(NULL, 0, (format) ? format : "%Y-%m-%d %H:%M:%S", localtime(&value->value.timeValue))) == 0 ) { printedlen *= 2; res=mcrealloc(res, printedlen); } *len = printedlen; return res; } switch (value->type) { case valueInt: printedlen = snprintf(NULL, 0, (format) ? format : "%d", value->value.intValue); break; case valueString: if ( value->value.stringValue == NULL || *value->value.stringValue == '\0' ) return NULL; printedlen = snprintf(NULL, 0, (format) ? format : "%s", value->value.stringValue); break; case valueBool: printedlen = snprintf(NULL, 0, "%s", (value->value.boolValue) ? "true" : "false" ); break; case valueDouble: printedlen = snprintf(NULL, 0, (format) ? format : "%f", value->value.doubleValue); break; case valuePointer: case valueTime: default: return NULL; } res = mcalloc( tmpl->templateContext, printedlen+1 ); switch (value->type) { case valueInt: printedlen = snprintf(res, printedlen+1, (format) ? format : "%d", value->value.intValue); break; case valueString: printedlen = snprintf(res, printedlen+1, (format) ? format : "%s", value->value.stringValue); break; case valueBool: printedlen = snprintf(res, printedlen+1, "%s", (value->value.boolValue) ? "true" : "false" ); break; case valueDouble: printedlen = snprintf(res, printedlen+1, (format) ? format : "%f", value->value.doubleValue); break; case valuePointer: case valueTime: default: return NULL; } *len = printedlen; return res; } static int isVariable(VariableValue value, int flags) { if ( value == NULL ) return 0; if ( flags & TND_DEFINED ) { return (value->flags & TND_DEFINED); } else { switch (value->type) { case valueInt: return value->value.intValue; case valueString: if ( value->value.stringValue == NULL || *value->value.stringValue == '\0' ) return 0; return 1; case valueTime: return ( value->value.timeValue > 0 ) ? 1 : 0; case valueBool: return value->value.boolValue; case valueDouble: return (value->value.doubleValue == 0) ? 0 : 1; case valuePointer: default: return 0; } } return 0; } static void printNode( Template tmpl, TemplateNode node ) { GListCell *cell; VariableValue value; if (!node) return; switch (node->type) { case LoopNode: { GListCell *cell = GListShift( node->nodeData.loop.listInstance ); LoopInstance instance; int i; if ( cell == NULL ) return; instance = GLCELL_DATA(cell); GListFreeCell( node->nodeData.loop.listInstance, cell ); for(i=0; inrow;i++) { VariableValue varvals; int j = 0; cell = GListShift( instance->rowValues ); varvals = GLCELL_DATA(cell); GListFreeCell( instance->rowValues, cell ); GListForeach( cell, node->nodeData.loop.listVarValues ) { value = (VariableValue)GLCELL_DATA(cell); if ( value->flags & TND___FIRST ) { if ( i==0 ) { value->flags |= TND_DEFINED; value->value.intValue = 1; } else { value->flags &= ~TND_DEFINED; value->value.intValue = 0; } } else if ( value->flags & TND___LAST ) { if ( i==instance->nrow - 1 ) { value->flags |= TND_DEFINED; value->value.intValue = 1; } else { value->flags &= ~TND_DEFINED; value->value.intValue = 0; } } else if ( value->flags & TND___COUNTER ) { value->flags |= TND_DEFINED; value->value.intValue = i+1; } else if ( value->flags & TND___SIZE ) { value->flags |= TND_DEFINED; value->value.intValue = instance->nrow; } else if ( value->flags & TND___ODD ) { value->flags |= TND_DEFINED; value->value.boolValue = (i%2) ? 0 : 1 ; } else if ( value->flags & TND___EVEN ) { value->flags |= TND_DEFINED; value->value.boolValue = (i%2) ? 1 : 0 ; } else { tassert( value->type == valuePointer ); value->value.ptrValue = varvals+j; } j++; } printNode( tmpl, node->nodeData.loop.bodyNode ); } } break; case ConditionNode: value = node->nodeData.condition.value; if ( value->type == valuePointer ) value = value->value.ptrValue; if ( node->nodeData.condition.flags & TND_NOT ) { if ( isVariable(value, node->nodeData.condition.flags) ) printNode( tmpl, node->nodeData.condition.elseNode ); else printNode( tmpl, node->nodeData.condition.ifNode ); } else { if ( isVariable(value, node->nodeData.condition.flags) ) printNode( tmpl, node->nodeData.condition.ifNode ); else printNode( tmpl, node->nodeData.condition.elseNode ); } break; case CollectionNode: GListForeach( cell, node->nodeData.children ) printNode( tmpl, (TemplateNode)GLCELL_DATA(cell) ); break; case VariableNode: value = node->nodeData.variable.value; if ( value->type == valuePointer ) value = value->value.ptrValue; if ( value && (value->flags & TND_DEFINED) != 0 ) { int len; char *res; res = printVal(tmpl, value, &len, node->nodeData.variable.formatValue); if ( (node->nodeData.variable.flags & TND_HTMLESCAPE) && tmpl->htmlEscape ) res = tmpl->htmlEscape(res, &len); if ( (node->nodeData.variable.flags & TND_URLESCAPE) && tmpl->urlEscape ) res = tmpl->urlEscape(res, &len); if ( res && len>0 ) { tmpl->printString( res, len ); mcfree(res); } } else if ( node->nodeData.variable.defaultValue ) { tmpl->printString( node->nodeData.variable.defaultValue, strlen( node->nodeData.variable.defaultValue ) ); } break; case TextNode: tmpl->printString( node->nodeData.text.value, node->nodeData.text.valueLength ); break; case IncludeNode: default: break; } } int printTemplate( Template tmpl ) { if (!tmpl->printString) return 1; printNode(tmpl, tmpl->tree); return 0; } static void recursiveDump(Template tmpl, TemplateNode node, int level) { GListCell *cell; if (node == NULL ) { printf("%d void\n", level); return; } switch(node->type) { case IncludeNode: printf("%d IncludeNode\n", level); break; case LoopNode: printf("%d LoopNode\n", level); recursiveDump(tmpl, node->nodeData.loop.bodyNode, level+1); break; case ConditionNode: printf("%d ConditionNode\n", level); recursiveDump(tmpl, node->nodeData.condition.ifNode, level+1); recursiveDump(tmpl, node->nodeData.condition.elseNode, level+1); break; case CollectionNode: printf("%d CollectionNode\n", level); GListForeach(cell, node->nodeData.children) recursiveDump(tmpl, (TemplateNode)GLCELL_DATA(cell), level+1); break; case TextNode: printf("%d TextNode len:%d\n", level, node->nodeData.text.valueLength); break; case VariableNode: printf("%d VariableNode '%s'\n", level, node->nodeData.variable.varName); break; default: tlog(TL_CRIT|TL_EXIT, "unknown node type: %d", node->type); } } void dumpTemplate( Template tmpl ) { recursiveDump(tmpl, tmpl->tree, 0); }