/******************************************************************************
*
* 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 <stdio.h>
#include <stdarg.h>
#include <qlist.h>
#include <qarray.h>
#include <qdict.h>
#include <qstrlist.h>
#include <qvaluelist.h>
#include <qstack.h>
#include <qfile.h>
#include <qregexp.h>
#include <qcstring.h>
#include <qdir.h>
#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<QCString> split(const QCString &str,const QCString &sep,
bool allowEmptyEntries=FALSE,bool cleanup=TRUE)
{
QValueList<QCString> 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 (q<p) result.resize(q-result.data()+1);
return result;
}
//----------------------------------------------------------------------------
#if ENABLE_TRACING
static QCString replace(const char *s,char csrc,char cdst)
{
QCString result = s;
for (char *p=result.data();*p;p++)
{
if (*p==csrc) *p=cdst;
}
return result;
}
#endif
//- TemplateVariant implementation -------------------------------------------
TemplateVariant::TemplateVariant(TemplateStructIntf *s)
: m_type(Struct), m_strukt(s), m_raw(FALSE)
{
m_strukt->addRef();
}
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<TemplateVariant> 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<TemplateVariant> 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<TemplateVariant>::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<TemplateNodeBlock> > 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<TemplateKeyValue> &arguments);
private:
const TemplateEngine *m_engine;
QCString m_templateName;
int m_line;
QCString m_outputDir;
QList< QDict<TemplateVariant> > m_contextStack;
TemplateBlockContext m_blockContext;
QDict<TemplateEscapeIntf*> m_escapeIntfDict;
TemplateEscapeIntf *m_activeEscapeIntf;
TemplateSpacelessIntf *m_spacelessIntf;
bool m_spacelessEnabled;
bool m_tabbingEnabled;
TemplateAutoRef<TemplateStruct> m_indices;
QDict< QStack<TemplateVariant> > 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<ListElem>
{
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<ListElem> 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<ListElem>
{
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<ListElem> 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<ListElem>
{
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<ListElem> 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 T> class AutoRegister
{
public:
AutoRegister<T>(const QCString &key)
{
TemplateFilterFactory::instance()->registerFilter(key,&T::apply);
}
};
private:
QDict<void> m_registry;
};
// register a handlers for each filter we support
static TemplateFilterFactory::AutoRegister<FilterAdd> fAdd("add");
static TemplateFilterFactory::AutoRegister<FilterGet> fGet("get");
static TemplateFilterFactory::AutoRegister<FilterRaw> fRaw("raw");
static TemplateFilterFactory::AutoRegister<FilterList> fList("list");
static TemplateFilterFactory::AutoRegister<FilterAppend> fAppend("append");
static TemplateFilterFactory::AutoRegister<FilterLength> fLength("length");
static TemplateFilterFactory::AutoRegister<FilterNoWrap> fNoWrap("nowrap");
static TemplateFilterFactory::AutoRegister<FilterFlatten> fFlatten("flatten");
static TemplateFilterFactory::AutoRegister<FilterDefault> fDefault("default");
static TemplateFilterFactory::AutoRegister<FilterPrepend> fPrepend("prepend");
static TemplateFilterFactory::AutoRegister<FilterGroupBy> fGroupBy("groupBy");
static TemplateFilterFactory::AutoRegister<FilterRelative> fRelative("relative");
static TemplateFilterFactory::AutoRegister<FilterListSort> fListSort("listsort");
static TemplateFilterFactory::AutoRegister<FilterTexLabel> fTexLabel("texLabel");
static TemplateFilterFactory::AutoRegister<FilterTexIndex> fTexIndex("texIndex");
static TemplateFilterFactory::AutoRegister<FilterPaginate> fPaginate("paginate");
static TemplateFilterFactory::AutoRegister<FilterStripPath> fStripPath("stripPath");
static TemplateFilterFactory::AutoRegister<FilterDecodeURL> fDecodeURL("decodeURL");
static TemplateFilterFactory::AutoRegister<FilterAlphaIndex> fAlphaIndex("alphaIndex");
static TemplateFilterFactory::AutoRegister<FilterDivisibleBy> fDivisibleBy("divisibleby");
static TemplateFilterFactory::AutoRegister<FilterIsRelativeURL> fIsRelativeURL("isRelativeURL");
static TemplateFilterFactory::AutoRegister<FilterIsAbsoluteURL> 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<TemplateContextImpl*>(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<ExprAst> &args)
: m_var(var), m_args(args)
{ TRACE(("ExprAstFunctionVariable()\n"));
m_args.setAutoDelete(TRUE);
}
~ExprAstFunctionVariable()
{
delete m_var;
}
virtual TemplateVariant resolve(TemplateContext *c)
{
QValueList<TemplateVariant> args;
for (uint i=0;i<m_args.count();i++)
{
TemplateVariant v = m_args.at(i)->resolve(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<ExprAst> 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<TemplateContextImpl*>(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<TemplateContextImpl*>(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.toString();
}
else
{
return lhs.toInt()<rhs.toInt();
}
case Operator::Greater:
if (lhs.type()==TemplateVariant::String && rhs.type()==TemplateVariant::String)
{
return !(lhs.toString()<rhs.toString());
}
else
{
return lhs.toInt()>rhs.toInt();
}
case Operator::LessEqual:
if (lhs.type()==TemplateVariant::String && rhs.type()==TemplateVariant::String)
{
return lhs.toString()==rhs.toString() || lhs.toString()<rhs.toString();
}
else
{
return lhs.toInt()<=rhs.toInt();
}
case Operator::GreaterEqual:
if (lhs.type()==TemplateVariant::String && rhs.type()==TemplateVariant::String)
{
return lhs.toString()==rhs.toString() || !(lhs.toString()<rhs.toString());
}
else
{
return lhs.toInt()>=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<TemplateToken> &tokens);
void parse(TemplateNode *parent,int line,const QStrList &stopAt,
QList<TemplateNode> &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<TemplateToken> &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<ExprAst> 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<TemplateNode>
{
public:
TemplateNodeList()
{
setAutoDelete(TRUE);
}
void render(FTextStream &ts,TemplateContext *c)
{
TRACE(("{TemplateNodeList::render\n"));
QListIterator<TemplateNode> 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("<unknown>"), 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<TemplateVariant> > it(m_contextStack);
QDict<TemplateVariant> *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<TemplateVariant> *dict = new QDict<TemplateVariant>;
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<TemplateVariant> *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<TemplateStruct*>(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<TemplateVariant> *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<TemplateStruct*>(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<TemplateVariant> &)
{
TemplateStruct *entry = (TemplateStruct*)ctx;
TemplateList *result = TemplateList::alloc();
getPathListFunc(entry,result);
return result;
}
void TemplateContextImpl::addIndexEntry(const QCString &indexName,const QValueList<TemplateKeyValue> &arguments)
{
QValueListConstIterator<TemplateKeyValue> 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<TemplateVariant> *stack = m_indexStacks.find(indexName);
if (!stack) // no stack yet, create it!
{
stack = new QStack<TemplateVariant>;
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<TemplateList*>(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<TemplateStruct*>(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<TemplateContextImpl*>(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<TemplateContextImpl*>(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<TemplateVariant>());
}
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<ExprAst> m_args;
};
//----------------------------------------------------------
/** @brief Helper class for creating template AST tag nodes and returning
* the template for a given node.
*/
template<class T> 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<TemplateImpl*>(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<TemplateNodeIf>
{
public:
TemplateNodeIf(TemplateParser *parser,TemplateNode *parent,int line,const QCString &data) :
TemplateNodeCreator<TemplateNodeIf>(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<TemplateContextImpl*>(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<GuardedNodes> 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<GuardedNodes> m_ifGuardedNodes;
TemplateNodeList m_falseNodes;
};
//----------------------------------------------------------
/** @brief Class representing a 'for' tag in a template */
class TemplateNodeRepeat : public TemplateNodeCreator<TemplateNodeRepeat>
{
public:
TemplateNodeRepeat(TemplateParser *parser,TemplateNode *parent,int line,const QCString &data)
: TemplateNodeCreator<TemplateNodeRepeat>(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<TemplateContextImpl*>(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<n;i++)
{
TemplateAutoRef<TemplateStruct> 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<TemplateNodeRange>
{
public:
TemplateNodeRange(TemplateParser *parser,TemplateNode *parent,int line,const QCString &data)
: TemplateNodeCreator<TemplateNodeRange>(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<TemplateContextImpl*>(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<TemplateStruct> 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 = i<e;
}
else
{
i++;
done = i>e;
}
}
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<TemplateNodeFor>
{
public:
TemplateNodeFor(TemplateParser *parser,TemplateNode *parent,int line,const QCString &data)
: TemplateNodeCreator<TemplateNodeFor>(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<TemplateContextImpl*>(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<TemplateVariant>());
}
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<TemplateStruct> 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;i<m_vars.count();i++,vi++)
{
c->set(m_vars[vi],v.toStruct()->get(m_vars[vi]));
}
}
for (;vi<m_vars.count();vi++)
{
c->set(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<QCString> m_vars;
TemplateNodeList m_loopNodes;
TemplateNodeList m_emptyNodes;
};
//----------------------------------------------------------
/** @brief Class representing an 'markers' tag in a template */
class TemplateNodeMsg : public TemplateNodeCreator<TemplateNodeMsg>
{
public:
TemplateNodeMsg(TemplateParser *parser,TemplateNode *parent,int line,const QCString &)
: TemplateNodeCreator<TemplateNodeMsg>(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<TemplateContextImpl*>(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<TemplateNodeBlock>
{
public:
TemplateNodeBlock(TemplateParser *parser,TemplateNode *parent,int line,const QCString &data)
: TemplateNodeCreator<TemplateNodeBlock>(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<TemplateContextImpl*>(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<TemplateStruct> 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<TemplateNodeExtend>
{
public:
TemplateNodeExtend(TemplateParser *parser,TemplateNode *parent,int line,const QCString &data)
: TemplateNodeCreator<TemplateNodeExtend>(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<TemplateContextImpl*>(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<TemplateImpl*>(bt) : 0;
if (baseTemplate)
{
// fill block context
TemplateBlockContext *bc = ci->blockContext();
// add overruling blocks to the context
QListIterator<TemplateNode> li(m_nodes);
TemplateNode *n;
for (li.toFirst();(n=li.current());++li)
{
TemplateNodeBlock *nb = dynamic_cast<TemplateNodeBlock*>(n);
if (nb)
{
bc->add(nb);
}
TemplateNodeMsg *msg = dynamic_cast<TemplateNodeMsg*>(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<TemplateNodeInclude>
{
public:
TemplateNodeInclude(TemplateParser *parser,TemplateNode *parent,int line,const QCString &data)
: TemplateNodeCreator<TemplateNodeInclude>(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<TemplateContextImpl*>(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<TemplateImpl*>(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<TemplateNodeCreate>
{
public:
TemplateNodeCreate(TemplateParser *parser,TemplateNode *parent,int line,const QCString &data)
: TemplateNodeCreator<TemplateNodeCreate>(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<TemplateContextImpl*>(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<TemplateImpl*>(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<TemplateNodeTree>
{
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<TemplateNodeTree>(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<TemplateVariant> &)
{
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<TemplateContextImpl*>(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<TemplateContextImpl*>(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<TemplateNodeIndexEntry>
{
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<TemplateNodeIndexEntry>(parser,parent,line)
{
TRACE(("{TemplateNodeIndexEntry(%s)\n",data.data()));
m_args.setAutoDelete(TRUE);
ExpressionParser expParser(parser,line);
QValueList<QCString> args = split(data," ");
QValueListIterator<QCString> 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<TemplateContextImpl*>(c);
if (ci==0) return; // should not happen
if (!m_name.isEmpty())
{
ci->setLocation(m_templateName,m_line);
QListIterator<Mapping> it(m_args);
Mapping *mapping;
QValueList<TemplateKeyValue> 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<Mapping> m_args;
};
//----------------------------------------------------------
/** @brief Class representing an 'opensubindex' tag in a template */
class TemplateNodeOpenSubIndex : public TemplateNodeCreator<TemplateNodeOpenSubIndex>
{
public:
TemplateNodeOpenSubIndex(TemplateParser *parser,TemplateNode *parent,int line,const QCString &data)
: TemplateNodeCreator<TemplateNodeOpenSubIndex>(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<TemplateContextImpl*>(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<TemplateNodeCloseSubIndex>
{
public:
TemplateNodeCloseSubIndex(TemplateParser *parser,TemplateNode *parent,int line,const QCString &data)
: TemplateNodeCreator<TemplateNodeCloseSubIndex>(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<TemplateContextImpl*>(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<TemplateNodeWith>
{
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<TemplateNodeWith>(parser,parent,line)
{
TRACE(("{TemplateNodeWith(%s)\n",data.data()));
m_args.setAutoDelete(TRUE);
ExpressionParser expParser(parser,line);
QCString filteredData = removeSpacesAroundEquals(data);
QValueList<QCString> args = split(filteredData," ");
QValueListIterator<QCString> 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<TemplateContextImpl*>(c);
if (ci==0) return; // should not happen
ci->setLocation(m_templateName,m_line);
c->push();
QListIterator<Mapping> 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<Mapping> m_args;
};
//----------------------------------------------------------
/** @brief Class representing an 'cycle' tag in a template */
class TemplateNodeCycle : public TemplateNodeCreator<TemplateNodeCycle>
{
public:
TemplateNodeCycle(TemplateParser *parser,TemplateNode *parent,int line,const QCString &data)
: TemplateNodeCreator<TemplateNodeCycle>(parser,parent,line)
{
TRACE(("{TemplateNodeCycle(%s)\n",data.data()));
m_args.setAutoDelete(TRUE);
m_index=0;
ExpressionParser expParser(parser,line);
QValueList<QCString> args = split(data," ");
QValueListIterator<QCString> 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<TemplateContextImpl*>(c);
ci->setLocation(m_templateName,m_line);
if (m_index<m_args.count())
{
TemplateVariant v = m_args.at(m_index)->resolve(c);
if (v.type()==TemplateVariant::Function)
{
v = v.call(QValueList<TemplateVariant>());
}
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<ExprAst> m_args;
};
//----------------------------------------------------------
/** @brief Class representing an 'set' tag in a template */
class TemplateNodeSet : public TemplateNodeCreator<TemplateNodeSet>
{
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<TemplateNodeSet>(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<TemplateContextImpl*>(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<TemplateNodeSpaceless>
{
public:
TemplateNodeSpaceless(TemplateParser *parser,TemplateNode *parent,int line,const QCString &)
: TemplateNodeCreator<TemplateNodeSpaceless>(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<TemplateContextImpl*>(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<TemplateNodeMarkers>
{
public:
TemplateNodeMarkers(TemplateParser *parser,TemplateNode *parent,int line,const QCString &data)
: TemplateNodeCreator<TemplateNodeMarkers>(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 || w<i)
{
parser->warn(m_templateName,line,"markers tag as wrong format. Expected: markers <var> in <list> with <string_with_markers>");
}
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<TemplateContextImpl*>(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)) && i<entryIndex; it->toNext(),i++) {}
if (ok && i==entryIndex) // found element
{
TemplateAutoRef<TemplateStruct> 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 (i<entryIndex)
{
ci->warn(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<TemplateNodeTabbing>
{
public:
TemplateNodeTabbing(TemplateParser *parser,TemplateNode *parent,int line,const QCString &)
: TemplateNodeCreator<TemplateNodeTabbing>(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<TemplateContextImpl*>(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<TemplateNodeResource>
{
public:
TemplateNodeResource(TemplateParser *parser,TemplateNode *parent,int line,const QCString &data)
: TemplateNodeCreator<TemplateNodeResource>(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<TemplateContextImpl*>(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<TemplateNodeEncoding>
{
public:
TemplateNodeEncoding(TemplateParser *parser,TemplateNode *parent,int line,const QCString &data)
: TemplateNodeCreator<TemplateNodeEncoding>(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<TemplateContextImpl*>(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 T> class AutoRegister
{
public:
AutoRegister<T>(const QCString &key)
{
TemplateNodeFactory::instance()->registerTemplateNode(key,T::createInstance);
}
};
private:
QDict<void> m_registry;
};
// register a handler for each start tag we support
static TemplateNodeFactory::AutoRegister<TemplateNodeIf> autoRefIf("if");
static TemplateNodeFactory::AutoRegister<TemplateNodeFor> autoRefFor("for");
static TemplateNodeFactory::AutoRegister<TemplateNodeMsg> autoRefMsg("msg");
static TemplateNodeFactory::AutoRegister<TemplateNodeSet> autoRefSet("set");
static TemplateNodeFactory::AutoRegister<TemplateNodeTree> autoRefTree("recursetree");
static TemplateNodeFactory::AutoRegister<TemplateNodeWith> autoRefWith("with");
static TemplateNodeFactory::AutoRegister<TemplateNodeBlock> autoRefBlock("block");
static TemplateNodeFactory::AutoRegister<TemplateNodeCycle> autoRefCycle("cycle");
static TemplateNodeFactory::AutoRegister<TemplateNodeRange> autoRefRange("range");
static TemplateNodeFactory::AutoRegister<TemplateNodeExtend> autoRefExtend("extend");
static TemplateNodeFactory::AutoRegister<TemplateNodeCreate> autoRefCreate("create");
static TemplateNodeFactory::AutoRegister<TemplateNodeRepeat> autoRefRepeat("repeat");
static TemplateNodeFactory::AutoRegister<TemplateNodeInclude> autoRefInclude("include");
static TemplateNodeFactory::AutoRegister<TemplateNodeMarkers> autoRefMarkers("markers");
static TemplateNodeFactory::AutoRegister<TemplateNodeTabbing> autoRefTabbing("tabbing");
static TemplateNodeFactory::AutoRegister<TemplateNodeResource> autoRefResource("resource");
static TemplateNodeFactory::AutoRegister<TemplateNodeEncoding> autoRefEncoding("encoding");
static TemplateNodeFactory::AutoRegister<TemplateNodeSpaceless> autoRefSpaceless("spaceless");
static TemplateNodeFactory::AutoRegister<TemplateNodeIndexEntry> autoRefIndexEntry("indexentry");
static TemplateNodeFactory::AutoRegister<TemplateNodeOpenSubIndex> autoRefOpenSubIndex("opensubindex");
static TemplateNodeFactory::AutoRegister<TemplateNodeCloseSubIndex> autoRefCloseSubIndex("closesubindex");
//----------------------------------------------------------
TemplateBlockContext::TemplateBlockContext() : m_blocks(257)
{
m_blocks.setAutoDelete(TRUE);
}
TemplateNodeBlock *TemplateBlockContext::get(const QCString &name) const
{
QList<TemplateNodeBlock> *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<TemplateNodeBlock> *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<TemplateNodeBlock> *list = m_blocks.find(block->name());
if (list==0)
{
list = new QList<TemplateNodeBlock>;
m_blocks.insert(block->name(),list);
}
list->prepend(block);
}
void TemplateBlockContext::add(TemplateBlockContext *ctx)
{
QDictIterator< QList<TemplateNodeBlock> > di(ctx->m_blocks);
QList<TemplateNodeBlock> *list;
for (di.toFirst();(list=di.current());++di)
{
QListIterator<TemplateNodeBlock> 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<TemplateNodeBlock> *list = m_blocks.find(block->name());
if (list==0)
{
list = new QList<TemplateNodeBlock>;
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<TemplateToken> &tokens);
void setOpenCloseCharacters(char openChar,char closeChar)
{ m_openChar=openChar; m_closeChar=closeChar; }
private:
void addToken(QList<TemplateToken> &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<TemplateToken> &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<pos)
{
addToken(tokens,m_data.data(),line,
lastTokenPos,pos,
TemplateToken::Text);
}
}
void TemplateLexer::addToken(QList<TemplateToken> &tokens,
const char *data,int line,
int startPos,int endPos,
TemplateToken::Type type)
{
if (startPos<endPos)
{
int len = endPos-startPos+1;
QCString text(len);
qstrncpy(text.rawData(),data+startPos,len);
if (type!=TemplateToken::Text) text = text.stripWhiteSpace();
tokens.append(new TemplateToken(type,text,line));
}
}
//----------------------------------------------------------
TemplateParser::TemplateParser(const TemplateEngine *engine,
const QCString &templateName,
QList<TemplateToken> &tokens) :
m_engine(engine), m_templateName(templateName), m_tokens(tokens)
{
}
void TemplateParser::parse(
TemplateNode *parent,int line,const QStrList &stopAt,
QList<TemplateNode> &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<TemplateToken> 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<TemplateContextImpl*>(c);
if (ci==0) return; // should not happen
if (!m_nodes.isEmpty())
{
TemplateNodeExtend *ne = dynamic_cast<TemplateNodeExtend*>(m_nodes.getFirst());
if (ne==0) // normal template, add blocks to block context
{
TemplateBlockContext *bc = ci->blockContext();
QListIterator<TemplateNode> li(m_nodes);
TemplateNode *n;
for (li.toFirst();(n=li.current());++li)
{
TemplateNodeBlock *nb = dynamic_cast<TemplateNodeBlock*>(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<m_indent;i++) printf(" ");
//m_indent++;
//printf("loadByName(%s,%d) {\n",fileName.data(),line);
m_includeStack.append(new IncludeEntry(IncludeEntry::Template,fileName,QCString(),line));
Template *templ = m_templateCache.find(fileName);
if (templ==0) // first time template is referenced
{
QCString filePath = m_templateDirName+"/"+fileName;
QFile f(filePath);
if (f.open(IO_ReadOnly))
{
QFileInfo fi(filePath);
int size=fi.size();
QCString data(size+1);
if (f.readBlock(data.rawData(),size)==size)
{
templ = new TemplateImpl(m_engine,filePath,data,m_extension);
m_templateCache.insert(fileName,templ);
return templ;
}
}
// fallback to default built-in template
const QCString data = ResourceMgr::instance().getAsString(fileName);
if (!data.isEmpty())
{
templ = new TemplateImpl(m_engine,fileName,data,m_extension);
m_templateCache.insert(fileName,templ);
}
else
{
err("Could not open template file %s\n",fileName.data());
}
}
return templ;
}
void unload(Template * /*t*/)
{
//(void)t;
//m_indent--;
//for (int i=0;i<m_indent;i++) printf(" ");
//printf("}\n");
m_includeStack.removeLast();
}
void enterBlock(const QCString &fileName,const QCString &blockName,int line)
{
//for (int i=0;i<m_indent;i++) printf(" ");
//m_indent++;
//printf("enterBlock(%s,%s,%d) {\n",fileName.data(),blockName.data(),line);
m_includeStack.append(new IncludeEntry(IncludeEntry::Block,fileName,blockName,line));
}
void leaveBlock()
{
//m_indent--;
//for (int i=0;i<m_indent;i++) printf(" ");
//printf("}\n");
m_includeStack.removeLast();
}
void printIncludeContext(const char *fileName,int line) const
{
QListIterator<IncludeEntry> 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<Template> m_templateCache;
//mutable int m_indent;
TemplateEngine *m_engine;
QList<IncludeEntry> m_includeStack;
QCString m_extension;
QCString m_templateDirName;
};
TemplateEngine::TemplateEngine()
{
p = new Private(this);
}
TemplateEngine::~TemplateEngine()
{
delete p;
}
TemplateContext *TemplateEngine::createContext() const
{
return new TemplateContextImpl(this);
}
void TemplateEngine::destroyContext(TemplateContext *ctx)
{
delete ctx;
}
Template *TemplateEngine::loadByName(const QCString &fileName,int line)
{
return p->loadByName(fileName,line);
}
void TemplateEngine::unload(Template *t)
{
p->unload(t);
}
void TemplateEngine::enterBlock(const QCString &fileName,const QCString &blockName,int line)
{
p->enterBlock(fileName,blockName,line);
}
void TemplateEngine::leaveBlock()
{
p->leaveBlock();
}
void TemplateEngine::printIncludeContext(const char *fileName,int line) const
{
p->printIncludeContext(fileName,line);
}
void TemplateEngine::setOutputExtension(const char *extension)
{
p->setOutputExtension(extension);
}
QCString TemplateEngine::outputExtension() const
{
return p->outputExtension();
}
void TemplateEngine::setTemplateDir(const char *dirName)
{
p->setTemplateDir(dirName);
}