/****************************************************************************** * * Copyright (C) 1997-2015 by Dimitri van Heesch. * * Permission to use, copy, modify, and distribute this software and its * documentation under the terms of the GNU General Public License is hereby * granted. No representations are made about the suitability of this software * for any purpose. It is provided "as is" without express or implied warranty. * See the GNU General Public License for more details. * * Documents produced by Doxygen are derivative works derived from the * input used in their production; they are not affected by this license. * */ #include "template.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "sortdict.h" #include "ftextstream.h" #include "message.h" #include "util.h" #include "resourcemgr.h" #include "portable.h" #define ENABLE_TRACING 0 #if ENABLE_TRACING #define TRACE(x) printf x #else #define TRACE(x) #endif class TemplateToken; //------------------------------------------------------------------- static QValueList split(const QCString &str,const QCString &sep, bool allowEmptyEntries=FALSE,bool cleanup=TRUE) { QValueList lst; int j = 0; int i = str.find( sep, j ); while (i!=-1) { if ( str.mid(j,i-j).length() > 0 ) { if (cleanup) { lst.append(str.mid(j,i-j).stripWhiteSpace()); } else { lst.append(str.mid(j,i-j)); } } else if (allowEmptyEntries) { lst.append(""); } j = i + sep.length(); i = str.find(sep,j); } int l = str.length() - 1; if (str.mid(j,l-j+1).length()>0) { if (cleanup) { lst.append(str.mid(j,l-j+1).stripWhiteSpace()); } else { lst.append(str.mid(j,l-j+1)); } } else if (allowEmptyEntries) { lst.append(""); } return lst; } //---------------------------------------------------------------------------- /** Strips spaces surrounding `=` from string \a in, so * `foo = 10 bar=5 baz= 'hello'` will become `foo=10 bar=5 baz='hello'` */ static QCString removeSpacesAroundEquals(const char *s) { QCString result(s); const char *p=result.data(); char *q = result.rawData(); char c; while ((c=*p++)) { if (c==' ') // found a space, see if there is a = as well { const char *t = p; bool found=FALSE; while (*t==' ' || *t=='=') { if (*t++=='=') found=TRUE; } if (found) { c='='; p=t; // move p to end of '\s*=\s*' sequence } } *q++=c; } if (qaddRef(); } TemplateVariant::TemplateVariant(TemplateListIntf *l) : m_type(List), m_list(l), m_raw(FALSE) { m_list->addRef(); } TemplateVariant::~TemplateVariant() { if (m_type==Struct) m_strukt->release(); else if (m_type==List) m_list->release(); } TemplateVariant::TemplateVariant(const TemplateVariant &v) : m_type(v.m_type), m_strukt(0), m_raw(FALSE) { m_raw = v.m_raw; switch (m_type) { case None: break; case Bool: m_boolVal = v.m_boolVal; break; case Integer: m_intVal = v.m_intVal; break; case String: m_strVal = v.m_strVal; break; case Struct: m_strukt = v.m_strukt; m_strukt->addRef(); break; case List: m_list = v.m_list; m_list->addRef(); break; case Function: m_delegate= v.m_delegate;break; } } TemplateVariant &TemplateVariant::operator=(const TemplateVariant &v) { // assignment can change the type of the variable, so we have to be // careful with reference counted content. TemplateStructIntf *tmpStruct = m_type==Struct ? m_strukt : 0; TemplateListIntf *tmpList = m_type==List ? m_list : 0; Type tmpType = m_type; m_type = v.m_type; m_raw = v.m_raw; switch (m_type) { case None: break; case Bool: m_boolVal = v.m_boolVal; break; case Integer: m_intVal = v.m_intVal; break; case String: m_strVal = v.m_strVal; break; case Struct: m_strukt = v.m_strukt; m_strukt->addRef(); break; case List: m_list = v.m_list; m_list->addRef(); break; case Function: m_delegate= v.m_delegate;break; } // release overwritten reference counted values if (tmpType==Struct && tmpStruct) tmpStruct->release(); else if (tmpType==List && tmpList ) tmpList->release(); return *this; } bool TemplateVariant::toBool() const { switch (m_type) { case None: return FALSE; case Bool: return m_boolVal; case Integer: return m_intVal!=0; case String: return !m_strVal.isEmpty(); case Struct: return TRUE; case List: return m_list->count()!=0; case Function: return FALSE; } return FALSE; } int TemplateVariant::toInt() const { switch (m_type) { case None: return 0; case Bool: return m_boolVal ? 1 : 0; case Integer: return m_intVal; case String: return m_strVal.toInt(); case Struct: return 0; case List: return m_list->count(); case Function: return 0; } return 0; } //- Template struct implementation -------------------------------------------- /** @brief Private data of a template struct object */ class TemplateStruct::Private { public: Private() : fields(17), refCount(0) { fields.setAutoDelete(TRUE); } QDict fields; int refCount; }; TemplateStruct::TemplateStruct() { p = new Private; } TemplateStruct::~TemplateStruct() { delete p; } int TemplateStruct::addRef() { return ++p->refCount; } int TemplateStruct::release() { int count = --p->refCount; if (count<=0) { delete this; } return count; } void TemplateStruct::set(const char *name,const TemplateVariant &v) { TemplateVariant *pv = p->fields.find(name); if (pv) // change existing field { *pv = v; } else // insert new field { p->fields.insert(name,new TemplateVariant(v)); } } TemplateVariant TemplateStruct::get(const char *name) const { TemplateVariant *v = p->fields.find(name); return v ? *v : TemplateVariant(); } TemplateStruct *TemplateStruct::alloc() { return new TemplateStruct; } //- Template list implementation ---------------------------------------------- /** @brief Private data of a template list object */ class TemplateList::Private { public: Private() : index(-1), refCount(0) {} QValueList elems; int index; int refCount; }; TemplateList::TemplateList() { p = new Private; } TemplateList::~TemplateList() { delete p; } int TemplateList::addRef() { return ++p->refCount; } int TemplateList::release() { int count = --p->refCount; if (count<=0) { delete this; } return count; } int TemplateList::count() const { return p->elems.count(); } void TemplateList::append(const TemplateVariant &v) { p->elems.append(v); } // iterator support class TemplateListConstIterator : public TemplateListIntf::ConstIterator { public: TemplateListConstIterator(const TemplateList &l) : m_list(l) { m_index=-1; } virtual ~TemplateListConstIterator() {} virtual void toFirst() { m_it = m_list.p->elems.begin(); m_index=0; } virtual void toLast() { m_it = m_list.p->elems.fromLast(); m_index=m_list.count()-1; } virtual void toNext() { if (m_it!=m_list.p->elems.end()) { ++m_it; ++m_index; } } virtual void toPrev() { if (m_index>0) { --m_it; --m_index; } else { m_index=-1; } } virtual bool current(TemplateVariant &v) const { if (m_index<0 || m_it==m_list.p->elems.end()) { v = TemplateVariant(); return FALSE; } else { v = *m_it; return TRUE; } } private: const TemplateList &m_list; QValueList::ConstIterator m_it; int m_index; }; TemplateListIntf::ConstIterator *TemplateList::createIterator() const { return new TemplateListConstIterator(*this); } TemplateVariant TemplateList::at(int index) const { if (index>=0 && index<(int)p->elems.count()) { return p->elems[index]; } else { return TemplateVariant(); } } TemplateList *TemplateList::alloc() { return new TemplateList; } //- Operator types ------------------------------------------------------------ /** @brief Class representing operators that can appear in template expressions */ class Operator { public: /* Operator precedence (low to high) or and not in ==, !=, <, >, <=, >= +, - *, /, % | : , */ enum Type { Or, And, Not, In, Equal, NotEqual, Less, Greater, LessEqual, GreaterEqual, Plus, Minus, Multiply, Divide, Modulo, Filter, Colon, Comma, LeftParen, RightParen, Last }; static const char *toString(Type op) { switch(op) { case Or: return "or"; case And: return "and"; case Not: return "not"; case In: return "in"; case Equal: return "=="; case NotEqual: return "!="; case Less: return "<"; case Greater: return ">"; case LessEqual: return "<="; case GreaterEqual: return ">="; case Plus: return "+"; case Minus: return "-"; case Multiply: return "*"; case Divide: return "/"; case Modulo: return "%"; case Filter: return "|"; case Colon: return ":"; case Comma: return ","; case LeftParen: return "("; case RightParen: return ")"; case Last: return "?"; } return "?"; } }; //----------------------------------------------------------------------------- class TemplateNodeBlock; /** @brief Class holding stacks of blocks available in the context */ class TemplateBlockContext { public: TemplateBlockContext(); TemplateNodeBlock *get(const QCString &name) const; TemplateNodeBlock *pop(const QCString &name) const; void add(TemplateNodeBlock *block); void add(TemplateBlockContext *ctx); void push(TemplateNodeBlock *block); void clear(); private: QDict< QList > m_blocks; }; /** @brief A container to store a key-value pair */ struct TemplateKeyValue { TemplateKeyValue() {} TemplateKeyValue(const QCString &k,const TemplateVariant &v) : key(k), value(v) {} QCString key; TemplateVariant value; }; /** @brief Internal class representing the implementation of a template * context */ class TemplateContextImpl : public TemplateContext { public: TemplateContextImpl(const TemplateEngine *e); virtual ~TemplateContextImpl(); // TemplateContext methods void push(); void pop(); void set(const char *name,const TemplateVariant &v); TemplateVariant get(const QCString &name) const; const TemplateVariant *getRef(const QCString &name) const; void setOutputDirectory(const QCString &dir) { m_outputDir = dir; } void setEscapeIntf(const QCString &ext,TemplateEscapeIntf *intf) { int i=(!ext.isEmpty() && ext.at(0)=='.') ? 1 : 0; m_escapeIntfDict.insert(ext.mid(i),new TemplateEscapeIntf*(intf)); } void selectEscapeIntf(const QCString &ext) { TemplateEscapeIntf **ppIntf = m_escapeIntfDict.find(ext); m_activeEscapeIntf = ppIntf ? *ppIntf : 0; } void setActiveEscapeIntf(TemplateEscapeIntf *intf) { m_activeEscapeIntf = intf; } void setSpacelessIntf(TemplateSpacelessIntf *intf) { m_spacelessIntf = intf; } // internal methods TemplateBlockContext *blockContext(); TemplateVariant getPrimary(const QCString &name) const; void setLocation(const QCString &templateName,int line) { m_templateName=templateName; m_line=line; } QCString templateName() const { return m_templateName; } int line() const { return m_line; } QCString outputDirectory() const { return m_outputDir; } TemplateEscapeIntf *escapeIntf() const { return m_activeEscapeIntf; } TemplateSpacelessIntf *spacelessIntf() const { return m_spacelessIntf; } void enableSpaceless(bool b) { if (b && !m_spacelessEnabled) m_spacelessIntf->reset(); m_spacelessEnabled=b; } bool spacelessEnabled() const { return m_spacelessEnabled && m_spacelessIntf; } void enableTabbing(bool b) { m_tabbingEnabled=b; if (m_activeEscapeIntf) m_activeEscapeIntf->enableTabbing(b); } bool tabbingEnabled() const { return m_tabbingEnabled; } bool needsRecoding() const { return !m_encoding.isEmpty(); } QCString encoding() const { return m_encoding; } void setEncoding(const QCString &file,int line,const QCString &enc); QCString recode(const QCString &s); void warn(const char *fileName,int line,const char *fmt,...) const; // index related functions void openSubIndex(const QCString &indexName); void closeSubIndex(const QCString &indexName); void addIndexEntry(const QCString &indexName,const QValueList &arguments); private: const TemplateEngine *m_engine; QCString m_templateName; int m_line; QCString m_outputDir; QList< QDict > m_contextStack; TemplateBlockContext m_blockContext; QDict m_escapeIntfDict; TemplateEscapeIntf *m_activeEscapeIntf; TemplateSpacelessIntf *m_spacelessIntf; bool m_spacelessEnabled; bool m_tabbingEnabled; TemplateAutoRef m_indices; QDict< QStack > m_indexStacks; QCString m_encoding; void *m_fromUtf8; }; //----------------------------------------------------------------------------- /** @brief The implementation of the "add" filter */ class FilterAdd { public: static int variantIntValue(const TemplateVariant &v,bool &isInt) { isInt = v.type()==TemplateVariant::Integer; if (!isInt && v.type()==TemplateVariant::String) { return v.toString().toInt(&isInt); } return isInt ? v.toInt() : 0; } static TemplateVariant apply(const TemplateVariant &v,const TemplateVariant &arg) { if (!v.isValid()) { return arg; } bool lhsIsInt; int lhsValue = variantIntValue(v,lhsIsInt); bool rhsIsInt; int rhsValue = variantIntValue(arg,rhsIsInt); if (lhsIsInt && rhsIsInt) { return lhsValue+rhsValue; } else if (v.type()==TemplateVariant::String && arg.type()==TemplateVariant::String) { return TemplateVariant(v.toString() + arg.toString()); } else { return v; } } }; //----------------------------------------------------------------------------- /** @brief The implementation of the "get" filter */ class FilterGet { public: static TemplateVariant apply(const TemplateVariant &v,const TemplateVariant &arg) { if (v.isValid() && v.type()==TemplateVariant::Struct && arg.type()==TemplateVariant::String) { TemplateVariant result = v.toStruct()->get(arg.toString()); //printf("\nok[%s]=%d\n",arg.toString().data(),result.type()); return result; } else { //printf("\nnok[%s]\n",arg.toString().data()); return FALSE; } } }; //----------------------------------------------------------------------------- /** @brief The implementation of the "raw" filter */ class FilterRaw { public: static TemplateVariant apply(const TemplateVariant &v,const TemplateVariant &) { if (v.isValid() && (v.type()==TemplateVariant::String || v.type()==TemplateVariant::Integer)) { return TemplateVariant(v.toString(),TRUE); } else { return v; } } }; //----------------------------------------------------------------------------- /** @brief The implementation of the "list" filter */ class FilterList { public: static TemplateVariant apply(const TemplateVariant &v,const TemplateVariant &) { if (v.isValid()) { if (v.type()==TemplateVariant::List) // input is already a list { return v; } // create a list with v as the only element TemplateList *list = TemplateList::alloc(); list->append(v); return list; } else { return v; } } }; //----------------------------------------------------------------------------- /** @brief The implementation of the "texlabel" filter */ class FilterTexLabel { public: static TemplateVariant apply(const TemplateVariant &v,const TemplateVariant &) { if (v.isValid() && (v.type()==TemplateVariant::String)) { return TemplateVariant(latexEscapeLabelName(v.toString(),FALSE),TRUE); } else { return v; } } }; //----------------------------------------------------------------------------- /** @brief The implementation of the "texindex" filter */ class FilterTexIndex { public: static TemplateVariant apply(const TemplateVariant &v,const TemplateVariant &) { if (v.isValid() && (v.type()==TemplateVariant::String)) { return TemplateVariant(latexEscapeIndexChars(v.toString(),FALSE),TRUE); } else { return v; } } }; //----------------------------------------------------------------------------- /** @brief The implementation of the "append" filter */ class FilterAppend { public: static TemplateVariant apply(const TemplateVariant &v,const TemplateVariant &arg) { if ((v.type()==TemplateVariant::String || v.type()==TemplateVariant::Integer) && (arg.type()==TemplateVariant::String || arg.type()==TemplateVariant::Integer)) { return TemplateVariant(v.toString() + arg.toString()); } else { return v; } } }; //----------------------------------------------------------------------------- /** @brief The implementation of the "prepend" filter */ class FilterPrepend { public: static TemplateVariant apply(const TemplateVariant &v,const TemplateVariant &arg) { if ((v.type()==TemplateVariant::String || v.type()==TemplateVariant::Integer) && arg.type()==TemplateVariant::String) { return TemplateVariant(arg.toString() + v.toString()); } else { return v; } } }; //-------------------------------------------------------------------- /** @brief The implementation of the "length" filter */ class FilterLength { public: static TemplateVariant apply(const TemplateVariant &v,const TemplateVariant &) { if (!v.isValid()) { return TemplateVariant(); } if (v.type()==TemplateVariant::List) { return TemplateVariant(v.toList()->count()); } else if (v.type()==TemplateVariant::String) { return TemplateVariant((int)v.toString().length()); } else { return TemplateVariant(); } } }; //-------------------------------------------------------------------- /** @brief The implementation of the "default" filter */ class FilterDefault { public: static TemplateVariant apply(const TemplateVariant &v,const TemplateVariant &arg) { if (!v.isValid()) { return arg; } else if (v.type()==TemplateVariant::String && v.toString().isEmpty()) { return arg; } else { return v; } } }; //-------------------------------------------------------------------- /** @brief The implementation of the "flatten" filter */ class FilterFlatten { public: static TemplateVariant apply(const TemplateVariant &v,const TemplateVariant &) { if (!v.isValid() || v.type()!=TemplateVariant::List) { return v; } else { TemplateList *list = TemplateList::alloc(); flatten(v.toList(),list); return TemplateVariant(list); } } private: static void flatten(TemplateListIntf *tree,TemplateList *list) { TemplateListIntf::ConstIterator *it = tree->createIterator(); TemplateVariant item; for (it->toFirst();(it->current(item));it->toNext()) { TemplateStructIntf *s = item.toStruct(); if (s) { list->append(item); // if s has "children" then recurse into the children TemplateVariant children = s->get("children"); if (children.isValid() && children.type()==TemplateVariant::List) { flatten(children.toList(),list); } } else { list->append(item); } } delete it; } }; //-------------------------------------------------------------------- /** @brief The implementation of the "listsort" filter */ class FilterListSort { struct ListElem { ListElem(const QCString &k,const TemplateVariant &v) : key(k), value(v) {} QCString key; TemplateVariant value; }; class SortList : public QList { public: SortList() { setAutoDelete(TRUE); } private: int compareValues(const ListElem *item1,const ListElem *item2) const { return qstrcmp(item1->key,item2->key); } }; public: static TemplateVariant apply(const TemplateVariant &v,const TemplateVariant &args) { if (v.type()==TemplateVariant::List && args.type()==TemplateVariant::String) { //printf("FilterListSort::apply: v=%s args=%s\n",v.toString().data(),args.toString().data()); TemplateListIntf::ConstIterator *it = v.toList()->createIterator(); TemplateVariant item; TemplateList *result = TemplateList::alloc(); // create list of items based on v using the data in args as a sort key SortList sortList; for (it->toFirst();(it->current(item));it->toNext()) { TemplateStructIntf *s = item.toStruct(); if (s) { QCString sortKey = determineSortKey(s,args.toString()); sortList.append(new ListElem(sortKey,item)); //printf("sortKey=%s\n",sortKey.data()); } } delete it; // sort the list sortList.sort(); // add sorted items to the result list QListIterator sit(sortList); ListElem *elem; for (sit.toFirst();(elem=sit.current());++sit) { result->append(elem->value); } return result; } return v; } private: static QCString determineSortKey(TemplateStructIntf *s,const QCString &arg) { int i,p=0; QCString result; while ((i=arg.find("{{",p))!=-1) { result+=arg.mid(p,i-p); int j=arg.find("}}",i+2); if (j!=-1) { QCString var = arg.mid(i+2,j-i-2); TemplateVariant val=s->get(var); //printf("found argument %s value=%s\n",var.data(),val.toString().data()); result+=val.toString(); p=j+2; } else { p=i+1; } } result+=arg.right(arg.length()-p); return result; } }; //-------------------------------------------------------------------- /** @brief The implementation of the "groupBy" filter */ class FilterGroupBy { struct ListElem { ListElem(const QCString &k,const TemplateVariant &v) : key(k), value(v) {} QCString key; TemplateVariant value; }; class SortList : public QList { public: SortList() { setAutoDelete(TRUE); } private: int compareValues(const ListElem *item1,const ListElem *item2) const { return qstrcmp(item1->key,item2->key); } }; public: static TemplateVariant apply(const TemplateVariant &v,const TemplateVariant &args) { if (v.type()==TemplateVariant::List && args.type()==TemplateVariant::String) { //printf("FilterListSort::apply: v=%s args=%s\n",v.toString().data(),args.toString().data()); TemplateListIntf::ConstIterator *it = v.toList()->createIterator(); TemplateVariant item; TemplateList *result = TemplateList::alloc(); // create list of items based on v using the data in args as a sort key SortList sortList; for (it->toFirst();(it->current(item));it->toNext()) { TemplateStructIntf *s = item.toStruct(); if (s) { QCString sortKey = determineSortKey(s,args.toString()); sortList.append(new ListElem(sortKey,item)); //printf("sortKey=%s\n",sortKey.data()); } } delete it; // sort the list sortList.sort(); // add sorted items to the result list QListIterator sit(sortList); ListElem *elem; TemplateList *groupList=0; QCString prevKey; for (sit.toFirst();(elem=sit.current());++sit) { if (groupList==0 || elem->key!=prevKey) { groupList = TemplateList::alloc(); result->append(groupList); prevKey = elem->key; } groupList->append(elem->value); } return result; } return v; } private: static QCString determineSortKey(TemplateStructIntf *s,const QCString &attribName) { TemplateVariant v = s->get(attribName); return v.toString(); } }; //-------------------------------------------------------------------- /** @brief The implementation of the "relative" filter */ class FilterRelative { public: static TemplateVariant apply(const TemplateVariant &v,const TemplateVariant &) { if (v.isValid() && v.type()==TemplateVariant::String && v.toString().left(2)=="..") { return TRUE; } else { return FALSE; } } }; //-------------------------------------------------------------------- /** @brief The implementation of the "paginate" filter */ class FilterPaginate { public: static TemplateVariant apply(const TemplateVariant &v,const TemplateVariant &args) { if (v.isValid() && v.type()==TemplateVariant::List && args.isValid() && args.type()==TemplateVariant::Integer) { int pageSize = args.toInt(); TemplateListIntf *list = v.toList(); TemplateList *result = TemplateList::alloc(); TemplateListIntf::ConstIterator *it = list->createIterator(); TemplateVariant item; TemplateList *pageList=0; int i = 0; for (it->toFirst();(it->current(item));it->toNext()) { if (pageList==0) { pageList = TemplateList::alloc(); result->append(pageList); } pageList->append(item); i++; if (i==pageSize) // page is full start a new one { pageList=0; i=0; } } delete it; return result; } else // wrong arguments { return v; } } }; //-------------------------------------------------------------------- /** @brief The implementation of the "alphaIndex" filter */ class FilterAlphaIndex { private: struct ListElem { ListElem(uint k,const TemplateVariant &v) : key(k), value(v) {} uint key; TemplateVariant value; }; class SortList : public QList { public: SortList() { setAutoDelete(TRUE); } private: int compareValues(const ListElem *item1,const ListElem *item2) const { return item1->key-item2->key; } }; static QCString keyToLetter(uint startLetter) { return QString(QChar(startLetter)).utf8(); } static QCString keyToLabel(uint startLetter) { char s[11]; // 0x12345678 + '\0' if ((startLetter>='0' && startLetter<='9') || (startLetter>='a' && startLetter<='z') || (startLetter>='A' && startLetter<='Z')) { int i=0; if (startLetter>='0' && startLetter<='9') s[i++] = 'x'; s[i++]=tolower((char)startLetter); s[i++]=0; } else { const char hex[]="0123456789abcdef"; int i=0; s[i++]='x'; if (startLetter>(1<<24)) // 4 byte character { s[i++]=hex[(startLetter>>28)&0xf]; s[i++]=hex[(startLetter>>24)&0xf]; } if (startLetter>(1<<16)) // 3 byte character { s[i++]=hex[(startLetter>>20)&0xf]; s[i++]=hex[(startLetter>>16)&0xf]; } if (startLetter>(1<<8)) // 2 byte character { s[i++]=hex[(startLetter>>12)&0xf]; s[i++]=hex[(startLetter>>8)&0xf]; } // one byte character s[i++]=hex[(startLetter>>4)&0xf]; s[i++]=hex[(startLetter>>0)&0xf]; s[i++]=0; } return s; } static uint determineSortKey(TemplateStructIntf *s,const QCString &attribName) { TemplateVariant v = s->get(attribName); int index = getPrefixIndex(v.toString()); return getUtf8CodeToUpper(v.toString(),index); } public: static TemplateVariant apply(const TemplateVariant &v,const TemplateVariant &args) { if (v.type()==TemplateVariant::List && args.type()==TemplateVariant::String) { //printf("FilterListSort::apply: v=%s args=%s\n",v.toString().data(),args.toString().data()); TemplateListIntf::ConstIterator *it = v.toList()->createIterator(); TemplateVariant item; TemplateList *result = TemplateList::alloc(); // create list of items based on v using the data in args as a sort key SortList sortList; for (it->toFirst();(it->current(item));it->toNext()) { TemplateStructIntf *s = item.toStruct(); if (s) { uint sortKey = determineSortKey(s,args.toString()); sortList.append(new ListElem(sortKey,item)); //printf("sortKey=%s\n",sortKey.data()); } } delete it; // sort the list sortList.sort(); // create an index from the sorted list uint letter=0; QListIterator sit(sortList); ListElem *elem; TemplateStruct *indexNode = 0; TemplateList *indexList = 0; for (sit.toFirst();(elem=sit.current());++sit) { if (letter!=elem->key || indexNode==0) { // create new indexNode indexNode = TemplateStruct::alloc(); indexList = TemplateList::alloc(); indexNode->set("letter", keyToLetter(elem->key)); indexNode->set("label", keyToLabel(elem->key)); indexNode->set("items",indexList); result->append(indexNode); letter=elem->key; } indexList->append(elem->value); } return result; } return v; } }; //-------------------------------------------------------------------- /** @brief The implementation of the "default" filter */ class FilterStripPath { public: static TemplateVariant apply(const TemplateVariant &v,const TemplateVariant &) { if (!v.isValid() || v.type()!=TemplateVariant::String) { return v; } QCString result = v.toString(); int i=result.findRev('/'); if (i!=-1) { result=result.mid(i+1); } i=result.findRev('\\'); if (i!=-1) { result=result.mid(i+1); } return result; } }; //-------------------------------------------------------------------- /** @brief The implementation of the "default" filter */ class FilterNoWrap { public: static TemplateVariant apply(const TemplateVariant &v,const TemplateVariant &) { if (!v.isValid() || v.type()!=TemplateVariant::String) { return v; } QCString s = v.toString(); return substitute(s," "," "); } }; //-------------------------------------------------------------------- /** @brief The implementation of the "divisibleby" filter */ class FilterDivisibleBy { public: static TemplateVariant apply(const TemplateVariant &v,const TemplateVariant &n) { if (!v.isValid() || !n.isValid()) { return TemplateVariant(); } if (v.type()==TemplateVariant::Integer && n.type()==TemplateVariant::Integer) { int ni = n.toInt(); if (ni>0) { return TemplateVariant((v.toInt()%ni)==0); } else { return TemplateVariant(FALSE); } } else { return TemplateVariant(); } } }; //-------------------------------------------------------------------- /** @brief The implementation of the "isRelativeURL" filter */ class FilterIsRelativeURL { public: static TemplateVariant apply(const TemplateVariant &v,const TemplateVariant &) { if (v.isValid() && v.type()==TemplateVariant::String) { QCString s = v.toString(); if (!s.isEmpty() && s.at(0)=='!') return TRUE; } return FALSE; } }; //-------------------------------------------------------------------- /** @brief The implementation of the "isRelativeURL" filter */ class FilterIsAbsoluteURL { public: static TemplateVariant apply(const TemplateVariant &v,const TemplateVariant &) { if (v.isValid() && v.type()==TemplateVariant::String) { QCString s = v.toString(); if (!s.isEmpty() && s.at(0)=='^') return TRUE; } return FALSE; } }; //-------------------------------------------------------------------- /** @brief The implementation of the "decodeURL" filter * The leading character is removed from the value in case it is a ^ or !. * - ^ is used to encode a absolute URL * - ! is used to encode a relative URL */ class FilterDecodeURL { public: static TemplateVariant apply(const TemplateVariant &v,const TemplateVariant &) { if (v.isValid() && v.type()==TemplateVariant::String) { QCString s = v.toString(); if (!s.isEmpty() && (s.at(0)=='^' || s.at(0)=='!')) { return s.mid(1); } } return v; } }; //-------------------------------------------------------------------- /** @brief Factory singleton for registering and creating filters */ class TemplateFilterFactory { public: typedef TemplateVariant (FilterFunction)(const TemplateVariant &v,const TemplateVariant &arg); static TemplateFilterFactory *instance() { static TemplateFilterFactory *instance = 0; if (instance==0) instance = new TemplateFilterFactory; return instance; } TemplateVariant apply(const QCString &name,const TemplateVariant &v,const TemplateVariant &arg, bool &ok) { FilterFunction *func = (FilterFunction*)m_registry.find(name); if (func) { ok=TRUE; return (*func)(v,arg); } else { ok=FALSE; return v; } } void registerFilter(const QCString &name,FilterFunction *func) { m_registry.insert(name,(void*)func); } /** @brief Helper class for registering a filter function */ template class AutoRegister { public: AutoRegister(const QCString &key) { TemplateFilterFactory::instance()->registerFilter(key,&T::apply); } }; private: QDict m_registry; }; // register a handlers for each filter we support static TemplateFilterFactory::AutoRegister fAdd("add"); static TemplateFilterFactory::AutoRegister fGet("get"); static TemplateFilterFactory::AutoRegister fRaw("raw"); static TemplateFilterFactory::AutoRegister fList("list"); static TemplateFilterFactory::AutoRegister fAppend("append"); static TemplateFilterFactory::AutoRegister fLength("length"); static TemplateFilterFactory::AutoRegister fNoWrap("nowrap"); static TemplateFilterFactory::AutoRegister fFlatten("flatten"); static TemplateFilterFactory::AutoRegister fDefault("default"); static TemplateFilterFactory::AutoRegister fPrepend("prepend"); static TemplateFilterFactory::AutoRegister fGroupBy("groupBy"); static TemplateFilterFactory::AutoRegister fRelative("relative"); static TemplateFilterFactory::AutoRegister fListSort("listsort"); static TemplateFilterFactory::AutoRegister fTexLabel("texLabel"); static TemplateFilterFactory::AutoRegister fTexIndex("texIndex"); static TemplateFilterFactory::AutoRegister fPaginate("paginate"); static TemplateFilterFactory::AutoRegister fStripPath("stripPath"); static TemplateFilterFactory::AutoRegister fDecodeURL("decodeURL"); static TemplateFilterFactory::AutoRegister fAlphaIndex("alphaIndex"); static TemplateFilterFactory::AutoRegister fDivisibleBy("divisibleby"); static TemplateFilterFactory::AutoRegister fIsRelativeURL("isRelativeURL"); static TemplateFilterFactory::AutoRegister fIsAbsoluteURL("isAbsoluteURL"); //-------------------------------------------------------------------- /** @brief Base class for all nodes in the abstract syntax tree of an * expression. */ class ExprAst { public: virtual ~ExprAst() {} virtual TemplateVariant resolve(TemplateContext *) { return TemplateVariant(); } }; /** @brief Class representing a number in the AST */ class ExprAstNumber : public ExprAst { public: ExprAstNumber(int num) : m_number(num) { TRACE(("ExprAstNumber(%d)\n",num)); } int number() const { return m_number; } virtual TemplateVariant resolve(TemplateContext *) { return TemplateVariant(m_number); } private: int m_number; }; /** @brief Class representing a variable in the AST */ class ExprAstVariable : public ExprAst { public: ExprAstVariable(const char *name) : m_name(name) { TRACE(("ExprAstVariable(%s)\n",name)); } const QCString &name() const { return m_name; } virtual TemplateVariant resolve(TemplateContext *c) { TemplateVariant v = c->get(m_name); TemplateContextImpl *ci = dynamic_cast(c); if (!v.isValid()) { if (ci) ci->warn(ci->templateName(),ci->line(),"undefined variable '%s' in expression",m_name.data()); } return v; } private: QCString m_name; }; class ExprAstFunctionVariable : public ExprAst { public: ExprAstFunctionVariable(ExprAst *var,const QList &args) : m_var(var), m_args(args) { TRACE(("ExprAstFunctionVariable()\n")); m_args.setAutoDelete(TRUE); } ~ExprAstFunctionVariable() { delete m_var; } virtual TemplateVariant resolve(TemplateContext *c) { QValueList args; for (uint i=0;iresolve(c); args.append(v); } TemplateVariant v = m_var->resolve(c); if (v.type()==TemplateVariant::Function) { v = v.call(args); } return v; } private: ExprAst *m_var; QList m_args; }; /** @brief Class representing a filter in the AST */ class ExprAstFilter : public ExprAst { public: ExprAstFilter(const char *name,ExprAst *arg) : m_name(name), m_arg(arg) { TRACE(("ExprAstFilter(%s)\n",name)); } ~ExprAstFilter() { delete m_arg; } const QCString &name() const { return m_name; } TemplateVariant apply(const TemplateVariant &v,TemplateContext *c) { TemplateContextImpl *ci = dynamic_cast(c); if (ci==0) return v; // should not happen TRACE(("Applying filter '%s' to '%s' (type=%d)\n",m_name.data(),v.toString().data(),v.type())); TemplateVariant arg; if (m_arg) arg = m_arg->resolve(c); bool ok; TemplateVariant result = TemplateFilterFactory::instance()->apply(m_name,v,arg,ok); if (!ok) { ci->warn(ci->templateName(),ci->line(),"unknown filter '%s'",m_name.data()); } return result; } private: QCString m_name; ExprAst *m_arg; }; /** @brief Class representing a filter applied to an expression in the AST */ class ExprAstFilterAppl : public ExprAst { public: ExprAstFilterAppl(ExprAst *expr,ExprAstFilter *filter) : m_expr(expr), m_filter(filter) { TRACE(("ExprAstFilterAppl\n")); } ~ExprAstFilterAppl() { delete m_expr; delete m_filter; } virtual TemplateVariant resolve(TemplateContext *c) { return m_filter->apply(m_expr->resolve(c),c); } private: ExprAst *m_expr; ExprAstFilter *m_filter; }; /** @brief Class representing a string literal in the AST */ class ExprAstLiteral : public ExprAst { public: ExprAstLiteral(const char *lit) : m_literal(lit) { TRACE(("ExprAstLiteral(%s)\n",lit)); } const QCString &literal() const { return m_literal; } virtual TemplateVariant resolve(TemplateContext *) { return TemplateVariant(m_literal); } private: QCString m_literal; }; /** @brief Class representing a negation (not) operator in the AST */ class ExprAstNegate : public ExprAst { public: ExprAstNegate(ExprAst *expr) : m_expr(expr) { TRACE(("ExprAstNegate\n")); } ~ExprAstNegate() { delete m_expr; } virtual TemplateVariant resolve(TemplateContext *c) { return TemplateVariant(!m_expr->resolve(c).toBool()); } private: ExprAst *m_expr; }; class ExprAstUnary : public ExprAst { public: ExprAstUnary(Operator::Type op,ExprAst *exp) : m_operator(op), m_exp(exp) { TRACE(("ExprAstUnary %s\n",Operator::toString(op))); } ~ExprAstUnary() { delete m_exp; } virtual TemplateVariant resolve(TemplateContext *c) { TemplateVariant exp = m_exp->resolve(c); switch (m_operator) { case Operator::Minus: return -exp.toInt(); default: return TemplateVariant(); } } private: Operator::Type m_operator; ExprAst *m_exp; }; /** @brief Class representing a binary operator in the AST */ class ExprAstBinary : public ExprAst { public: ExprAstBinary(Operator::Type op,ExprAst *lhs,ExprAst *rhs) : m_operator(op), m_lhs(lhs), m_rhs(rhs) { TRACE(("ExprAstBinary %s\n",Operator::toString(op))); } ~ExprAstBinary() { delete m_lhs; delete m_rhs; } virtual TemplateVariant resolve(TemplateContext *c) { TemplateContextImpl *ci = dynamic_cast(c); if (ci==0) return TemplateVariant(); // should not happen TemplateVariant lhs = m_lhs->resolve(c); TemplateVariant rhs = m_rhs ? m_rhs->resolve(c) : TemplateVariant(); switch(m_operator) { case Operator::Or: return TemplateVariant(lhs.toBool() || rhs.toBool()); case Operator::And: return TemplateVariant(lhs.toBool() && rhs.toBool()); case Operator::Equal: return TemplateVariant(lhs == rhs); case Operator::NotEqual: return TemplateVariant(!(lhs == rhs)); case Operator::Less: if (lhs.type()==TemplateVariant::String && rhs.type()==TemplateVariant::String) { return lhs.toString()rhs.toInt(); } case Operator::LessEqual: if (lhs.type()==TemplateVariant::String && rhs.type()==TemplateVariant::String) { return lhs.toString()==rhs.toString() || lhs.toString()=rhs.toInt(); } case Operator::Plus: { return TemplateVariant(lhs.toInt() + rhs.toInt()); } case Operator::Minus: { return TemplateVariant(lhs.toInt() - rhs.toInt()); } case Operator::Multiply: { return TemplateVariant(lhs.toInt() * rhs.toInt()); } case Operator::Divide: { int denom = rhs.toInt(); if (denom!=0) { return TemplateVariant(lhs.toInt() / denom); } else // divide by zero { ci->warn(ci->templateName(),ci->line(),"division by zero while evaluating expression is undefined"); return 0; } } case Operator::Modulo: { int denom = rhs.toInt(); if (denom!=0) { return TemplateVariant(lhs.toInt() % denom); } else // module zero { ci->warn(ci->templateName(),ci->line(),"modulo zero while evaluating expression is undefined"); return 0; } } default: return TemplateVariant(); } } private: Operator::Type m_operator; ExprAst *m_lhs; ExprAst *m_rhs; }; //---------------------------------------------------------- /** @brief Base class of all nodes in a template's AST */ class TemplateNode { public: TemplateNode(TemplateNode *parent) : m_parent(parent) {} virtual ~TemplateNode() {} virtual void render(FTextStream &ts, TemplateContext *c) = 0; TemplateNode *parent() { return m_parent; } private: TemplateNode *m_parent; }; //---------------------------------------------------------- /** @brief Parser for templates */ class TemplateParser { public: TemplateParser(const TemplateEngine *engine, const QCString &templateName,QList &tokens); void parse(TemplateNode *parent,int line,const QStrList &stopAt, QList &nodes); bool hasNextToken() const; TemplateToken *takeNextToken(); void removeNextToken(); void prependToken(const TemplateToken *token); const TemplateToken *currentToken() const; QCString templateName() const { return m_templateName; } void warn(const char *fileName,int line,const char *fmt,...) const; private: const TemplateEngine *m_engine; QCString m_templateName; QList &m_tokens; }; //-------------------------------------------------------------------- /** @brief Recursive decent parser for Django style template expressions. */ class ExpressionParser { public: ExpressionParser(const TemplateParser *parser,int line) : m_parser(parser), m_line(line), m_tokenStream(0) { } virtual ~ExpressionParser() { } ExprAst *parse(const char *expr) { if (expr==0) return 0; m_tokenStream = expr; getNextToken(); return parseExpression(); } private: /** @brief Class representing a token within an expression. */ class ExprToken { public: ExprToken() : type(Unknown), num(-1), op(Operator::Or) { } enum Type { Unknown, Operator, Number, Identifier, Literal }; Type type; int num; QCString id; Operator::Type op; }; ExprAst *parseExpression() { TRACE(("{parseExpression(%s)\n",m_tokenStream)); ExprAst *result = parseOrExpression(); TRACE(("}parseExpression(%s)\n",m_tokenStream)); return result; } ExprAst *parseOrExpression() { TRACE(("{parseOrExpression(%s)\n",m_tokenStream)); ExprAst *lhs = parseAndExpression(); if (lhs) { while (m_curToken.type==ExprToken::Operator && m_curToken.op==Operator::Or) { getNextToken(); ExprAst *rhs = parseAndExpression(); lhs = new ExprAstBinary(Operator::Or,lhs,rhs); } } TRACE(("}parseOrExpression(%s)\n",m_tokenStream)); return lhs; } ExprAst *parseAndExpression() { TRACE(("{parseAndExpression(%s)\n",m_tokenStream)); ExprAst *lhs = parseNotExpression(); if (lhs) { while (m_curToken.type==ExprToken::Operator && m_curToken.op==Operator::And) { getNextToken(); ExprAst *rhs = parseNotExpression(); lhs = new ExprAstBinary(Operator::And,lhs,rhs); } } TRACE(("}parseAndExpression(%s)\n",m_tokenStream)); return lhs; } ExprAst *parseNotExpression() { TRACE(("{parseNotExpression(%s)\n",m_tokenStream)); ExprAst *result=0; if (m_curToken.type==ExprToken::Operator && m_curToken.op==Operator::Not) { getNextToken(); ExprAst *expr = parseCompareExpression(); if (expr==0) { warn(m_parser->templateName(),m_line,"argument missing for not operator"); return 0; } result = new ExprAstNegate(expr); } else { result = parseCompareExpression(); } TRACE(("}parseNotExpression(%s)\n",m_tokenStream)); return result; } ExprAst *parseCompareExpression() { TRACE(("{parseCompareExpression(%s)\n",m_tokenStream)); ExprAst *lhs = parseAdditiveExpression(); if (lhs) { Operator::Type op = m_curToken.op; if (m_curToken.type==ExprToken::Operator && (op==Operator::Less || op==Operator::Greater || op==Operator::Equal || op==Operator::NotEqual || op==Operator::LessEqual || op==Operator::GreaterEqual ) ) { getNextToken(); ExprAst *rhs = parseNotExpression(); lhs = new ExprAstBinary(op,lhs,rhs); } } TRACE(("}parseCompareExpression(%s)\n",m_tokenStream)); return lhs; } ExprAst *parseAdditiveExpression() { TRACE(("{parseAdditiveExpression(%s)\n",m_tokenStream)); ExprAst *lhs = parseMultiplicativeExpression(); if (lhs) { while (m_curToken.type==ExprToken::Operator && (m_curToken.op==Operator::Plus || m_curToken.op==Operator::Minus)) { Operator::Type op = m_curToken.op; getNextToken(); ExprAst *rhs = parseMultiplicativeExpression(); lhs = new ExprAstBinary(op,lhs,rhs); } } TRACE(("}parseAdditiveExpression(%s)\n",m_tokenStream)); return lhs; } ExprAst *parseMultiplicativeExpression() { TRACE(("{parseMultiplicativeExpression(%s)\n",m_tokenStream)); ExprAst *lhs = parseUnaryExpression(); if (lhs) { while (m_curToken.type==ExprToken::Operator && (m_curToken.op==Operator::Multiply || m_curToken.op==Operator::Divide || m_curToken.op==Operator::Modulo)) { Operator::Type op = m_curToken.op; getNextToken(); ExprAst *rhs = parseUnaryExpression(); lhs = new ExprAstBinary(op,lhs,rhs); } } TRACE(("}parseMultiplicativeExpression(%s)\n",m_tokenStream)); return lhs; } ExprAst *parseUnaryExpression() { TRACE(("{parseUnaryExpression(%s)\n",m_tokenStream)); ExprAst *result=0; if (m_curToken.type==ExprToken::Operator) { if (m_curToken.op==Operator::Plus) { getNextToken(); result = parsePrimaryExpression(); } else if (m_curToken.op==Operator::Minus) { getNextToken(); ExprAst *rhs = parsePrimaryExpression(); result = new ExprAstUnary(m_curToken.op,rhs); } else { result = parsePrimaryExpression(); } } else { result = parsePrimaryExpression(); } TRACE(("}parseUnaryExpression(%s)\n",m_tokenStream)); return result; } ExprAst *parsePrimaryExpression() { TRACE(("{parsePrimary(%s)\n",m_tokenStream)); ExprAst *result=0; switch (m_curToken.type) { case ExprToken::Number: result = parseNumber(); break; case ExprToken::Identifier: result = parseFilteredVariable(); break; case ExprToken::Literal: result = parseLiteral(); break; case ExprToken::Operator: if (m_curToken.op==Operator::LeftParen) { getNextToken(); // skip over opening bracket result = parseExpression(); if (m_curToken.type!=ExprToken::Operator || m_curToken.op!=Operator::RightParen) { warn(m_parser->templateName(),m_line,"missing closing parenthesis"); } else { getNextToken(); // skip over closing bracket } } else { warn(m_parser->templateName(),m_line,"unexpected operator '%s' in expression", Operator::toString(m_curToken.op)); } break; default: warn(m_parser->templateName(),m_line,"unexpected token in expression"); } TRACE(("}parsePrimary(%s)\n",m_tokenStream)); return result; } ExprAst *parseNumber() { TRACE(("{parseNumber(%d)\n",m_curToken.num)); ExprAst *num = new ExprAstNumber(m_curToken.num); getNextToken(); TRACE(("}parseNumber()\n")); return num; } ExprAst *parseIdentifier() { TRACE(("{parseIdentifier(%s)\n",m_curToken.id.data())); ExprAst *id = new ExprAstVariable(m_curToken.id); getNextToken(); TRACE(("}parseIdentifier()\n")); return id; } ExprAst *parseLiteral() { TRACE(("{parseLiteral(%s)\n",m_curToken.id.data())); ExprAst *expr = new ExprAstLiteral(m_curToken.id); getNextToken(); TRACE(("}parseLiteral()\n")); return expr; } ExprAst *parseIdentifierOptionalArgs() { TRACE(("{parseIdentifierOptionalArgs(%s)\n",m_curToken.id.data())); ExprAst *expr = parseIdentifier(); if (expr) { if (m_curToken.type==ExprToken::Operator && m_curToken.op==Operator::Colon) { getNextToken(); ExprAst *argExpr = parsePrimaryExpression(); QList args; args.append(argExpr); while (m_curToken.type==ExprToken::Operator && m_curToken.op==Operator::Comma) { getNextToken(); argExpr = parsePrimaryExpression(); args.append(argExpr); } expr = new ExprAstFunctionVariable(expr,args); } } TRACE(("}parseIdentifierOptionalArgs()\n")); return expr; } ExprAst *parseFilteredVariable() { TRACE(("{parseFilteredVariable()\n")); ExprAst *expr = parseIdentifierOptionalArgs(); if (expr) { while (m_curToken.type==ExprToken::Operator && m_curToken.op==Operator::Filter) { getNextToken(); ExprAstFilter *filter = parseFilter(); if (!filter) break; expr = new ExprAstFilterAppl(expr,filter); } } TRACE(("}parseFilteredVariable()\n")); return expr; } ExprAstFilter *parseFilter() { TRACE(("{parseFilter(%s)\n",m_curToken.id.data())); QCString filterName = m_curToken.id; getNextToken(); ExprAst *argExpr=0; if (m_curToken.type==ExprToken::Operator && m_curToken.op==Operator::Colon) { getNextToken(); argExpr = parsePrimaryExpression(); } ExprAstFilter *filter = new ExprAstFilter(filterName,argExpr); TRACE(("}parseFilter()\n")); return filter; } bool getNextToken() { const char *p = m_tokenStream; char s[2]; s[1]=0; if (p==0 || *p=='\0') return FALSE; while (*p==' ') p++; // skip over spaces char c=*p; if (*p=='\0') // only spaces... { m_tokenStream = p; return FALSE; } const char *q = p; switch (c) { case '=': if (c=='=' && *(p+1)=='=') // equal { m_curToken.op = Operator::Equal; p+=2; } break; case '!': if (c=='!' && *(p+1)=='=') // not equal { m_curToken.op = Operator::NotEqual; p+=2; } break; case '<': if (c=='<' && *(p+1)=='=') // less or equal { m_curToken.op = Operator::LessEqual; p+=2; } else // less { m_curToken.op = Operator::Less; p++; } break; case '>': if (c=='>' && *(p+1)=='=') // greater or equal { m_curToken.op = Operator::GreaterEqual; p+=2; } else // greater { m_curToken.op = Operator::Greater; p++; } break; case '(': m_curToken.op = Operator::LeftParen; p++; break; case ')': m_curToken.op = Operator::RightParen; p++; break; case '|': m_curToken.op = Operator::Filter; p++; break; case '+': m_curToken.op = Operator::Plus; p++; break; case '-': m_curToken.op = Operator::Minus; p++; break; case '*': m_curToken.op = Operator::Multiply; p++; break; case '/': m_curToken.op = Operator::Divide; p++; break; case '%': m_curToken.op = Operator::Modulo; p++; break; case ':': m_curToken.op = Operator::Colon; p++; break; case ',': m_curToken.op = Operator::Comma; p++; break; case 'n': if (strncmp(p,"not ",4)==0) { m_curToken.op = Operator::Not; p+=4; } break; case 'a': if (strncmp(p,"and ",4)==0) { m_curToken.op = Operator::And; p+=4; } break; case 'o': if (strncmp(p,"or ",3)==0) { m_curToken.op = Operator::Or; p+=3; } break; default: break; } if (p!=q) // found an operator { m_curToken.type = ExprToken::Operator; } else // no token found yet { if (c>='0' && c<='9') // number? { m_curToken.type = ExprToken::Number; const char *np = p; m_curToken.num = 0; while (*np>='0' && *np<='9') { m_curToken.num*=10; m_curToken.num+=*np-'0'; np++; } p=np; } else if (c=='_' || (c>='a' && c<='z') || (c>='A' && c<='Z')) // identifier? { m_curToken.type = ExprToken::Identifier; s[0]=c; m_curToken.id = s; p++; while ((c=*p) && (c=='_' || c=='.' || (c>='a' && c<='z') || (c>='A' && c<='Z') || (c>='0' && c<='9')) ) { s[0]=c; m_curToken.id+=s; p++; } if (m_curToken.id=="True") // treat true literal as numerical 1 { m_curToken.type = ExprToken::Number; m_curToken.num = 1; } else if (m_curToken.id=="False") // treat false literal as numerical 0 { m_curToken.type = ExprToken::Number; m_curToken.num = 0; } } else if (c=='"' || c=='\'') // string literal { m_curToken.type = ExprToken::Literal; m_curToken.id.resize(0); p++; char tokenChar = c; char cp=0; while ((c=*p) && (c!=tokenChar || (c==tokenChar && cp=='\\'))) { s[0]=c; if (c!='\\' || cp=='\\') // don't add escapes { m_curToken.id+=s; } cp=c; p++; } if (*p==tokenChar) p++; } } if (p==q) // still no valid token found -> error { m_curToken.type = ExprToken::Unknown; char s[2]; s[0]=c; s[1]=0; warn(m_parser->templateName(),m_line,"Found unknown token '%s' (%d) while parsing %s",s,c,m_tokenStream); m_curToken.id = s; p++; } //TRACE(("token type=%d op=%d num=%d id=%s\n", // m_curToken.type,m_curToken.op,m_curToken.num,m_curToken.id.data())); m_tokenStream = p; return TRUE; } const TemplateParser *m_parser; ExprToken m_curToken; int m_line; const char *m_tokenStream; }; //---------------------------------------------------------- /** @brief Class representing a lexical token in a template */ class TemplateToken { public: enum Type { Text, Variable, Block }; TemplateToken(Type t,const char *d,int l) : type(t), data(d), line(l) {} Type type; QCString data; int line; }; //---------------------------------------------------------- /** @brief Class representing a list of AST nodes in a template */ class TemplateNodeList : public QList { public: TemplateNodeList() { setAutoDelete(TRUE); } void render(FTextStream &ts,TemplateContext *c) { TRACE(("{TemplateNodeList::render\n")); QListIterator it(*this); TemplateNode *tn=0; for (it.toFirst();(tn=it.current());++it) { tn->render(ts,c); } TRACE(("}TemplateNodeList::render\n")); } }; //---------------------------------------------------------- /** @brief Internal class representing the implementation of a template */ class TemplateImpl : public TemplateNode, public Template { public: TemplateImpl(TemplateEngine *e,const QCString &name,const QCString &data, const QCString &extension); ~TemplateImpl(); void render(FTextStream &ts, TemplateContext *c); TemplateEngine *engine() const { return m_engine; } TemplateBlockContext *blockContext() { return &m_blockContext; } private: TemplateEngine *m_engine; QCString m_name; TemplateNodeList m_nodes; TemplateBlockContext m_blockContext; }; //---------------------------------------------------------- /** @brief Weak reference wrapper for TemplateStructIntf that provides access to the * wrapped struct without holding a reference. */ class TemplateStructWeakRef : public TemplateStructIntf { public: TemplateStructWeakRef(TemplateStructIntf *ref) : m_ref(ref), m_refCount(0) {} virtual TemplateVariant get(const char *name) const { return m_ref->get(name); } virtual int addRef() { return ++m_refCount; } virtual int release() { int count=--m_refCount; if (count<=0) { delete this; } return count; } private: TemplateStructIntf *m_ref; int m_refCount; }; //---------------------------------------------------------- TemplateContextImpl::TemplateContextImpl(const TemplateEngine *e) : m_engine(e), m_templateName(""), m_line(1), m_activeEscapeIntf(0), m_spacelessIntf(0), m_spacelessEnabled(FALSE), m_tabbingEnabled(FALSE), m_indices(TemplateStruct::alloc()) { m_indexStacks.setAutoDelete(TRUE); m_contextStack.setAutoDelete(TRUE); m_escapeIntfDict.setAutoDelete(TRUE); m_fromUtf8 = (void*)(-1); push(); set("index",m_indices.get()); } TemplateContextImpl::~TemplateContextImpl() { pop(); } void TemplateContextImpl::setEncoding(const QCString &templateName,int line,const QCString &enc) { if (enc==m_encoding) return; // nothing changed if (m_fromUtf8!=(void *)(-1)) { portable_iconv_close(m_fromUtf8); m_fromUtf8 = (void*)(-1); } m_encoding=enc; if (!enc.isEmpty()) { m_fromUtf8 = portable_iconv_open(enc,"UTF-8"); if (m_fromUtf8==(void*)(-1)) { warn(templateName,line,"unsupported character conversion: '%s'->'UTF-8'\n", enc.data()); } } //printf("TemplateContextImpl::setEncoding(%s)\n",enc.data()); } QCString TemplateContextImpl::recode(const QCString &s) { //printf("TemplateContextImpl::recode(%s)\n",s.data()); int iSize = s.length(); int oSize = iSize*4+1; QCString output(oSize); size_t iLeft = iSize; size_t oLeft = oSize; char *iPtr = s.rawData(); char *oPtr = output.rawData(); if (!portable_iconv(m_fromUtf8,&iPtr,&iLeft,&oPtr,&oLeft)) { oSize -= (int)oLeft; output.resize(oSize+1); output.at(oSize)='\0'; return output; } else { return s; } } void TemplateContextImpl::set(const char *name,const TemplateVariant &v) { TemplateVariant *pv = m_contextStack.getFirst()->find(name); if (pv) { m_contextStack.getFirst()->remove(name); } m_contextStack.getFirst()->insert(name,new TemplateVariant(v)); } TemplateVariant TemplateContextImpl::get(const QCString &name) const { int i=name.find('.'); if (i==-1) // simple name { return getPrimary(name); } else // obj.prop { QCString objName = name.left(i); TemplateVariant v = getPrimary(objName); QCString propName = name.mid(i+1); while (!propName.isEmpty()) { //printf("getPrimary(%s) type=%d:%s\n",objName.data(),v.type(),v.toString().data()); if (v.type()==TemplateVariant::Struct) { i = propName.find("."); int l = i==-1 ? propName.length() : i; v = v.toStruct()->get(propName.left(l)); if (!v.isValid()) { warn(m_templateName,m_line,"requesting non-existing property '%s' for object '%s'",propName.left(l).data(),objName.data()); } if (i!=-1) { objName = propName.left(i); propName = propName.mid(i+1); } else { propName.resize(0); } } else if (v.type()==TemplateVariant::List) { i = propName.find("."); int l = i==-1 ? propName.length() : i; bool b; int index = propName.left(l).toInt(&b); if (b) { v = v.toList()->at(index); } else { warn(m_templateName,m_line,"list index '%s' is not valid",propName.data()); break; } if (i!=-1) { propName = propName.mid(i+1); } else { propName.resize(0); } } else { warn(m_templateName,m_line,"using . on an object '%s' is not an struct or list",objName.data()); return TemplateVariant(); } } return v; } } const TemplateVariant *TemplateContextImpl::getRef(const QCString &name) const { QListIterator< QDict > it(m_contextStack); QDict *dict; for (it.toFirst();(dict=it.current());++it) { TemplateVariant *v = dict->find(name); if (v) return v; } return 0; // not found } TemplateVariant TemplateContextImpl::getPrimary(const QCString &name) const { const TemplateVariant *v = getRef(name); return v ? *v : TemplateVariant(); } void TemplateContextImpl::push() { QDict *dict = new QDict; dict->setAutoDelete(TRUE); m_contextStack.prepend(dict); } void TemplateContextImpl::pop() { if (!m_contextStack.removeFirst()) { warn(m_templateName,m_line,"pop() called on empty context stack!\n"); } } TemplateBlockContext *TemplateContextImpl::blockContext() { return &m_blockContext; } void TemplateContextImpl::warn(const char *fileName,int line,const char *fmt,...) const { va_list args; va_start(args,fmt); va_warn(fileName,line,fmt,args); va_end(args); m_engine->printIncludeContext(fileName,line); } void TemplateContextImpl::openSubIndex(const QCString &indexName) { //printf("TemplateContextImpl::openSubIndex(%s)\n",indexName.data()); QStack *stack = m_indexStacks.find(indexName); if (!stack || stack->isEmpty() || stack->top()->type()==TemplateVariant::List) // error: no stack yet or no entry { warn(m_templateName,m_line,"opensubindex for index %s without preceding indexentry",indexName.data()); return; } // get the parent entry to add the list to TemplateStruct *entry = dynamic_cast(stack->top()->toStruct()); if (entry) { // add new list to the stack TemplateList *list = TemplateList::alloc(); stack->push(new TemplateVariant(list)); entry->set("children",list); entry->set("is_leaf_node",false); } } void TemplateContextImpl::closeSubIndex(const QCString &indexName) { //printf("TemplateContextImpl::closeSubIndex(%s)\n",indexName.data()); QStack *stack = m_indexStacks.find(indexName); if (!stack || stack->count()<3) { warn(m_templateName,m_line,"closesubindex for index %s without matching open",indexName.data()); } else // stack->count()>=2 { if (stack->top()->type()==TemplateVariant::Struct) { delete stack->pop(); // pop struct delete stack->pop(); // pop list } else // empty list! correct "is_left_node" attribute of the parent entry { delete stack->pop(); // pop list TemplateStruct *entry = dynamic_cast(stack->top()->toStruct()); if (entry) { entry->set("is_leaf_node",true); } } } //fprintf(stderr,"TemplateContextImpl::closeSubIndex(%s) end g_count=%d\n\n",indexName.data(),g_count); } static void getPathListFunc(TemplateStructIntf *entry,TemplateList *list) { TemplateVariant parent = entry->get("parent"); if (parent.type()==TemplateVariant::Struct) { getPathListFunc(parent.toStruct(),list); } list->append(entry); } static TemplateVariant getPathFunc(const void *ctx, const QValueList &) { TemplateStruct *entry = (TemplateStruct*)ctx; TemplateList *result = TemplateList::alloc(); getPathListFunc(entry,result); return result; } void TemplateContextImpl::addIndexEntry(const QCString &indexName,const QValueList &arguments) { QValueListConstIterator it = arguments.begin(); //printf("TemplateContextImpl::addIndexEntry(%s)\n",indexName.data()); //while (it!=arguments.end()) //{ // printf(" key=%s value=%s\n",(*it).key.data(),(*it).value.toString().data()); // ++it; //} TemplateVariant parent(FALSE); QStack *stack = m_indexStacks.find(indexName); if (!stack) // no stack yet, create it! { stack = new QStack; stack->setAutoDelete(TRUE); m_indexStacks.insert(indexName,stack); } TemplateList *list = 0; if (stack->isEmpty()) // first item, create empty list and add it to the index { list = TemplateList::alloc(); stack->push(new TemplateVariant(list)); m_indices->set(indexName,list); // make list available under index } else // stack not empty { if (stack->top()->type()==TemplateVariant::Struct) // already an entry in the list { // remove current entry from the stack delete stack->pop(); } else // first entry after opensubindex { ASSERT(stack->top()->type()==TemplateVariant::List); } if (stack->count()>1) { TemplateVariant *tmp = stack->pop(); // To prevent a cyclic dependency between parent and child which causes a memory // leak, we wrap the parent into a weak reference version. parent = new TemplateStructWeakRef(stack->top()->toStruct()); stack->push(tmp); ASSERT(parent.type()==TemplateVariant::Struct); } // get list to add new item list = dynamic_cast(stack->top()->toList()); } TemplateStruct *entry = TemplateStruct::alloc(); // add user specified fields to the entry for (it=arguments.begin();it!=arguments.end();++it) { entry->set((*it).key,(*it).value); } if (list->count()>0) { TemplateStruct *lastEntry = dynamic_cast(list->at(list->count()-1).toStruct()); lastEntry->set("last",false); } entry->set("is_leaf_node",true); entry->set("first",list->count()==0); entry->set("index",list->count()); entry->set("parent",parent); entry->set("path",TemplateVariant::Delegate::fromFunction(entry,getPathFunc)); entry->set("last",true); stack->push(new TemplateVariant(entry)); list->append(entry); } //---------------------------------------------------------- /** @brief Class representing a piece of plain text in a template */ class TemplateNodeText : public TemplateNode { public: TemplateNodeText(TemplateParser *,TemplateNode *parent,int,const QCString &data) : TemplateNode(parent), m_data(data) { TRACE(("TemplateNodeText('%s')\n",replace(data,'\n',' ').data())); } void render(FTextStream &ts, TemplateContext *c) { TemplateContextImpl* ci = dynamic_cast(c); if (ci==0) return; // should not happen //printf("TemplateNodeText::render(%s) needsRecoding=%d ci=%p\n",m_data.data(),ci->needsRecoding(),ci); if (ci->spacelessEnabled()) { if (ci->needsRecoding()) { ts << ci->recode(ci->spacelessIntf()->remove(m_data)); } else { ts << ci->spacelessIntf()->remove(m_data); } } else { if (ci->needsRecoding()) { ts << ci->recode(m_data); } else { ts << m_data; } } } private: QCString m_data; }; //---------------------------------------------------------- /** @brief Class representing a variable in a template */ class TemplateNodeVariable : public TemplateNode { public: TemplateNodeVariable(TemplateParser *parser,TemplateNode *parent,int line,const QCString &var) : TemplateNode(parent), m_templateName(parser->templateName()), m_line(line) { TRACE(("TemplateNodeVariable(%s)\n",var.data())); ExpressionParser expParser(parser,line); m_var = expParser.parse(var); if (m_var==0) { parser->warn(m_templateName,line,"invalid expression '%s' for variable",var.data()); } } ~TemplateNodeVariable() { delete m_var; } void render(FTextStream &ts, TemplateContext *c) { TemplateContextImpl* ci = dynamic_cast(c); if (ci==0) return; // should not happen ci->setLocation(m_templateName,m_line); if (m_var) { TemplateVariant v = m_var->resolve(c); if (v.type()==TemplateVariant::Function) { v = v.call(QValueList()); } if (ci->escapeIntf() && !v.raw()) { if (ci->needsRecoding()) { ts << ci->recode(ci->escapeIntf()->escape(v.toString())); } else { ts << ci->escapeIntf()->escape(v.toString()); } } else { if (ci->needsRecoding()) { ts << ci->recode(v.toString()); } else { ts << v.toString(); } } } } private: QCString m_templateName; int m_line; ExprAst *m_var; QList m_args; }; //---------------------------------------------------------- /** @brief Helper class for creating template AST tag nodes and returning * the template for a given node. */ template class TemplateNodeCreator : public TemplateNode { public: TemplateNodeCreator(TemplateParser *parser,TemplateNode *parent,int line) : TemplateNode(parent), m_templateName(parser->templateName()), m_line(line) {} static TemplateNode *createInstance(TemplateParser *parser, TemplateNode *parent, int line, const QCString &data) { return new T(parser,parent,line,data); } TemplateImpl *getTemplate() { TemplateNode *root = this; while (root && root->parent()) { root = root->parent(); } return dynamic_cast(root); } protected: void mkpath(TemplateContextImpl *ci,const QCString &fileName) { int i=fileName.find('/'); QCString outputDir = ci->outputDirectory(); QDir d(outputDir); if (!d.exists()) { QDir rootDir; rootDir.setPath(QDir::currentDirPath()); if (!rootDir.mkdir(outputDir)) { err("tag OUTPUT_DIRECTORY: Output directory `%s' does not " "exist and cannot be created\n",outputDir.data()); return; } d.setPath(outputDir); } int j=0; while (i!=-1) // fileName contains path part { if (d.exists()) { bool ok = d.mkdir(fileName.mid(j,i-j)); if (!ok) break; QCString dirName = outputDir+'/'+fileName.left(i); d = QDir(dirName); j = i+1; } i=fileName.find('/',i+1); } } QCString m_templateName; int m_line; }; //---------------------------------------------------------- /** @brief Class representing an 'if' tag in a template */ class TemplateNodeIf : public TemplateNodeCreator { public: TemplateNodeIf(TemplateParser *parser,TemplateNode *parent,int line,const QCString &data) : TemplateNodeCreator(parser,parent,line) { m_ifGuardedNodes.setAutoDelete(TRUE); TRACE(("{TemplateNodeIf(%s)\n",data.data())); if (data.isEmpty()) { parser->warn(m_templateName,line,"missing argument for if tag"); } QStrList stopAt; stopAt.append("endif"); stopAt.append("elif"); stopAt.append("else"); // if 'nodes' GuardedNodes *guardedNodes = new GuardedNodes; ExpressionParser ex(parser,line); guardedNodes->line = line; guardedNodes->guardAst = ex.parse(data); parser->parse(this,line,stopAt,guardedNodes->trueNodes); m_ifGuardedNodes.append(guardedNodes); TemplateToken *tok = parser->takeNextToken(); // elif 'nodes' while (tok && tok->data.left(5)=="elif ") { ExpressionParser ex(parser,line); guardedNodes = new GuardedNodes; guardedNodes->line = tok->line; guardedNodes->guardAst = ex.parse(tok->data.mid(5)); parser->parse(this,tok->line,stopAt,guardedNodes->trueNodes); m_ifGuardedNodes.append(guardedNodes); // proceed to the next token delete tok; tok = parser->takeNextToken(); } // else 'nodes' if (tok && tok->data=="else") { stopAt.removeLast(); // remove "else" stopAt.removeLast(); // remove "elif" parser->parse(this,line,stopAt,m_falseNodes); parser->removeNextToken(); // skip over endif } delete tok; TRACE(("}TemplateNodeIf(%s)\n",data.data())); } ~TemplateNodeIf() { } void render(FTextStream &ts, TemplateContext *c) { TemplateContextImpl* ci = dynamic_cast(c); if (ci==0) return; // should not happen ci->setLocation(m_templateName,m_line); //printf("TemplateNodeIf::render #trueNodes=%d #falseNodes=%d\n",m_trueNodes.count(),m_falseNodes.count()); bool processed=FALSE; QListIterator li(m_ifGuardedNodes); GuardedNodes *nodes; for (li.toFirst();(nodes=li.current()) && !processed;++li) { if (nodes->guardAst) { TemplateVariant guardValue = nodes->guardAst->resolve(c); if (guardValue.toBool()) // render nodes for the first guard that evaluated to 'true' { nodes->trueNodes.render(ts,c); processed=TRUE; } } else { ci->warn(m_templateName,nodes->line,"invalid expression for if/elif"); } } if (!processed) { // all guards are false, render 'else' nodes m_falseNodes.render(ts,c); } } private: struct GuardedNodes { GuardedNodes() : guardAst(0) {} ~GuardedNodes() { delete guardAst; } int line; ExprAst *guardAst; TemplateNodeList trueNodes; }; QList m_ifGuardedNodes; TemplateNodeList m_falseNodes; }; //---------------------------------------------------------- /** @brief Class representing a 'for' tag in a template */ class TemplateNodeRepeat : public TemplateNodeCreator { public: TemplateNodeRepeat(TemplateParser *parser,TemplateNode *parent,int line,const QCString &data) : TemplateNodeCreator(parser,parent,line) { TRACE(("{TemplateNodeRepeat(%s)\n",data.data())); ExpressionParser expParser(parser,line); m_expr = expParser.parse(data); QStrList stopAt; stopAt.append("endrepeat"); parser->parse(this,line,stopAt,m_repeatNodes); parser->removeNextToken(); // skip over endrepeat TRACE(("}TemplateNodeRepeat(%s)\n",data.data())); } ~TemplateNodeRepeat() { delete m_expr; } void render(FTextStream &ts, TemplateContext *c) { TemplateContextImpl* ci = dynamic_cast(c); if (ci==0) return; // should not happen ci->setLocation(m_templateName,m_line); TemplateVariant v; if (m_expr && (v=m_expr->resolve(c)).type()==TemplateVariant::Integer) { int i, n = v.toInt(); for (i=0;i s(TemplateStruct::alloc()); s->set("counter0", (int)i); s->set("counter", (int)(i+1)); s->set("revcounter", (int)(n-i)); s->set("revcounter0", (int)(n-i-1)); s->set("first",i==0); s->set("last", i==n-1); c->set("repeatloop",s.get()); // render all items for this iteration of the loop m_repeatNodes.render(ts,c); } } else // simple type... { ci->warn(m_templateName,m_line,"for requires a variable of list type!"); } } private: TemplateNodeList m_repeatNodes; ExprAst *m_expr; }; //---------------------------------------------------------- /** @brief Class representing a 'range' tag in a template */ class TemplateNodeRange : public TemplateNodeCreator { public: TemplateNodeRange(TemplateParser *parser,TemplateNode *parent,int line,const QCString &data) : TemplateNodeCreator(parser,parent,line), m_down(FALSE) { TRACE(("{TemplateNodeRange(%s)\n",data.data())); QCString start,end; int i1 = data.find(" from "); int i2 = data.find(" to "); int i3 = data.find(" downto "); if (i1==-1) { if (data.right(5)==" from") { parser->warn(m_templateName,line,"range missing after 'from' keyword"); } else if (data=="from") { parser->warn(m_templateName,line,"range needs an iterator variable and a range"); } else { parser->warn(m_templateName,line,"range is missing 'from' keyword"); } } else if (i2==-1 && i3==-1) { if (data.right(3)==" to") { parser->warn(m_templateName,line,"range is missing end value after 'to' keyword"); } else if (data.right(7)==" downto") { parser->warn(m_templateName,line,"range is missing end value after 'downto' keyword"); } else { parser->warn(m_templateName,line,"range is missing 'to' or 'downto' keyword"); } } else { m_var = data.left(i1).stripWhiteSpace(); if (m_var.isEmpty()) { parser->warn(m_templateName,line,"range needs an iterator variable"); } start = data.mid(i1+6,i2-i1-6).stripWhiteSpace(); if (i2!=-1) { end = data.right(data.length()-i2-4).stripWhiteSpace(); m_down = FALSE; } else if (i3!=-1) { end = data.right(data.length()-i3-8).stripWhiteSpace(); m_down = TRUE; } } ExpressionParser expParser(parser,line); m_startExpr = expParser.parse(start); m_endExpr = expParser.parse(end); QStrList stopAt; stopAt.append("endrange"); parser->parse(this,line,stopAt,m_loopNodes); parser->removeNextToken(); // skip over endrange TRACE(("}TemplateNodeRange(%s)\n",data.data())); } ~TemplateNodeRange() { delete m_startExpr; delete m_endExpr; } void render(FTextStream &ts, TemplateContext *c) { TemplateContextImpl* ci = dynamic_cast(c); if (ci==0) return; // should not happen ci->setLocation(m_templateName,m_line); //printf("TemplateNodeRange::render #loopNodes=%d\n", // m_loopNodes.count()); if (m_startExpr && m_endExpr) { TemplateVariant vs = m_startExpr->resolve(c); TemplateVariant ve = m_endExpr->resolve(c); if (vs.type()==TemplateVariant::Integer && ve.type()==TemplateVariant::Integer) { int s = vs.toInt(); int e = ve.toInt(); int l = m_down ? s-e+1 : e-s+1; if (l>0) { c->push(); //int index = m_reversed ? list.count() : 0; const TemplateVariant *parentLoop = c->getRef("forloop"); uint index = 0; int i = m_down ? e : s; bool done=false; while (!done) { // set the forloop meta-data variable TemplateAutoRef s(TemplateStruct::alloc()); s->set("counter0", (int)index); s->set("counter", (int)(index+1)); s->set("revcounter", (int)(l-index)); s->set("revcounter0", (int)(l-index-1)); s->set("first",index==0); s->set("last", (int)index==l-1); s->set("parentloop",parentLoop ? *parentLoop : TemplateVariant()); c->set("forloop",s.get()); // set the iterator variable c->set(m_var,i); // render all items for this iteration of the loop m_loopNodes.render(ts,c); index++; if (m_down) { i--; done = ie; } } c->pop(); } else { ci->warn(m_templateName,m_line,"range %d %s %d is empty!", s,m_down?"downto":"to",e); } } else if (vs.type()!=TemplateVariant::Integer) { ci->warn(m_templateName,m_line,"range requires a start value of integer type!"); } else if (ve.type()!=TemplateVariant::Integer) { ci->warn(m_templateName,m_line,"range requires an end value of integer type!"); } } else if (!m_startExpr) { ci->warn(m_templateName,m_line,"range has empty start value"); } else if (!m_endExpr) { ci->warn(m_templateName,m_line,"range has empty end value"); } } private: bool m_down; ExprAst *m_startExpr; ExprAst *m_endExpr; QCString m_var; TemplateNodeList m_loopNodes; }; //---------------------------------------------------------- /** @brief Class representing a 'for' tag in a template */ class TemplateNodeFor : public TemplateNodeCreator { public: TemplateNodeFor(TemplateParser *parser,TemplateNode *parent,int line,const QCString &data) : TemplateNodeCreator(parser,parent,line), m_reversed(FALSE) { TRACE(("{TemplateNodeFor(%s)\n",data.data())); QCString exprStr; int i = data.find(" in "); if (i==-1) { if (data.right(3)==" in") { parser->warn(m_templateName,line,"for is missing container after 'in' keyword"); } else if (data=="in") { parser->warn(m_templateName,line,"for needs at least one iterator variable"); } else { parser->warn(m_templateName,line,"for is missing 'in' keyword"); } } else { m_vars = split(data.left(i),","); if (m_vars.count()==0) { parser->warn(m_templateName,line,"for needs at least one iterator variable"); } int j = data.find(" reversed",i); m_reversed = (j!=-1); if (j==-1) j=data.length(); if (j>i+4) { exprStr = data.mid(i+4,j-i-4); // skip over " in " part } if (exprStr.isEmpty()) { parser->warn(m_templateName,line,"for is missing container after 'in' keyword"); } } ExpressionParser expParser(parser,line); m_expr = expParser.parse(exprStr); QStrList stopAt; stopAt.append("endfor"); stopAt.append("empty"); parser->parse(this,line,stopAt,m_loopNodes); TemplateToken *tok = parser->takeNextToken(); if (tok && tok->data=="empty") { stopAt.removeLast(); parser->parse(this,line,stopAt,m_emptyNodes); parser->removeNextToken(); // skip over endfor } delete tok; TRACE(("}TemplateNodeFor(%s)\n",data.data())); } ~TemplateNodeFor() { delete m_expr; } void render(FTextStream &ts, TemplateContext *c) { TemplateContextImpl* ci = dynamic_cast(c); if (ci==0) return; // should not happen ci->setLocation(m_templateName,m_line); //printf("TemplateNodeFor::render #loopNodes=%d #emptyNodes=%d\n", // m_loopNodes.count(),m_emptyNodes.count()); if (m_expr) { TemplateVariant v = m_expr->resolve(c); if (v.type()==TemplateVariant::Function) { v = v.call(QValueList()); } const TemplateListIntf *list = v.toList(); if (list) { uint listSize = list->count(); if (listSize==0) // empty for loop { m_emptyNodes.render(ts,c); return; } c->push(); //int index = m_reversed ? list.count() : 0; TemplateVariant v; const TemplateVariant *parentLoop = c->getRef("forloop"); uint index = m_reversed ? listSize-1 : 0; TemplateListIntf::ConstIterator *it = list->createIterator(); for (m_reversed ? it->toLast() : it->toFirst(); (it->current(v)); m_reversed ? it->toPrev() : it->toNext()) { TemplateAutoRef s(TemplateStruct::alloc()); s->set("counter0", (int)index); s->set("counter", (int)(index+1)); s->set("revcounter", (int)(listSize-index)); s->set("revcounter0", (int)(listSize-index-1)); s->set("first",index==0); s->set("last", index==listSize-1); s->set("parentloop",parentLoop ? *parentLoop : TemplateVariant()); c->set("forloop",s.get()); // add variables for this loop to the context //obj->addVariableToContext(index,m_vars,c); uint vi=0; if (m_vars.count()==1) // loop variable represents an item { c->set(m_vars[vi++],v); } else if (m_vars.count()>1 && v.type()==TemplateVariant::Struct) // loop variables represent elements in a list item { for (uint i=0;iset(m_vars[vi],v.toStruct()->get(m_vars[vi])); } } for (;viset(m_vars[vi],TemplateVariant()); } // render all items for this iteration of the loop m_loopNodes.render(ts,c); if (m_reversed) index--; else index++; } c->pop(); delete it; } else // simple type... { ci->warn(m_templateName,m_line,"for requires a variable of list type, got type '%s'!",v.typeAsString().data()); } } } private: bool m_reversed; ExprAst *m_expr; QValueList m_vars; TemplateNodeList m_loopNodes; TemplateNodeList m_emptyNodes; }; //---------------------------------------------------------- /** @brief Class representing an 'markers' tag in a template */ class TemplateNodeMsg : public TemplateNodeCreator { public: TemplateNodeMsg(TemplateParser *parser,TemplateNode *parent,int line,const QCString &) : TemplateNodeCreator(parser,parent,line) { TRACE(("{TemplateNodeMsg()\n")); QStrList stopAt; stopAt.append("endmsg"); parser->parse(this,line,stopAt,m_nodes); parser->removeNextToken(); // skip over endmsg TRACE(("}TemplateNodeMsg()\n")); } void render(FTextStream &, TemplateContext *c) { TemplateContextImpl* ci = dynamic_cast(c); if (ci==0) return; // should not happen ci->setLocation(m_templateName,m_line); TemplateEscapeIntf *escIntf = ci->escapeIntf(); ci->setActiveEscapeIntf(0); // avoid escaping things we send to standard out bool enable = ci->spacelessEnabled(); ci->enableSpaceless(FALSE); FTextStream ts(stdout); m_nodes.render(ts,c); ts << endl; ci->setActiveEscapeIntf(escIntf); ci->enableSpaceless(enable); } private: TemplateNodeList m_nodes; }; //---------------------------------------------------------- /** @brief Class representing a 'block' tag in a template */ class TemplateNodeBlock : public TemplateNodeCreator { public: TemplateNodeBlock(TemplateParser *parser,TemplateNode *parent,int line,const QCString &data) : TemplateNodeCreator(parser,parent,line) { TRACE(("{TemplateNodeBlock(%s)\n",data.data())); m_blockName = data; if (m_blockName.isEmpty()) { parser->warn(parser->templateName(),line,"block tag without name"); } QStrList stopAt; stopAt.append("endblock"); parser->parse(this,line,stopAt,m_nodes); parser->removeNextToken(); // skip over endblock TRACE(("}TemplateNodeBlock(%s)\n",data.data())); } void render(FTextStream &ts, TemplateContext *c) { TemplateContextImpl *ci = dynamic_cast(c); if (ci==0) return; // should not happen ci->setLocation(m_templateName,m_line); TemplateImpl *t = getTemplate(); if (t) { // remove block from the context, so block.super can work TemplateNodeBlock *nb = ci->blockContext()->pop(m_blockName); if (nb) // block is overruled { ci->push(); QGString super; FTextStream ss(&super); // get super block of block nb TemplateNodeBlock *sb = ci->blockContext()->get(m_blockName); if (sb && sb!=nb && sb!=this) // nb and sb both overrule this block { sb->render(ss,c); // render parent of nb to string } else if (nb!=this) // only nb overrules this block { m_nodes.render(ss,c); // render parent of nb to string } // add 'block.super' variable to allow access to parent block content TemplateAutoRef superBlock(TemplateStruct::alloc()); superBlock->set("super",TemplateVariant(super.data(),TRUE)); ci->set("block",superBlock.get()); // render the overruled block contents t->engine()->enterBlock(nb->m_templateName,nb->m_blockName,nb->m_line); nb->m_nodes.render(ts,c); t->engine()->leaveBlock(); ci->pop(); // re-add block to the context ci->blockContext()->push(nb); } else // block has no overrule { t->engine()->enterBlock(m_templateName,m_blockName,m_line); m_nodes.render(ts,c); t->engine()->leaveBlock(); } } } QCString name() const { return m_blockName; } private: QCString m_blockName; TemplateNodeList m_nodes; }; //---------------------------------------------------------- /** @brief Class representing a 'extend' tag in a template */ class TemplateNodeExtend : public TemplateNodeCreator { public: TemplateNodeExtend(TemplateParser *parser,TemplateNode *parent,int line,const QCString &data) : TemplateNodeCreator(parser,parent,line) { TRACE(("{TemplateNodeExtend(%s)\n",data.data())); ExpressionParser ep(parser,line); if (data.isEmpty()) { parser->warn(m_templateName,line,"extend tag is missing template file argument"); } m_extendExpr = ep.parse(data); QStrList stopAt; parser->parse(this,line,stopAt,m_nodes); TRACE(("}TemplateNodeExtend(%s)\n",data.data())); } ~TemplateNodeExtend() { delete m_extendExpr; } void render(FTextStream &ts, TemplateContext *c) { TemplateContextImpl* ci = dynamic_cast(c); if (ci==0) return; // should not happen ci->setLocation(m_templateName,m_line); if (m_extendExpr==0) return; QCString extendFile = m_extendExpr->resolve(c).toString(); if (extendFile.isEmpty()) { ci->warn(m_templateName,m_line,"invalid parameter for extend command"); } // goto root of tree (template node) TemplateImpl *t = getTemplate(); if (t) { Template *bt = t->engine()->loadByName(extendFile,m_line); TemplateImpl *baseTemplate = bt ? dynamic_cast(bt) : 0; if (baseTemplate) { // fill block context TemplateBlockContext *bc = ci->blockContext(); // add overruling blocks to the context QListIterator li(m_nodes); TemplateNode *n; for (li.toFirst();(n=li.current());++li) { TemplateNodeBlock *nb = dynamic_cast(n); if (nb) { bc->add(nb); } TemplateNodeMsg *msg = dynamic_cast(n); if (msg) { msg->render(ts,c); } } // render the base template with the given context baseTemplate->render(ts,c); // clean up bc->clear(); t->engine()->unload(t); } else { ci->warn(m_templateName,m_line,"failed to load template %s for extend",extendFile.data()); } } } private: ExprAst *m_extendExpr; TemplateNodeList m_nodes; }; /** @brief Class representing an 'include' tag in a template */ class TemplateNodeInclude : public TemplateNodeCreator { public: TemplateNodeInclude(TemplateParser *parser,TemplateNode *parent,int line,const QCString &data) : TemplateNodeCreator(parser,parent,line) { TRACE(("TemplateNodeInclude(%s)\n",data.data())); ExpressionParser ep(parser,line); if (data.isEmpty()) { parser->warn(m_templateName,line,"include tag is missing template file argument"); } m_includeExpr = ep.parse(data); } ~TemplateNodeInclude() { delete m_includeExpr; } void render(FTextStream &ts, TemplateContext *c) { TemplateContextImpl* ci = dynamic_cast(c); if (ci==0) return; // should not happen ci->setLocation(m_templateName,m_line); if (m_includeExpr) { QCString includeFile = m_includeExpr->resolve(c).toString(); if (includeFile.isEmpty()) { ci->warn(m_templateName,m_line,"invalid parameter for include command\n"); } else { TemplateImpl *t = getTemplate(); if (t) { Template *it = t->engine()->loadByName(includeFile,m_line); TemplateImpl *incTemplate = it ? dynamic_cast(it) : 0; if (incTemplate) { incTemplate->render(ts,c); t->engine()->unload(t); } else { ci->warn(m_templateName,m_line,"failed to load template '%s' for include",includeFile.data()?includeFile.data():""); } } } } } private: ExprAst *m_includeExpr; }; //---------------------------------------------------------- static void stripLeadingWhiteSpace(QGString &s) { const char *src = s.data(); if (src) { char *dst = s.data(); char c; bool skipSpaces=TRUE; while ((c=*src++)) { if (c=='\n') { *dst++=c; skipSpaces=TRUE; } else if (c==' ' && skipSpaces) {} else { *dst++=c; skipSpaces=FALSE; } } *dst='\0'; } } /** @brief Class representing an 'create' tag in a template */ class TemplateNodeCreate : public TemplateNodeCreator { public: TemplateNodeCreate(TemplateParser *parser,TemplateNode *parent,int line,const QCString &data) : TemplateNodeCreator(parser,parent,line), m_templateExpr(0), m_fileExpr(0) { TRACE(("TemplateNodeCreate(%s)\n",data.data())); ExpressionParser ep(parser,line); if (data.isEmpty()) { parser->warn(m_templateName,line,"create tag is missing arguments"); } int i = data.find(" from "); if (i==-1) { if (data.right(3)==" from") { parser->warn(m_templateName,line,"create is missing template name after 'from' keyword"); } else if (data=="from") { parser->warn(m_templateName,line,"create needs a file name and a template name"); } else { parser->warn(m_templateName,line,"create is missing 'from' keyword"); } } else { ExpressionParser ep(parser,line); m_fileExpr = ep.parse(data.left(i).stripWhiteSpace()); m_templateExpr = ep.parse(data.mid(i+6).stripWhiteSpace()); } } ~TemplateNodeCreate() { delete m_templateExpr; delete m_fileExpr; } void render(FTextStream &, TemplateContext *c) { TemplateContextImpl* ci = dynamic_cast(c); if (ci==0) return; // should not happen ci->setLocation(m_templateName,m_line); if (m_templateExpr && m_fileExpr) { QCString templateFile = m_templateExpr->resolve(c).toString(); QCString outputFile = m_fileExpr->resolve(c).toString(); if (templateFile.isEmpty()) { ci->warn(m_templateName,m_line,"empty template name parameter for create command\n"); } else if (outputFile.isEmpty()) { ci->warn(m_templateName,m_line,"empty file name parameter for create command\n"); } else { TemplateImpl *t = getTemplate(); if (t) { QCString extension=outputFile; int i=extension.findRev('.'); if (i!=-1) { extension=extension.right(extension.length()-i-1); } t->engine()->setOutputExtension(extension); Template *ct = t->engine()->loadByName(templateFile,m_line); TemplateImpl *createTemplate = ct ? dynamic_cast(ct) : 0; if (createTemplate) { mkpath(ci,outputFile); if (!ci->outputDirectory().isEmpty()) { outputFile.prepend(ci->outputDirectory()+"/"); } //printf("NoteCreate(%s)\n",outputFile.data()); QFile f(outputFile); if (f.open(IO_WriteOnly)) { TemplateEscapeIntf *escIntf = ci->escapeIntf(); ci->selectEscapeIntf(extension); FTextStream ts(&f); QGString out; FTextStream os(&out); createTemplate->render(os,c); stripLeadingWhiteSpace(out); ts << out; t->engine()->unload(t); ci->setActiveEscapeIntf(escIntf); } else { ci->warn(m_templateName,m_line,"failed to open output file '%s' for create command",outputFile.data()); } } else { ci->warn(m_templateName,m_line,"failed to load template '%s' for include",templateFile.data()); } t->engine()->setOutputExtension(""); } } } } private: ExprAst *m_templateExpr; ExprAst *m_fileExpr; }; //---------------------------------------------------------- /** @brief Class representing an 'tree' tag in a template */ class TemplateNodeTree : public TemplateNodeCreator { struct TreeContext { TreeContext(TemplateNodeTree *o,const TemplateListIntf *l,TemplateContext *c) : object(o), list(l), templateCtx(c) {} TemplateNodeTree *object; const TemplateListIntf *list; TemplateContext *templateCtx; }; public: TemplateNodeTree(TemplateParser *parser,TemplateNode *parent,int line,const QCString &data) : TemplateNodeCreator(parser,parent,line) { TRACE(("{TemplateNodeTree(%s)\n",data.data())); ExpressionParser ep(parser,line); if (data.isEmpty()) { parser->warn(m_templateName,line,"recursetree tag is missing data argument"); } m_treeExpr = ep.parse(data); QStrList stopAt; stopAt.append("endrecursetree"); parser->parse(this,line,stopAt,m_treeNodes); parser->removeNextToken(); // skip over endrecursetree TRACE(("}TemplateNodeTree(%s)\n",data.data())); } ~TemplateNodeTree() { delete m_treeExpr; } static TemplateVariant renderChildrenStub(const void *ctx, const QValueList &) { return TemplateVariant(((TreeContext*)ctx)->object-> renderChildren((const TreeContext*)ctx),TRUE); } QCString renderChildren(const TreeContext *ctx) { //printf("TemplateNodeTree::renderChildren(%d)\n",ctx->list->count()); // render all children of node to a string and return it TemplateContext *c = ctx->templateCtx; TemplateContextImpl* ci = dynamic_cast(c); if (ci==0) return QCString(); // should not happen QGString result; FTextStream ss(&result); c->push(); TemplateVariant node; TemplateListIntf::ConstIterator *it = ctx->list->createIterator(); for (it->toFirst();(it->current(node));it->toNext()) { c->set("node",node); bool hasChildren=FALSE; const TemplateStructIntf *ns = node.toStruct(); if (ns) // node is a struct { TemplateVariant v = ns->get("children"); if (v.isValid()) // with a field 'children' { const TemplateListIntf *list = v.toList(); if (list && list->count()>0) // non-empty list { TreeContext childCtx(this,list,ctx->templateCtx); TemplateVariant children(TemplateVariant::Delegate::fromFunction(&childCtx,renderChildrenStub)); children.setRaw(TRUE); c->set("children",children); m_treeNodes.render(ss,c); hasChildren=TRUE; } else if (list==0) { ci->warn(m_templateName,m_line,"recursetree: children attribute has type '%s' instead of list\n",v.typeAsString().data()); } } //else //{ // ci->warn(m_templateName,m_line,"recursetree: children attribute is not valid"); //} } if (!hasChildren) { c->set("children",TemplateVariant("")); // provide default m_treeNodes.render(ss,c); } } c->pop(); delete it; return result.data(); } void render(FTextStream &ts, TemplateContext *c) { //printf("TemplateNodeTree::render()\n"); TemplateContextImpl* ci = dynamic_cast(c); if (ci==0) return; // should not happen ci->setLocation(m_templateName,m_line); TemplateVariant v = m_treeExpr->resolve(c); const TemplateListIntf *list = v.toList(); if (list) { TreeContext ctx(this,list,c); ts << renderChildren(&ctx); } else { ci->warn(m_templateName,m_line,"recursetree's argument should be a list type"); } } private: ExprAst *m_treeExpr; TemplateNodeList m_treeNodes; }; //---------------------------------------------------------- /** @brief Class representing an 'indexentry' tag in a template */ class TemplateNodeIndexEntry : public TemplateNodeCreator { struct Mapping { Mapping(const QCString &n,ExprAst *e) : name(n), value(e) {} ~Mapping() { delete value; } QCString name; ExprAst *value; }; public: TemplateNodeIndexEntry(TemplateParser *parser,TemplateNode *parent,int line,const QCString &data) : TemplateNodeCreator(parser,parent,line) { TRACE(("{TemplateNodeIndexEntry(%s)\n",data.data())); m_args.setAutoDelete(TRUE); ExpressionParser expParser(parser,line); QValueList args = split(data," "); QValueListIterator it = args.begin(); if (it==args.end() || (*it).find('=')!=-1) { parser->warn(parser->templateName(),line,"Missing name for indexentry tag"); } else { m_name = *it; ++it; while (it!=args.end()) { QCString arg = *it; int j=arg.find('='); if (j>0) { ExprAst *expr = expParser.parse(arg.mid(j+1)); if (expr) { m_args.append(new Mapping(arg.left(j),expr)); } } else { parser->warn(parser->templateName(),line,"invalid argument '%s' for indexentry tag",arg.data()); } ++it; } } TRACE(("}TemplateNodeIndexEntry(%s)\n",data.data())); } void render(FTextStream &, TemplateContext *c) { TemplateContextImpl *ci = dynamic_cast(c); if (ci==0) return; // should not happen if (!m_name.isEmpty()) { ci->setLocation(m_templateName,m_line); QListIterator it(m_args); Mapping *mapping; QValueList list; for (it.toFirst();(mapping=it.current());++it) { list.append(TemplateKeyValue(mapping->name,mapping->value->resolve(c))); } ci->addIndexEntry(m_name,list); } } private: QCString m_name; QList m_args; }; //---------------------------------------------------------- /** @brief Class representing an 'opensubindex' tag in a template */ class TemplateNodeOpenSubIndex : public TemplateNodeCreator { public: TemplateNodeOpenSubIndex(TemplateParser *parser,TemplateNode *parent,int line,const QCString &data) : TemplateNodeCreator(parser,parent,line) { TRACE(("{TemplateNodeOpenSubIndex(%s)\n",data.data())); m_name = data.stripWhiteSpace(); if (m_name.isEmpty()) { parser->warn(parser->templateName(),line,"Missing argument for opensubindex tag"); } else if (m_name.find(' ')!=-1) { parser->warn(parser->templateName(),line,"Expected single argument for opensubindex tag got '%s'",data.data()); m_name=""; } TRACE(("}TemplateNodeOpenSubIndex(%s)\n",data.data())); } void render(FTextStream &, TemplateContext *c) { TemplateContextImpl *ci = dynamic_cast(c); if (ci==0) return; // should not happen if (!m_name.isEmpty()) { ci->setLocation(m_templateName,m_line); ci->openSubIndex(m_name); } } private: QCString m_name; }; //---------------------------------------------------------- /** @brief Class representing an 'closesubindex' tag in a template */ class TemplateNodeCloseSubIndex : public TemplateNodeCreator { public: TemplateNodeCloseSubIndex(TemplateParser *parser,TemplateNode *parent,int line,const QCString &data) : TemplateNodeCreator(parser,parent,line) { TRACE(("{TemplateNodeCloseSubIndex(%s)\n",data.data())); m_name = data.stripWhiteSpace(); if (m_name.isEmpty()) { parser->warn(parser->templateName(),line,"Missing argument for closesubindex tag"); } else if (m_name.find(' ')!=-1 || m_name.isEmpty()) { parser->warn(parser->templateName(),line,"Expected single argument for closesubindex tag got '%s'",data.data()); m_name=""; } TRACE(("}TemplateNodeCloseSubIndex(%s)\n",data.data())); } void render(FTextStream &, TemplateContext *c) { TemplateContextImpl *ci = dynamic_cast(c); if (ci==0) return; // should not happen if (!m_name.isEmpty()) { ci->setLocation(m_templateName,m_line); ci->closeSubIndex(m_name); } } private: QCString m_name; }; //---------------------------------------------------------- /** @brief Class representing an 'with' tag in a template */ class TemplateNodeWith : public TemplateNodeCreator { struct Mapping { Mapping(const QCString &n,ExprAst *e) : name(n), value(e) {} ~Mapping() { delete value; } QCString name; ExprAst *value; }; public: TemplateNodeWith(TemplateParser *parser,TemplateNode *parent,int line,const QCString &data) : TemplateNodeCreator(parser,parent,line) { TRACE(("{TemplateNodeWith(%s)\n",data.data())); m_args.setAutoDelete(TRUE); ExpressionParser expParser(parser,line); QCString filteredData = removeSpacesAroundEquals(data); QValueList args = split(filteredData," "); QValueListIterator it = args.begin(); while (it!=args.end()) { QCString arg = *it; int j=arg.find('='); if (j>0) { ExprAst *expr = expParser.parse(arg.mid(j+1)); if (expr) { m_args.append(new Mapping(arg.left(j),expr)); } } else { parser->warn(parser->templateName(),line,"invalid argument '%s' for 'with' tag",arg.data()); } ++it; } QStrList stopAt; stopAt.append("endwith"); parser->parse(this,line,stopAt,m_nodes); parser->removeNextToken(); // skip over endwith TRACE(("}TemplateNodeWith(%s)\n",data.data())); } ~TemplateNodeWith() { } void render(FTextStream &ts, TemplateContext *c) { TemplateContextImpl *ci = dynamic_cast(c); if (ci==0) return; // should not happen ci->setLocation(m_templateName,m_line); c->push(); QListIterator it(m_args); Mapping *mapping; for (it.toFirst();(mapping=it.current());++it) { TemplateVariant value = mapping->value->resolve(c); ci->set(mapping->name,value); } m_nodes.render(ts,c); c->pop(); } private: TemplateNodeList m_nodes; QList m_args; }; //---------------------------------------------------------- /** @brief Class representing an 'cycle' tag in a template */ class TemplateNodeCycle : public TemplateNodeCreator { public: TemplateNodeCycle(TemplateParser *parser,TemplateNode *parent,int line,const QCString &data) : TemplateNodeCreator(parser,parent,line) { TRACE(("{TemplateNodeCycle(%s)\n",data.data())); m_args.setAutoDelete(TRUE); m_index=0; ExpressionParser expParser(parser,line); QValueList args = split(data," "); QValueListIterator it = args.begin(); while (it!=args.end()) { ExprAst *expr = expParser.parse(*it); if (expr) { m_args.append(expr); } ++it; } if (m_args.count()<2) { parser->warn(parser->templateName(),line,"expected at least two arguments for cycle command, got %d",m_args.count()); } TRACE(("}TemplateNodeCycle(%s)\n",data.data())); } void render(FTextStream &ts, TemplateContext *c) { TemplateContextImpl *ci = dynamic_cast(c); ci->setLocation(m_templateName,m_line); if (m_indexresolve(c); if (v.type()==TemplateVariant::Function) { v = v.call(QValueList()); } if (ci->escapeIntf() && !v.raw()) { if (ci->needsRecoding()) { ts << ci->recode(ci->escapeIntf()->escape(v.toString())); } else { ts << ci->escapeIntf()->escape(v.toString()); } } else { if (ci->needsRecoding()) { ts << ci->recode(v.toString()); } else { ts << v.toString(); } } } if (++m_index==m_args.count()) // wrap around { m_index=0; } } private: uint m_index; QList m_args; }; //---------------------------------------------------------- /** @brief Class representing an 'set' tag in a template */ class TemplateNodeSet : public TemplateNodeCreator { struct Mapping { Mapping(const QCString &n,ExprAst *e) : name(n), value(e) {} ~Mapping() { delete value; } QCString name; ExprAst *value; }; public: TemplateNodeSet(TemplateParser *parser,TemplateNode *parent,int line,const QCString &data) : TemplateNodeCreator(parser,parent,line), m_mapping(0) { TRACE(("{TemplateNodeSet(%s)\n",data.data())); ExpressionParser expParser(parser,line); // data format: name=expression int j=data.find('='); ExprAst *expr = 0; if (j>0 && (expr = expParser.parse(data.mid(j+1)))) { m_mapping = new Mapping(data.left(j),expr); } TRACE(("}TemplateNodeSet(%s)\n",data.data())); } ~TemplateNodeSet() { delete m_mapping; } void render(FTextStream &, TemplateContext *c) { TemplateContextImpl *ci = dynamic_cast(c); if (ci==0) return; // should not happen ci->setLocation(m_templateName,m_line); if (m_mapping) { TemplateVariant value = m_mapping->value->resolve(c); ci->set(m_mapping->name,value); } } private: Mapping *m_mapping; }; //---------------------------------------------------------- /** @brief Class representing an 'spaceless' tag in a template */ class TemplateNodeSpaceless : public TemplateNodeCreator { public: TemplateNodeSpaceless(TemplateParser *parser,TemplateNode *parent,int line,const QCString &) : TemplateNodeCreator(parser,parent,line) { TRACE(("{TemplateNodeSpaceless()\n")); QStrList stopAt; stopAt.append("endspaceless"); parser->parse(this,line,stopAt,m_nodes); parser->removeNextToken(); // skip over endwith TRACE(("}TemplateNodeSpaceless()\n")); } void render(FTextStream &ts, TemplateContext *c) { TemplateContextImpl *ci = dynamic_cast(c); if (ci==0) return; // should not happen ci->setLocation(m_templateName,m_line); bool wasSpaceless = ci->spacelessEnabled(); ci->enableSpaceless(TRUE); m_nodes.render(ts,c); ci->enableSpaceless(wasSpaceless); } private: TemplateNodeList m_nodes; }; //---------------------------------------------------------- /** @brief Class representing an 'markers' tag in a template */ class TemplateNodeMarkers : public TemplateNodeCreator { public: TemplateNodeMarkers(TemplateParser *parser,TemplateNode *parent,int line,const QCString &data) : TemplateNodeCreator(parser,parent,line), m_listExpr(0), m_patternExpr(0) { TRACE(("{TemplateNodeMarkers(%s)\n",data.data())); int i = data.find(" in "); int w = data.find(" with "); if (i==-1 || w==-1 || wwarn(m_templateName,line,"markers tag as wrong format. Expected: markers in with "); } else { ExpressionParser expParser(parser,line); m_var = data.left(i); m_listExpr = expParser.parse(data.mid(i+4,w-i-4)); m_patternExpr = expParser.parse(data.right(data.length()-w-6)); } QStrList stopAt; stopAt.append("endmarkers"); parser->parse(this,line,stopAt,m_nodes); parser->removeNextToken(); // skip over endmarkers TRACE(("}TemplateNodeMarkers(%s)\n",data.data())); } ~TemplateNodeMarkers() { delete m_listExpr; delete m_patternExpr; } void render(FTextStream &ts, TemplateContext *c) { TemplateContextImpl *ci = dynamic_cast(c); if (ci==0) return; // should not happen ci->setLocation(m_templateName,m_line); if (!m_var.isEmpty() && m_listExpr && m_patternExpr) { TemplateVariant v = m_listExpr->resolve(c); const TemplateListIntf *list = v.toList(); TemplateVariant patternStr = m_patternExpr->resolve(c); if (list) { if (patternStr.type()==TemplateVariant::String) { TemplateListIntf::ConstIterator *it = list->createIterator(); c->push(); QCString str = patternStr.toString(); QRegExp marker("@[0-9]+"); // pattern for a marker, i.e. @0, @1 ... @12, etc int index=0,newIndex,matchLen; while ((newIndex=marker.match(str,index,&matchLen))!=-1) { if (ci->needsRecoding()) { ts << ci->recode(str.mid(index,newIndex-index)); // write text before marker } else { ts << str.mid(index,newIndex-index); // write text before marker } bool ok; uint entryIndex = str.mid(newIndex+1,matchLen-1).toUInt(&ok); // get marker id TemplateVariant var; uint i=0; // search for list element at position id for (it->toFirst(); (it->current(var)) && itoNext(),i++) {} if (ok && i==entryIndex) // found element { TemplateAutoRef s(TemplateStruct::alloc()); s->set("id",(int)i); c->set("markers",s.get()); c->set(m_var,var); // define local variable to hold element of list type bool wasSpaceless = ci->spacelessEnabled(); ci->enableSpaceless(TRUE); m_nodes.render(ts,c); ci->enableSpaceless(wasSpaceless); } else if (!ok) { ci->warn(m_templateName,m_line,"markers pattern string has invalid markers '%s'",str.data()); } else if (iwarn(m_templateName,m_line,"markers list does not an element for marker position %d",i); } index=newIndex+matchLen; // set index just after marker } if (ci->needsRecoding()) { ts << ci->recode(str.right(str.length()-index)); // write text after last marker } else { ts << str.right(str.length()-index); // write text after last marker } c->pop(); delete it; } else { ci->warn(m_templateName,m_line,"markers requires a parameter of string type after 'with'!"); } } else { ci->warn(m_templateName,m_line,"markers requires a parameter of list type after 'in'!"); } } } private: TemplateNodeList m_nodes; QCString m_var; ExprAst *m_listExpr; ExprAst *m_patternExpr; }; //---------------------------------------------------------- /** @brief Class representing an 'tabbing' tag in a template */ class TemplateNodeTabbing : public TemplateNodeCreator { public: TemplateNodeTabbing(TemplateParser *parser,TemplateNode *parent,int line,const QCString &) : TemplateNodeCreator(parser,parent,line) { TRACE(("{TemplateNodeTabbing()\n")); QStrList stopAt; stopAt.append("endtabbing"); parser->parse(this,line,stopAt,m_nodes); parser->removeNextToken(); // skip over endtabbing TRACE(("}TemplateNodeTabbing()\n")); } void render(FTextStream &ts, TemplateContext *c) { TemplateContextImpl *ci = dynamic_cast(c); if (ci==0) return; // should not happen ci->setLocation(m_templateName,m_line); bool wasTabbing = ci->tabbingEnabled(); ci->enableTabbing(TRUE); m_nodes.render(ts,c); ci->enableTabbing(wasTabbing); } private: TemplateNodeList m_nodes; }; //---------------------------------------------------------- /** @brief Class representing an 'markers' tag in a template */ class TemplateNodeResource : public TemplateNodeCreator { public: TemplateNodeResource(TemplateParser *parser,TemplateNode *parent,int line,const QCString &data) : TemplateNodeCreator(parser,parent,line) { TRACE(("{TemplateNodeResource(%s)\n",data.data())); ExpressionParser ep(parser,line); int i; if (data.isEmpty()) { parser->warn(m_templateName,line,"resource tag is missing resource file argument"); m_resExpr=0; m_asExpr=0; } else if ((i=data.find(" as "))!=-1) // resource a as b { m_resExpr = ep.parse(data.left(i)); // part before as m_asExpr = ep.parse(data.mid(i+4)); // part after as } else // resource a { m_resExpr = ep.parse(data); m_asExpr = 0; } TRACE(("}TemplateNodeResource(%s)\n",data.data())); } ~TemplateNodeResource() { delete m_resExpr; delete m_asExpr; } void render(FTextStream &, TemplateContext *c) { TemplateContextImpl *ci = dynamic_cast(c); if (ci==0) return; // should not happen ci->setLocation(m_templateName,m_line); if (m_resExpr) { QCString resourceFile = m_resExpr->resolve(c).toString(); if (resourceFile.isEmpty()) { ci->warn(m_templateName,m_line,"invalid parameter for resource command\n"); } else { QCString outputDirectory = ci->outputDirectory(); if (m_asExpr) { QCString targetFile = m_asExpr->resolve(c).toString(); mkpath(ci,targetFile); if (targetFile.isEmpty()) { ci->warn(m_templateName,m_line,"invalid parameter at right side of 'as' for resource command\n"); } else { ResourceMgr::instance().copyResourceAs(resourceFile,outputDirectory,targetFile); } } else { ResourceMgr::instance().copyResource(resourceFile,outputDirectory); } } } } private: ExprAst *m_resExpr; ExprAst *m_asExpr; }; //---------------------------------------------------------- /** @brief Class representing the 'encoding' tag in a template */ class TemplateNodeEncoding : public TemplateNodeCreator { public: TemplateNodeEncoding(TemplateParser *parser,TemplateNode *parent,int line,const QCString &data) : TemplateNodeCreator(parser,parent,line) { TRACE(("{TemplateNodeEncoding(%s)\n",data.data())); ExpressionParser ep(parser,line); if (data.isEmpty()) { parser->warn(m_templateName,line,"encoding tag is missing encoding argument"); m_encExpr = 0; } else { m_encExpr = ep.parse(data); } QStrList stopAt; stopAt.append("endencoding"); parser->parse(this,line,stopAt,m_nodes); parser->removeNextToken(); // skip over endencoding TRACE(("}TemplateNodeEncoding(%s)\n",data.data())); } ~TemplateNodeEncoding() { delete m_encExpr; } void render(FTextStream &ts, TemplateContext *c) { TemplateContextImpl *ci = dynamic_cast(c); if (ci==0) return; // should not happen ci->setLocation(m_templateName,m_line); QCString encStr; if (m_encExpr) { encStr = m_encExpr->resolve(c).toString(); } QCString oldEncStr = ci->encoding(); if (!encStr.isEmpty()) { ci->setEncoding(m_templateName,m_line,encStr); } m_nodes.render(ts,c); ci->setEncoding(m_templateName,m_line,oldEncStr); } private: ExprAst *m_encExpr; TemplateNodeList m_nodes; }; //---------------------------------------------------------- /** @brief Factory class for creating tag AST nodes found in a template */ class TemplateNodeFactory { public: typedef TemplateNode *(*CreateFunc)(TemplateParser *parser, TemplateNode *parent, int line, const QCString &data); static TemplateNodeFactory *instance() { static TemplateNodeFactory *instance = 0; if (instance==0) instance = new TemplateNodeFactory; return instance; } TemplateNode *create(const QCString &name, TemplateParser *parser, TemplateNode *parent, int line, const QCString &data) { if (m_registry.find(name)==0) return 0; return ((CreateFunc)m_registry[name])(parser,parent,line,data); } void registerTemplateNode(const QCString &name,CreateFunc func) { m_registry.insert(name,(void*)func); } /** @brief Helper class for registering a template AST node */ template class AutoRegister { public: AutoRegister(const QCString &key) { TemplateNodeFactory::instance()->registerTemplateNode(key,T::createInstance); } }; private: QDict m_registry; }; // register a handler for each start tag we support static TemplateNodeFactory::AutoRegister autoRefIf("if"); static TemplateNodeFactory::AutoRegister autoRefFor("for"); static TemplateNodeFactory::AutoRegister autoRefMsg("msg"); static TemplateNodeFactory::AutoRegister autoRefSet("set"); static TemplateNodeFactory::AutoRegister autoRefTree("recursetree"); static TemplateNodeFactory::AutoRegister autoRefWith("with"); static TemplateNodeFactory::AutoRegister autoRefBlock("block"); static TemplateNodeFactory::AutoRegister autoRefCycle("cycle"); static TemplateNodeFactory::AutoRegister autoRefRange("range"); static TemplateNodeFactory::AutoRegister autoRefExtend("extend"); static TemplateNodeFactory::AutoRegister autoRefCreate("create"); static TemplateNodeFactory::AutoRegister autoRefRepeat("repeat"); static TemplateNodeFactory::AutoRegister autoRefInclude("include"); static TemplateNodeFactory::AutoRegister autoRefMarkers("markers"); static TemplateNodeFactory::AutoRegister autoRefTabbing("tabbing"); static TemplateNodeFactory::AutoRegister autoRefResource("resource"); static TemplateNodeFactory::AutoRegister autoRefEncoding("encoding"); static TemplateNodeFactory::AutoRegister autoRefSpaceless("spaceless"); static TemplateNodeFactory::AutoRegister autoRefIndexEntry("indexentry"); static TemplateNodeFactory::AutoRegister autoRefOpenSubIndex("opensubindex"); static TemplateNodeFactory::AutoRegister autoRefCloseSubIndex("closesubindex"); //---------------------------------------------------------- TemplateBlockContext::TemplateBlockContext() : m_blocks(257) { m_blocks.setAutoDelete(TRUE); } TemplateNodeBlock *TemplateBlockContext::get(const QCString &name) const { QList *list = m_blocks.find(name); if (list==0 || list->count()==0) { return 0; } else { return list->getLast(); } } TemplateNodeBlock *TemplateBlockContext::pop(const QCString &name) const { QList *list = m_blocks.find(name); if (list==0 || list->count()==0) { return 0; } else { return list->take(list->count()-1); } } void TemplateBlockContext::add(TemplateNodeBlock *block) { QList *list = m_blocks.find(block->name()); if (list==0) { list = new QList; m_blocks.insert(block->name(),list); } list->prepend(block); } void TemplateBlockContext::add(TemplateBlockContext *ctx) { QDictIterator< QList > di(ctx->m_blocks); QList *list; for (di.toFirst();(list=di.current());++di) { QListIterator li(*list); TemplateNodeBlock *nb; for (li.toFirst();(nb=li.current());++li) { add(nb); } } } void TemplateBlockContext::clear() { m_blocks.clear(); } void TemplateBlockContext::push(TemplateNodeBlock *block) { QList *list = m_blocks.find(block->name()); if (list==0) { list = new QList; m_blocks.insert(block->name(),list); } list->append(block); } //---------------------------------------------------------- /** @brief Lexer class for turning a template into a list of tokens */ class TemplateLexer { public: TemplateLexer(const TemplateEngine *engine,const QCString &fileName,const QCString &data); void tokenize(QList &tokens); void setOpenCloseCharacters(char openChar,char closeChar) { m_openChar=openChar; m_closeChar=closeChar; } private: void addToken(QList &tokens, const char *data,int line,int startPos,int endPos, TemplateToken::Type type); void reset(); const TemplateEngine *m_engine; QCString m_fileName; QCString m_data; char m_openChar; char m_closeChar; }; TemplateLexer::TemplateLexer(const TemplateEngine *engine,const QCString &fileName,const QCString &data) : m_engine(engine), m_fileName(fileName), m_data(data) { m_openChar='{'; m_closeChar='}'; } void TemplateLexer::tokenize(QList &tokens) { enum LexerStates { StateText, StateBeginTemplate, StateTag, StateEndTag, StateComment, StateEndComment, StateMaybeVar, StateVariable, StateEndVariable }; const char *p=m_data.data(); if (p==0) return; int state=StateText; int pos=0; int lastTokenPos=0; int startLinePos=0; bool emptyOutputLine=TRUE; int line=1; char c; int markStartPos=-1; for (;(c=*p);p++,pos++) { switch (state) { case StateText: if (c==m_openChar) // {{ or {% or {# or something else { state=StateBeginTemplate; } else if (c!=' ' && c!='\t' && c!='\n') // non-whitepace text { emptyOutputLine=FALSE; } break; case StateBeginTemplate: switch (c) { case '%': // {% state=StateTag; markStartPos=pos-1; break; case '#': // {# state=StateComment; markStartPos=pos-1; break; case '{': // {{ if (m_openChar=='{') { state=StateMaybeVar; } else { state=StateVariable; } markStartPos=pos-1; break; default: state=StateText; emptyOutputLine=FALSE; break; } break; case StateTag: if (c=='\n') { warn(m_fileName,line,"unexpected new line inside %c%%...%%%c block",m_openChar,m_closeChar); m_engine->printIncludeContext(m_fileName,line); } else if (c=='%') // %} or something else { state=StateEndTag; } break; case StateEndTag: if (c==m_closeChar) // %} { // found tag! state=StateText; addToken(tokens,m_data.data(),line,lastTokenPos, emptyOutputLine ? startLinePos : markStartPos, TemplateToken::Text); addToken(tokens,m_data.data(),line,markStartPos+2, pos-1,TemplateToken::Block); lastTokenPos = pos+1; } else // something else { if (c=='\n') { warn(m_fileName,line,"unexpected new line inside %c%%...%%%c block",m_openChar,m_closeChar); m_engine->printIncludeContext(m_fileName,line); } state=StateTag; } break; case StateComment: if (c=='\n') { warn(m_fileName,line,"unexpected new line inside %c#...#%c block",m_openChar,m_closeChar); m_engine->printIncludeContext(m_fileName,line); } else if (c=='#') // #} or something else { state=StateEndComment; } break; case StateEndComment: if (c==m_closeChar) // #} { // found comment tag! state=StateText; addToken(tokens,m_data.data(),line,lastTokenPos, emptyOutputLine ? startLinePos : markStartPos, TemplateToken::Text); lastTokenPos = pos+1; } else // something else { if (c=='\n') { warn(m_fileName,line,"unexpected new line inside %c#...#%c block",m_openChar,m_closeChar); m_engine->printIncludeContext(m_fileName,line); } state=StateComment; } break; case StateMaybeVar: switch (c) { case '#': // {{# state=StateComment; markStartPos=pos-1; break; case '%': // {{% state=StateTag; markStartPos=pos-1; break; default: // {{ state=StateVariable; break; } break; case StateVariable: emptyOutputLine=FALSE; // assume a variable expands to content if (c=='\n') { warn(m_fileName,line,"unexpected new line inside %c{...}%c block",m_openChar,m_closeChar); m_engine->printIncludeContext(m_fileName,line); } else if (c=='}') // }} or something else { state=StateEndVariable; } break; case StateEndVariable: if (c==m_closeChar) // }} { // found variable tag! state=StateText; addToken(tokens,m_data.data(),line,lastTokenPos, emptyOutputLine ? startLinePos : markStartPos, TemplateToken::Text); addToken(tokens,m_data.data(),line,markStartPos+2, pos-1,TemplateToken::Variable); lastTokenPos = pos+1; } else // something else { if (c=='\n') { warn(m_fileName,line,"unexpected new line inside %c{...}%c block",m_openChar,m_closeChar); m_engine->printIncludeContext(m_fileName,line); } state=StateVariable; } break; } if (c=='\n') // new line { state=StateText; startLinePos=pos+1; // if the current line only contain commands and whitespace, // then skip it in the output by moving lastTokenPos if (markStartPos!=-1 && emptyOutputLine) lastTokenPos = startLinePos; // reset markers markStartPos=-1; line++; emptyOutputLine=TRUE; } } if (lastTokenPos &tokens, const char *data,int line, int startPos,int endPos, TemplateToken::Type type) { if (startPos &tokens) : m_engine(engine), m_templateName(templateName), m_tokens(tokens) { } void TemplateParser::parse( TemplateNode *parent,int line,const QStrList &stopAt, QList &nodes) { TRACE(("{TemplateParser::parse\n")); // process the tokens. Build node list while (hasNextToken()) { TemplateToken *tok = takeNextToken(); //printf("%p:Token type=%d data='%s' line=%d\n", // parent,tok->type,tok->data.data(),tok->line); switch(tok->type) { case TemplateToken::Text: nodes.append(new TemplateNodeText(this,parent,tok->line,tok->data)); break; case TemplateToken::Variable: // {{ var }} nodes.append(new TemplateNodeVariable(this,parent,tok->line,tok->data)); break; case TemplateToken::Block: // {% tag %} { QCString command = tok->data; int sep = command.find(' '); if (sep!=-1) { command=command.left(sep); } if (stopAt.contains(command)) { prependToken(tok); TRACE(("}TemplateParser::parse: stop\n")); return; } QCString arg; if (sep!=-1) { arg = tok->data.mid(sep+1); } TemplateNode *node = TemplateNodeFactory::instance()-> create(command,this,parent,tok->line,arg); if (node) { nodes.append(node); } else if (command=="empty" || command=="else" || command=="endif" || command=="endfor" || command=="endblock" || command=="endwith" || command=="endrecursetree" || command=="endspaceless" || command=="endmarkers" || command=="endmsg" || command=="endrepeat" || command=="elif" || command=="endrange" || command=="endtabbing" || command=="endencoding") { warn(m_templateName,tok->line,"Found tag '%s' without matching start tag",command.data()); } else { warn(m_templateName,tok->line,"Unknown tag '%s'",command.data()); } } break; } delete tok; } if (!stopAt.isEmpty()) { QStrListIterator it(stopAt); const char *s; QCString options; for (it.toFirst();(s=it.current());++it) { if (!options.isEmpty()) options+=", "; options+=s; } warn(m_templateName,line,"Unclosed tag in template, expected one of: %s", options.data()); } TRACE(("}TemplateParser::parse: last token\n")); } bool TemplateParser::hasNextToken() const { return !m_tokens.isEmpty(); } TemplateToken *TemplateParser::takeNextToken() { return m_tokens.take(0); } const TemplateToken *TemplateParser::currentToken() const { return m_tokens.getFirst(); }; void TemplateParser::removeNextToken() { m_tokens.removeFirst(); } void TemplateParser::prependToken(const TemplateToken *token) { m_tokens.prepend(token); } void TemplateParser::warn(const char *fileName,int line,const char *fmt,...) const { va_list args; va_start(args,fmt); va_warn(fileName,line,fmt,args); va_end(args); m_engine->printIncludeContext(fileName,line); } //---------------------------------------------------------- TemplateImpl::TemplateImpl(TemplateEngine *engine,const QCString &name,const QCString &data, const QCString &extension) : TemplateNode(0) { m_name = name; m_engine = engine; TemplateLexer lexer(engine,name,data); if (extension=="tex") { lexer.setOpenCloseCharacters('<','>'); } QList tokens; tokens.setAutoDelete(TRUE); lexer.tokenize(tokens); TemplateParser parser(engine,name,tokens); parser.parse(this,1,QStrList(),m_nodes); } TemplateImpl::~TemplateImpl() { //printf("deleting template %s\n",m_name.data()); } void TemplateImpl::render(FTextStream &ts, TemplateContext *c) { TemplateContextImpl *ci = dynamic_cast(c); if (ci==0) return; // should not happen if (!m_nodes.isEmpty()) { TemplateNodeExtend *ne = dynamic_cast(m_nodes.getFirst()); if (ne==0) // normal template, add blocks to block context { TemplateBlockContext *bc = ci->blockContext(); QListIterator li(m_nodes); TemplateNode *n; for (li.toFirst();(n=li.current());++li) { TemplateNodeBlock *nb = dynamic_cast(n); if (nb) { bc->add(nb); } } } m_nodes.render(ts,c); } } //---------------------------------------------------------- /** @brief Private data of the template engine */ class TemplateEngine::Private { class IncludeEntry { public: enum Type { Template, Block }; IncludeEntry(Type type,const QCString &fileName,const QCString &blockName,int line) : m_type(type), m_fileName(fileName), m_blockName(blockName), m_line(line) {} Type type() const { return m_type; } QCString fileName() const { return m_fileName; } QCString blockName() const { return m_blockName; } int line() const { return m_line; } private: Type m_type; QCString m_fileName; QCString m_blockName; int m_line; }; public: Private(TemplateEngine *engine) : m_templateCache(17) /*, m_indent(0)*/, m_engine(engine) { m_templateCache.setAutoDelete(TRUE); m_includeStack.setAutoDelete(TRUE); } Template *loadByName(const QCString &fileName,int line) { //for (int i=0;i li(m_includeStack); li.toLast(); IncludeEntry *ie=li.current(); while ((ie=li.current())) { --li; IncludeEntry *next=li.current(); if (ie->type()==IncludeEntry::Template) { if (next) { warn(fileName,line," inside template '%s' included from template '%s' at line %d",ie->fileName().data(),next->fileName().data(),ie->line()); } } else // ie->type()==IncludeEntry::Block { warn(fileName,line," included by block '%s' inside template '%s' at line %d",ie->blockName().data(), ie->fileName().data(),ie->line()); } } } void setOutputExtension(const char *extension) { m_extension = extension; } QCString outputExtension() const { return m_extension; } void setTemplateDir(const char *dirName) { m_templateDirName = dirName; } private: QDict