Imported Upstream version 1.8.15
[platform/upstream/doxygen.git] / src / template.cpp
1 /******************************************************************************
2  *
3  * Copyright (C) 1997-2015 by Dimitri van Heesch.
4  *
5  * Permission to use, copy, modify, and distribute this software and its
6  * documentation under the terms of the GNU General Public License is hereby
7  * granted. No representations are made about the suitability of this software
8  * for any purpose. It is provided "as is" without express or implied warranty.
9  * See the GNU General Public License for more details.
10  *
11  * Documents produced by Doxygen are derivative works derived from the
12  * input used in their production; they are not affected by this license.
13  *
14  */
15
16 #include "template.h"
17
18 #include <stdio.h>
19 #include <stdarg.h>
20
21 #include <qlist.h>
22 #include <qarray.h>
23 #include <qdict.h>
24 #include <qstrlist.h>
25 #include <qvaluelist.h>
26 #include <qstack.h>
27 #include <qfile.h>
28 #include <qregexp.h>
29 #include <qcstring.h>
30 #include <qdir.h>
31
32 #include "sortdict.h"
33 #include "ftextstream.h"
34 #include "message.h"
35 #include "util.h"
36 #include "resourcemgr.h"
37 #include "portable.h"
38
39 #define ENABLE_TRACING 0
40
41 #if ENABLE_TRACING
42 #define TRACE(x) printf x
43 #else
44 #define TRACE(x)
45 #endif
46
47 class TemplateToken;
48
49 //-------------------------------------------------------------------
50
51 static QValueList<QCString> split(const QCString &str,const QCString &sep,
52                                   bool allowEmptyEntries=FALSE,bool cleanup=TRUE)
53 {
54   QValueList<QCString> lst;
55
56   int j = 0;
57   int i = str.find( sep, j );
58
59   while (i!=-1)
60   {
61     if ( str.mid(j,i-j).length() > 0 )
62     {
63       if (cleanup)
64       {
65         lst.append(str.mid(j,i-j).stripWhiteSpace());
66       }
67       else
68       {
69         lst.append(str.mid(j,i-j));
70       }
71     }
72     else if (allowEmptyEntries)
73     {
74       lst.append("");
75     }
76     j = i + sep.length();
77     i = str.find(sep,j);
78   }
79
80   int l = str.length() - 1;
81   if (str.mid(j,l-j+1).length()>0)
82   {
83     if (cleanup)
84     {
85       lst.append(str.mid(j,l-j+1).stripWhiteSpace());
86     }
87     else
88     {
89       lst.append(str.mid(j,l-j+1));
90     }
91   }
92   else if (allowEmptyEntries)
93   {
94     lst.append("");
95   }
96
97   return lst;
98 }
99
100 //----------------------------------------------------------------------------
101
102 /** Strips spaces surrounding `=` from string \a in, so
103  *  `foo = 10 bar=5 baz= 'hello'` will become `foo=10 bar=5 baz='hello'`
104  */
105 static QCString removeSpacesAroundEquals(const char *s)
106 {
107   QCString result(s);
108   const char *p=result.data();
109   char *q = result.rawData();
110   char c;
111   while ((c=*p++))
112   {
113     if (c==' ') // found a space, see if there is a = as well
114     {
115       const char *t = p;
116       bool found=FALSE;
117       while (*t==' ' || *t=='=') { if (*t++=='=') found=TRUE; }
118       if (found)
119       {
120         c='=';
121         p=t; // move p to end of '\s*=\s*' sequence
122       }
123     }
124     *q++=c;
125   }
126   if (q<p) result.resize(q-result.data()+1);
127   return result;
128 }
129
130 //----------------------------------------------------------------------------
131
132 #if ENABLE_TRACING
133 static QCString replace(const char *s,char csrc,char cdst)
134 {
135   QCString result = s;
136   for (char *p=result.data();*p;p++)
137   {
138     if (*p==csrc) *p=cdst;
139   }
140   return result;
141 }
142 #endif
143
144 //- TemplateVariant implementation -------------------------------------------
145
146
147 TemplateVariant::TemplateVariant(TemplateStructIntf *s)
148   : m_type(Struct), m_strukt(s), m_raw(FALSE)
149 {
150   m_strukt->addRef();
151 }
152
153 TemplateVariant::TemplateVariant(TemplateListIntf *l)
154   : m_type(List), m_list(l), m_raw(FALSE)
155 {
156   m_list->addRef();
157 }
158
159 TemplateVariant::~TemplateVariant()
160 {
161   if      (m_type==Struct) m_strukt->release();
162   else if (m_type==List)   m_list->release();
163 }
164
165 TemplateVariant::TemplateVariant(const TemplateVariant &v)
166   : m_type(v.m_type), m_strukt(0), m_raw(FALSE)
167 {
168   m_raw = v.m_raw;
169   switch (m_type)
170   {
171     case None: break;
172     case Bool:     m_boolVal = v.m_boolVal; break;
173     case Integer:  m_intVal  = v.m_intVal;  break;
174     case String:   m_strVal  = v.m_strVal;  break;
175     case Struct:   m_strukt  = v.m_strukt;  m_strukt->addRef(); break;
176     case List:     m_list    = v.m_list;    m_list->addRef();   break;
177     case Function: m_delegate= v.m_delegate;break;
178   }
179 }
180
181 TemplateVariant &TemplateVariant::operator=(const TemplateVariant &v)
182 {
183   // assignment can change the type of the variable, so we have to be
184   // careful with reference counted content.
185   TemplateStructIntf *tmpStruct = m_type==Struct ? m_strukt : 0;
186   TemplateListIntf   *tmpList   = m_type==List   ? m_list   : 0;
187   Type tmpType = m_type;
188
189   m_type    = v.m_type;
190   m_raw     = v.m_raw;
191   switch (m_type)
192   {
193     case None: break;
194     case Bool:     m_boolVal = v.m_boolVal; break;
195     case Integer:  m_intVal  = v.m_intVal;  break;
196     case String:   m_strVal  = v.m_strVal;  break;
197     case Struct:   m_strukt  = v.m_strukt;  m_strukt->addRef(); break;
198     case List:     m_list    = v.m_list;    m_list->addRef();   break;
199     case Function: m_delegate= v.m_delegate;break;
200   }
201
202   // release overwritten reference counted values
203   if      (tmpType==Struct && tmpStruct) tmpStruct->release();
204   else if (tmpType==List   && tmpList  ) tmpList->release();
205   return *this;
206 }
207
208 bool TemplateVariant::toBool() const
209 {
210   switch (m_type)
211   {
212     case None:     return FALSE;
213     case Bool:     return m_boolVal;
214     case Integer:  return m_intVal!=0;
215     case String:   return !m_strVal.isEmpty();
216     case Struct:   return TRUE;
217     case List:     return m_list->count()!=0;
218     case Function: return FALSE;
219   }
220   return FALSE;
221 }
222
223 int TemplateVariant::toInt() const
224 {
225   switch (m_type)
226   {
227     case None:     return 0;
228     case Bool:     return m_boolVal ? 1 : 0;
229     case Integer:  return m_intVal;
230     case String:   return m_strVal.toInt();
231     case Struct:   return 0;
232     case List:     return m_list->count();
233     case Function: return 0;
234   }
235   return 0;
236 }
237
238 //- Template struct implementation --------------------------------------------
239
240
241 /** @brief Private data of a template struct object */
242 class TemplateStruct::Private
243 {
244   public:
245     Private() : fields(17), refCount(0)
246     { fields.setAutoDelete(TRUE); }
247     QDict<TemplateVariant> fields;
248     int refCount;
249 };
250
251 TemplateStruct::TemplateStruct()
252 {
253   p = new Private;
254 }
255
256 TemplateStruct::~TemplateStruct()
257 {
258   delete p;
259 }
260
261 int TemplateStruct::addRef()
262 {
263   return ++p->refCount;
264 }
265
266 int TemplateStruct::release()
267 {
268   int count = --p->refCount;
269   if (count<=0)
270   {
271     delete this;
272   }
273   return count;
274 }
275
276 void TemplateStruct::set(const char *name,const TemplateVariant &v)
277 {
278   TemplateVariant *pv = p->fields.find(name);
279   if (pv) // change existing field
280   {
281     *pv = v;
282   }
283   else // insert new field
284   {
285     p->fields.insert(name,new TemplateVariant(v));
286   }
287 }
288
289 TemplateVariant TemplateStruct::get(const char *name) const
290 {
291   TemplateVariant *v = p->fields.find(name);
292   return v ? *v : TemplateVariant();
293 }
294
295 TemplateStruct *TemplateStruct::alloc()
296 {
297   return new TemplateStruct;
298 }
299
300 //- Template list implementation ----------------------------------------------
301
302
303 /** @brief Private data of a template list object */
304 class TemplateList::Private
305 {
306   public:
307     Private() : index(-1), refCount(0) {}
308     QValueList<TemplateVariant> elems;
309     int index;
310     int refCount;
311 };
312
313
314 TemplateList::TemplateList()
315 {
316   p = new Private;
317 }
318
319 TemplateList::~TemplateList()
320 {
321   delete p;
322 }
323
324 int TemplateList::addRef()
325 {
326   return ++p->refCount;
327 }
328
329 int TemplateList::release()
330 {
331   int count = --p->refCount;
332   if (count<=0)
333   {
334     delete this;
335   }
336   return count;
337 }
338
339 int TemplateList::count() const
340 {
341   return p->elems.count();
342 }
343
344 void TemplateList::append(const TemplateVariant &v)
345 {
346   p->elems.append(v);
347 }
348
349 // iterator support
350 class TemplateListConstIterator : public TemplateListIntf::ConstIterator
351 {
352   public:
353     TemplateListConstIterator(const TemplateList &l) : m_list(l) { m_index=-1; }
354     virtual ~TemplateListConstIterator() {}
355     virtual void toFirst()
356     {
357       m_it = m_list.p->elems.begin();
358       m_index=0;
359     }
360     virtual void toLast()
361     {
362       m_it = m_list.p->elems.fromLast();
363       m_index=m_list.count()-1;
364     }
365     virtual void toNext()
366     {
367       if (m_it!=m_list.p->elems.end())
368       {
369         ++m_it;
370         ++m_index;
371       }
372     }
373     virtual void toPrev()
374     {
375       if (m_index>0)
376       {
377         --m_it;
378         --m_index;
379       }
380       else
381       {
382         m_index=-1;
383       }
384     }
385     virtual bool current(TemplateVariant &v) const
386     {
387       if (m_index<0 || m_it==m_list.p->elems.end())
388       {
389         v = TemplateVariant();
390         return FALSE;
391       }
392       else
393       {
394         v = *m_it;
395         return TRUE;
396       }
397     }
398   private:
399     const TemplateList &m_list;
400     QValueList<TemplateVariant>::ConstIterator m_it;
401     int m_index;
402 };
403
404 TemplateListIntf::ConstIterator *TemplateList::createIterator() const
405 {
406   return new TemplateListConstIterator(*this);
407 }
408
409 TemplateVariant TemplateList::at(int index) const
410 {
411   if (index>=0 && index<(int)p->elems.count())
412   {
413     return p->elems[index];
414   }
415   else
416   {
417     return TemplateVariant();
418   }
419 }
420
421 TemplateList *TemplateList::alloc()
422 {
423   return new TemplateList;
424 }
425
426 //- Operator types ------------------------------------------------------------
427
428 /** @brief Class representing operators that can appear in template expressions */
429 class Operator
430 {
431   public:
432       /* Operator precedence (low to high)
433          or
434          and
435          not
436          in
437          ==, !=, <, >, <=, >=
438          +, -
439          *, /, %
440          |
441          :
442          ,
443        */
444     enum Type
445     {
446       Or, And, Not, In, Equal, NotEqual, Less, Greater, LessEqual,
447       GreaterEqual, Plus, Minus, Multiply, Divide, Modulo, Filter, Colon, Comma,
448       LeftParen, RightParen,
449       Last
450     };
451
452     static const char *toString(Type op)
453     {
454       switch(op)
455       {
456         case Or:           return "or";
457         case And:          return "and";
458         case Not:          return "not";
459         case In:           return "in";
460         case Equal:        return "==";
461         case NotEqual:     return "!=";
462         case Less:         return "<";
463         case Greater:      return ">";
464         case LessEqual:    return "<=";
465         case GreaterEqual: return ">=";
466         case Plus:         return "+";
467         case Minus:        return "-";
468         case Multiply:     return "*";
469         case Divide:       return "/";
470         case Modulo:       return "%";
471         case Filter:       return "|";
472         case Colon:        return ":";
473         case Comma:        return ",";
474         case LeftParen:    return "(";
475         case RightParen:   return ")";
476         case Last:         return "?";
477       }
478       return "?";
479     }
480 };
481
482 //-----------------------------------------------------------------------------
483
484 class TemplateNodeBlock;
485
486 /** @brief Class holding stacks of blocks available in the context */
487 class TemplateBlockContext
488 {
489   public:
490     TemplateBlockContext();
491     TemplateNodeBlock *get(const QCString &name) const;
492     TemplateNodeBlock *pop(const QCString &name) const;
493     void add(TemplateNodeBlock *block);
494     void add(TemplateBlockContext *ctx);
495     void push(TemplateNodeBlock *block);
496     void clear();
497   private:
498     QDict< QList<TemplateNodeBlock> > m_blocks;
499 };
500
501 /** @brief A container to store a key-value pair */
502 struct TemplateKeyValue
503 {
504   TemplateKeyValue() {}
505   TemplateKeyValue(const QCString &k,const TemplateVariant &v) : key(k), value(v) {}
506   QCString key;
507   TemplateVariant value;
508 };
509
510 /** @brief Internal class representing the implementation of a template
511  *  context */
512 class TemplateContextImpl : public TemplateContext
513 {
514   public:
515     TemplateContextImpl(const TemplateEngine *e);
516     virtual ~TemplateContextImpl();
517
518     // TemplateContext methods
519     void push();
520     void pop();
521     void set(const char *name,const TemplateVariant &v);
522     TemplateVariant get(const QCString &name) const;
523     const TemplateVariant *getRef(const QCString &name) const;
524     void setOutputDirectory(const QCString &dir)
525     { m_outputDir = dir; }
526     void setEscapeIntf(const QCString &ext,TemplateEscapeIntf *intf)
527     {
528       int i=(!ext.isEmpty() && ext.at(0)=='.') ? 1 : 0;
529       m_escapeIntfDict.insert(ext.mid(i),new TemplateEscapeIntf*(intf));
530     }
531     void selectEscapeIntf(const QCString &ext)
532     { TemplateEscapeIntf **ppIntf = m_escapeIntfDict.find(ext);
533       m_activeEscapeIntf = ppIntf ? *ppIntf : 0;
534     }
535     void setActiveEscapeIntf(TemplateEscapeIntf *intf) { m_activeEscapeIntf = intf; }
536     void setSpacelessIntf(TemplateSpacelessIntf *intf) { m_spacelessIntf = intf; }
537
538     // internal methods
539     TemplateBlockContext *blockContext();
540     TemplateVariant getPrimary(const QCString &name) const;
541     void setLocation(const QCString &templateName,int line)
542     { m_templateName=templateName; m_line=line; }
543     QCString templateName() const                { return m_templateName; }
544     int line() const                             { return m_line; }
545     QCString outputDirectory() const             { return m_outputDir; }
546     TemplateEscapeIntf *escapeIntf() const       { return m_activeEscapeIntf; }
547     TemplateSpacelessIntf *spacelessIntf() const { return m_spacelessIntf; }
548     void enableSpaceless(bool b)                 { if (b && !m_spacelessEnabled) m_spacelessIntf->reset(); 
549                                                    m_spacelessEnabled=b;
550                                                  }
551     bool spacelessEnabled() const                { return m_spacelessEnabled && m_spacelessIntf; }
552     void enableTabbing(bool b)                   { m_tabbingEnabled=b;
553                                                    if (m_activeEscapeIntf) m_activeEscapeIntf->enableTabbing(b);
554                                                  }
555     bool tabbingEnabled() const                  { return m_tabbingEnabled; }
556     bool needsRecoding() const                   { return !m_encoding.isEmpty(); }
557     QCString encoding() const                    { return m_encoding; }
558     void setEncoding(const QCString &file,int line,const QCString &enc);
559     QCString recode(const QCString &s);
560     void warn(const char *fileName,int line,const char *fmt,...) const;
561
562     // index related functions
563     void openSubIndex(const QCString &indexName);
564     void closeSubIndex(const QCString &indexName);
565     void addIndexEntry(const QCString &indexName,const QValueList<TemplateKeyValue> &arguments);
566
567   private:
568     const TemplateEngine *m_engine;
569     QCString m_templateName;
570     int m_line;
571     QCString m_outputDir;
572     QList< QDict<TemplateVariant> > m_contextStack;
573     TemplateBlockContext m_blockContext;
574     QDict<TemplateEscapeIntf*> m_escapeIntfDict;
575     TemplateEscapeIntf *m_activeEscapeIntf;
576     TemplateSpacelessIntf *m_spacelessIntf;
577     bool m_spacelessEnabled;
578     bool m_tabbingEnabled;
579     TemplateAutoRef<TemplateStruct> m_indices;
580     QDict< QStack<TemplateVariant> > m_indexStacks;
581     QCString m_encoding;
582     void *m_fromUtf8;
583 };
584
585 //-----------------------------------------------------------------------------
586
587 /** @brief The implementation of the "add" filter */
588 class FilterAdd
589 {
590   public:
591     static int variantIntValue(const TemplateVariant &v,bool &isInt)
592     {
593       isInt = v.type()==TemplateVariant::Integer;
594       if (!isInt && v.type()==TemplateVariant::String)
595       {
596         return v.toString().toInt(&isInt);
597       }
598       return isInt ? v.toInt() : 0;
599     }
600     static TemplateVariant apply(const TemplateVariant &v,const TemplateVariant &arg)
601     {
602       if (!v.isValid())
603       {
604         return arg;
605       }
606       bool lhsIsInt;
607       int  lhsValue = variantIntValue(v,lhsIsInt);
608       bool rhsIsInt;
609       int  rhsValue = variantIntValue(arg,rhsIsInt);
610       if (lhsIsInt && rhsIsInt)
611       {
612         return lhsValue+rhsValue;
613       }
614       else if (v.type()==TemplateVariant::String && arg.type()==TemplateVariant::String)
615       {
616         return TemplateVariant(v.toString() + arg.toString());
617       }
618       else
619       {
620         return v;
621       }
622     }
623 };
624
625 //-----------------------------------------------------------------------------
626
627 /** @brief The implementation of the "get" filter */
628 class FilterGet
629 {
630   public:
631     static TemplateVariant apply(const TemplateVariant &v,const TemplateVariant &arg)
632     {
633       if (v.isValid() && v.type()==TemplateVariant::Struct && arg.type()==TemplateVariant::String)
634       {
635         TemplateVariant result = v.toStruct()->get(arg.toString());
636         //printf("\nok[%s]=%d\n",arg.toString().data(),result.type());
637         return result;
638       }
639       else
640       {
641         //printf("\nnok[%s]\n",arg.toString().data());
642         return FALSE;
643       }
644     }
645 };
646
647 //-----------------------------------------------------------------------------
648
649 /** @brief The implementation of the "raw" filter */
650 class FilterRaw
651 {
652   public:
653     static TemplateVariant apply(const TemplateVariant &v,const TemplateVariant &)
654     {
655       if (v.isValid() && (v.type()==TemplateVariant::String || v.type()==TemplateVariant::Integer))
656       {
657         return TemplateVariant(v.toString(),TRUE);
658       }
659       else
660       {
661         return v;
662       }
663     }
664 };
665
666 //-----------------------------------------------------------------------------
667
668 /** @brief The implementation of the "list" filter */
669 class FilterList
670 {
671   public:
672     static TemplateVariant apply(const TemplateVariant &v,const TemplateVariant &)
673     {
674       if (v.isValid())
675       {
676         if (v.type()==TemplateVariant::List) // input is already a list
677         {
678           return v;
679         }
680         // create a list with v as the only element
681         TemplateList *list = TemplateList::alloc();
682         list->append(v);
683         return list;
684       }
685       else
686       {
687         return v;
688       }
689     }
690 };
691
692 //-----------------------------------------------------------------------------
693 /** @brief The implementation of the "texlabel" filter */
694 class FilterTexLabel
695 {
696   public:
697     static TemplateVariant apply(const TemplateVariant &v,const TemplateVariant &)
698     {
699       if (v.isValid() && (v.type()==TemplateVariant::String))
700       {
701         return TemplateVariant(latexEscapeLabelName(v.toString()),TRUE);
702       }
703       else
704       {
705         return v;
706       }
707     }
708 };
709
710 //-----------------------------------------------------------------------------
711
712 /** @brief The implementation of the "texindex" filter */
713 class FilterTexIndex
714 {
715   public:
716     static TemplateVariant apply(const TemplateVariant &v,const TemplateVariant &)
717     {
718       if (v.isValid() && (v.type()==TemplateVariant::String))
719       {
720         return TemplateVariant(latexEscapeIndexChars(v.toString()),TRUE);
721       }
722       else
723       {
724         return v;
725       }
726     }
727 };
728
729 //-----------------------------------------------------------------------------
730
731 /** @brief The implementation of the "append" filter */
732 class FilterAppend
733 {
734   public:
735     static TemplateVariant apply(const TemplateVariant &v,const TemplateVariant &arg)
736     {
737       if ((v.type()==TemplateVariant::String || v.type()==TemplateVariant::Integer) &&
738           (arg.type()==TemplateVariant::String || arg.type()==TemplateVariant::Integer))
739       {
740         return TemplateVariant(v.toString() + arg.toString());
741       }
742       else
743       {
744         return v;
745       }
746     }
747 };
748
749 //-----------------------------------------------------------------------------
750
751 /** @brief The implementation of the "prepend" filter */
752 class FilterPrepend
753 {
754   public:
755     static TemplateVariant apply(const TemplateVariant &v,const TemplateVariant &arg)
756     {
757       if ((v.type()==TemplateVariant::String || v.type()==TemplateVariant::Integer) &&
758           arg.type()==TemplateVariant::String)
759       {
760         return TemplateVariant(arg.toString() + v.toString());
761       }
762       else
763       {
764         return v;
765       }
766     }
767 };
768
769 //--------------------------------------------------------------------
770
771 /** @brief The implementation of the "length" filter */
772 class FilterLength
773 {
774   public:
775     static TemplateVariant apply(const TemplateVariant &v,const TemplateVariant &)
776     {
777       if (!v.isValid())
778       {
779         return TemplateVariant();
780       }
781       if (v.type()==TemplateVariant::List)
782       {
783         return TemplateVariant(v.toList()->count());
784       }
785       else if (v.type()==TemplateVariant::String)
786       {
787         return TemplateVariant((int)v.toString().length());
788       }
789       else
790       {
791         return TemplateVariant();
792       }
793     }
794 };
795
796 //--------------------------------------------------------------------
797
798 /** @brief The implementation of the "default" filter */
799 class FilterDefault
800 {
801   public:
802     static TemplateVariant apply(const TemplateVariant &v,const TemplateVariant &arg)
803     {
804       if (!v.isValid())
805       {
806         return arg;
807       }
808       else if (v.type()==TemplateVariant::String && v.toString().isEmpty())
809       {
810         return arg;
811       }
812       else
813       {
814         return v;
815       }
816     }
817 };
818
819 //--------------------------------------------------------------------
820
821 /** @brief The implementation of the "flatten" filter */
822 class FilterFlatten
823 {
824   public:
825     static TemplateVariant apply(const TemplateVariant &v,const TemplateVariant &)
826     {
827       if (!v.isValid() || v.type()!=TemplateVariant::List)
828       {
829         return v;
830       }
831       else
832       {
833         TemplateList *list = TemplateList::alloc();
834         flatten(v.toList(),list);
835         return TemplateVariant(list);
836       }
837     }
838
839   private:
840     static void flatten(TemplateListIntf *tree,TemplateList *list)
841     {
842       TemplateListIntf::ConstIterator *it = tree->createIterator();
843       TemplateVariant item;
844       for (it->toFirst();(it->current(item));it->toNext())
845       {
846         TemplateStructIntf *s = item.toStruct();
847         if (s)
848         {
849           list->append(item);
850           // if s has "children" then recurse into the children
851           TemplateVariant children = s->get("children");
852           if (children.isValid() && children.type()==TemplateVariant::List)
853           {
854             flatten(children.toList(),list);
855           }
856         }
857         else
858         {
859           list->append(item);
860         }
861       }
862       delete it;
863     }
864 };
865
866 //--------------------------------------------------------------------
867
868 /** @brief The implementation of the "listsort" filter */
869 class FilterListSort
870 {
871     struct ListElem
872     {
873       ListElem(const QCString &k,const TemplateVariant &v) : key(k), value(v) {}
874       QCString key;
875       TemplateVariant value;
876     };
877     class SortList : public QList<ListElem>
878     {
879       public:
880         SortList() { setAutoDelete(TRUE); }
881       private:
882         int compareValues(const ListElem *item1,const ListElem *item2) const
883         {
884           return qstrcmp(item1->key,item2->key);
885         }
886     };
887   public:
888     static TemplateVariant apply(const TemplateVariant &v,const TemplateVariant &args)
889     {
890       if (v.type()==TemplateVariant::List && args.type()==TemplateVariant::String)
891       {
892         //printf("FilterListSort::apply: v=%s args=%s\n",v.toString().data(),args.toString().data());
893         TemplateListIntf::ConstIterator *it = v.toList()->createIterator();
894
895         TemplateVariant item;
896         TemplateList *result = TemplateList::alloc();
897
898         // create list of items based on v using the data in args as a sort key
899         SortList sortList;
900         for (it->toFirst();(it->current(item));it->toNext())
901         {
902           TemplateStructIntf *s = item.toStruct();
903           if (s)
904           {
905             QCString sortKey = determineSortKey(s,args.toString());
906             sortList.append(new ListElem(sortKey,item));
907             //printf("sortKey=%s\n",sortKey.data());
908           }
909         }
910         delete it;
911
912         // sort the list
913         sortList.sort();
914
915         // add sorted items to the result list
916         QListIterator<ListElem> sit(sortList);
917         ListElem *elem;
918         for (sit.toFirst();(elem=sit.current());++sit)
919         {
920           result->append(elem->value);
921         }
922         return result;
923       }
924       return v;
925     }
926
927   private:
928     static QCString determineSortKey(TemplateStructIntf *s,const QCString &arg)
929     {
930       int i,p=0;
931       QCString result;
932       while ((i=arg.find("{{",p))!=-1)
933       {
934         result+=arg.mid(p,i-p);
935         int j=arg.find("}}",i+2);
936         if (j!=-1)
937         {
938           QCString var = arg.mid(i+2,j-i-2);
939           TemplateVariant val=s->get(var);
940           //printf("found argument %s value=%s\n",var.data(),val.toString().data());
941           result+=val.toString();
942           p=j+2;
943         }
944         else
945         {
946           p=i+1;
947         }
948       }
949       result+=arg.right(arg.length()-p);
950       return result;
951     }
952 };
953
954 //--------------------------------------------------------------------
955
956 /** @brief The implementation of the "groupBy" filter */
957 class FilterGroupBy
958 {
959     struct ListElem
960     {
961       ListElem(const QCString &k,const TemplateVariant &v) : key(k), value(v) {}
962       QCString key;
963       TemplateVariant value;
964     };
965     class SortList : public QList<ListElem>
966     {
967       public:
968         SortList() { setAutoDelete(TRUE); }
969       private:
970         int compareValues(const ListElem *item1,const ListElem *item2) const
971         {
972           return qstrcmp(item1->key,item2->key);
973         }
974     };
975   public:
976     static TemplateVariant apply(const TemplateVariant &v,const TemplateVariant &args)
977     {
978       if (v.type()==TemplateVariant::List && args.type()==TemplateVariant::String)
979       {
980         //printf("FilterListSort::apply: v=%s args=%s\n",v.toString().data(),args.toString().data());
981         TemplateListIntf::ConstIterator *it = v.toList()->createIterator();
982
983         TemplateVariant item;
984         TemplateList *result = TemplateList::alloc();
985
986         // create list of items based on v using the data in args as a sort key
987         SortList sortList;
988         for (it->toFirst();(it->current(item));it->toNext())
989         {
990           TemplateStructIntf *s = item.toStruct();
991           if (s)
992           {
993             QCString sortKey = determineSortKey(s,args.toString());
994             sortList.append(new ListElem(sortKey,item));
995             //printf("sortKey=%s\n",sortKey.data());
996           }
997         }
998         delete it;
999
1000         // sort the list
1001         sortList.sort();
1002
1003         // add sorted items to the result list
1004         QListIterator<ListElem> sit(sortList);
1005         ListElem *elem;
1006         TemplateList *groupList=0;
1007         QCString prevKey;
1008         for (sit.toFirst();(elem=sit.current());++sit)
1009         {
1010           if (groupList==0 || elem->key!=prevKey)
1011           {
1012             groupList = TemplateList::alloc();
1013             result->append(groupList);
1014             prevKey = elem->key;
1015           }
1016           groupList->append(elem->value);
1017         }
1018         return result;
1019       }
1020       return v;
1021     }
1022
1023   private:
1024     static QCString determineSortKey(TemplateStructIntf *s,const QCString &attribName)
1025     {
1026        TemplateVariant v = s->get(attribName);
1027        return v.toString();
1028     }
1029 };
1030
1031 //--------------------------------------------------------------------
1032
1033 /** @brief The implementation of the "relative" filter */
1034 class FilterRelative
1035 {
1036   public:
1037     static TemplateVariant apply(const TemplateVariant &v,const TemplateVariant &)
1038     {
1039       if (v.isValid() && v.type()==TemplateVariant::String && v.toString().left(2)=="..")
1040       {
1041         return TRUE;
1042       }
1043       else
1044       {
1045         return FALSE;
1046       }
1047     }
1048 };
1049
1050 //--------------------------------------------------------------------
1051
1052 /** @brief The implementation of the "paginate" filter */
1053 class FilterPaginate
1054 {
1055   public:
1056     static TemplateVariant apply(const TemplateVariant &v,const TemplateVariant &args)
1057     {
1058       if (v.isValid() && v.type()==TemplateVariant::List &&
1059           args.isValid() && args.type()==TemplateVariant::Integer)
1060       {
1061         int pageSize = args.toInt();
1062         TemplateListIntf *list   = v.toList();
1063         TemplateList     *result = TemplateList::alloc();
1064         TemplateListIntf::ConstIterator *it = list->createIterator();
1065         TemplateVariant   item;
1066         TemplateList     *pageList=0;
1067         int i = 0;
1068         for (it->toFirst();(it->current(item));it->toNext())
1069         {
1070           if (pageList==0)
1071           {
1072             pageList = TemplateList::alloc();
1073             result->append(pageList);
1074           }
1075           pageList->append(item);
1076           i++;
1077           if (i==pageSize) // page is full start a new one
1078           {
1079             pageList=0;
1080             i=0;
1081           }
1082         }
1083         delete it;
1084         return result;
1085       }
1086       else // wrong arguments
1087       {
1088         return v;
1089       }
1090     }
1091 };
1092
1093 //--------------------------------------------------------------------
1094
1095 /** @brief The implementation of the "alphaIndex" filter */
1096 class FilterAlphaIndex
1097 {
1098   private:
1099     struct ListElem
1100     {
1101       ListElem(uint k,const TemplateVariant &v) : key(k), value(v) {}
1102       uint key;
1103       TemplateVariant value;
1104     };
1105     class SortList : public QList<ListElem>
1106     {
1107       public:
1108         SortList() { setAutoDelete(TRUE); }
1109       private:
1110         int compareValues(const ListElem *item1,const ListElem *item2) const
1111         {
1112           return item1->key-item2->key;
1113         }
1114     };
1115     static QCString keyToLetter(uint startLetter)
1116     {
1117       return QString(QChar(startLetter)).utf8();
1118     }
1119     static QCString keyToLabel(uint startLetter)
1120     {
1121       char s[11]; // 0x12345678 + '\0'
1122       if ((startLetter>='0' && startLetter<='9') ||
1123           (startLetter>='a' && startLetter<='z') ||
1124           (startLetter>='A' && startLetter<='Z'))
1125       {
1126         int i=0;
1127         if (startLetter>='0' && startLetter<='9') s[i++] = 'x';
1128         s[i++]=tolower((char)startLetter);
1129         s[i++]=0;
1130       }
1131       else
1132       {
1133         const char hex[]="0123456789abcdef";
1134         int i=0;
1135         s[i++]='x';
1136         if (startLetter>(1<<24)) // 4 byte character
1137         {
1138           s[i++]=hex[(startLetter>>28)&0xf];
1139           s[i++]=hex[(startLetter>>24)&0xf];
1140         }
1141         if (startLetter>(1<<16)) // 3 byte character
1142         {
1143           s[i++]=hex[(startLetter>>20)&0xf];
1144           s[i++]=hex[(startLetter>>16)&0xf];
1145         }
1146         if (startLetter>(1<<8)) // 2 byte character
1147         {
1148           s[i++]=hex[(startLetter>>12)&0xf];
1149           s[i++]=hex[(startLetter>>8)&0xf];
1150         }
1151         // one byte character
1152         s[i++]=hex[(startLetter>>4)&0xf];
1153         s[i++]=hex[(startLetter>>0)&0xf];
1154         s[i++]=0;
1155       }
1156       return s;
1157     }
1158     static uint determineSortKey(TemplateStructIntf *s,const QCString &attribName)
1159     {
1160        TemplateVariant v = s->get(attribName);
1161        int index = getPrefixIndex(v.toString());
1162        return getUtf8CodeToUpper(v.toString(),index);
1163     }
1164
1165   public:
1166     static TemplateVariant apply(const TemplateVariant &v,const TemplateVariant &args)
1167     {
1168       if (v.type()==TemplateVariant::List && args.type()==TemplateVariant::String)
1169       {
1170         //printf("FilterListSort::apply: v=%s args=%s\n",v.toString().data(),args.toString().data());
1171         TemplateListIntf::ConstIterator *it = v.toList()->createIterator();
1172
1173         TemplateVariant item;
1174         TemplateList *result = TemplateList::alloc();
1175
1176         // create list of items based on v using the data in args as a sort key
1177         SortList sortList;
1178         for (it->toFirst();(it->current(item));it->toNext())
1179         {
1180           TemplateStructIntf *s = item.toStruct();
1181           if (s)
1182           {
1183             uint sortKey = determineSortKey(s,args.toString());
1184             sortList.append(new ListElem(sortKey,item));
1185             //printf("sortKey=%s\n",sortKey.data());
1186           }
1187         }
1188         delete it;
1189
1190         // sort the list
1191         sortList.sort();
1192
1193         // create an index from the sorted list
1194         uint letter=0;
1195         QListIterator<ListElem> sit(sortList);
1196         ListElem *elem;
1197         TemplateStruct *indexNode = 0;
1198         TemplateList *indexList = 0;
1199         for (sit.toFirst();(elem=sit.current());++sit)
1200         {
1201           if (letter!=elem->key || indexNode==0)
1202           {
1203             // create new indexNode
1204             indexNode = TemplateStruct::alloc();
1205             indexList = TemplateList::alloc();
1206             indexNode->set("letter", keyToLetter(elem->key));
1207             indexNode->set("label",  keyToLabel(elem->key));
1208             indexNode->set("items",indexList);
1209             result->append(indexNode);
1210             letter=elem->key;
1211           }
1212           indexList->append(elem->value);
1213         }
1214         return result;
1215       }
1216       return v;
1217     }
1218 };
1219
1220 //--------------------------------------------------------------------
1221
1222 /** @brief The implementation of the "default" filter */
1223 class FilterStripPath
1224 {
1225   public:
1226     static TemplateVariant apply(const TemplateVariant &v,const TemplateVariant &)
1227     {
1228       if (!v.isValid() || v.type()!=TemplateVariant::String)
1229       {
1230         return v;
1231       }
1232       QCString result = v.toString();
1233       int i=result.findRev('/');
1234       if (i!=-1)
1235       {
1236         result=result.mid(i+1);
1237       }
1238       i=result.findRev('\\');
1239       if (i!=-1)
1240       {
1241         result=result.mid(i+1);
1242       }
1243       return result;
1244     }
1245 };
1246
1247 //--------------------------------------------------------------------
1248
1249 /** @brief The implementation of the "default" filter */
1250 class FilterNoWrap
1251 {
1252   public:
1253     static TemplateVariant apply(const TemplateVariant &v,const TemplateVariant &)
1254     {
1255       if (!v.isValid() || v.type()!=TemplateVariant::String)
1256       {
1257         return v;
1258       }
1259       QCString s = v.toString();
1260       return substitute(s," ","&#160;");
1261     }
1262 };
1263
1264 //--------------------------------------------------------------------
1265
1266 /** @brief The implementation of the "divisibleby" filter */
1267 class FilterDivisibleBy
1268 {
1269   public:
1270     static TemplateVariant apply(const TemplateVariant &v,const TemplateVariant &n)
1271     {
1272       if (!v.isValid() || !n.isValid())
1273       {
1274         return TemplateVariant();
1275       }
1276       if (v.type()==TemplateVariant::Integer && n.type()==TemplateVariant::Integer)
1277       {
1278         int ni = n.toInt();
1279         if (ni>0)
1280         {
1281           return TemplateVariant((v.toInt()%ni)==0);
1282         }
1283         else
1284         {
1285           return TemplateVariant(FALSE);
1286         }
1287       }
1288       else
1289       {
1290         return TemplateVariant();
1291       }
1292     }
1293 };
1294
1295 //--------------------------------------------------------------------
1296
1297 /** @brief The implementation of the "isRelativeURL" filter */
1298 class FilterIsRelativeURL
1299 {
1300   public:
1301     static TemplateVariant apply(const TemplateVariant &v,const TemplateVariant &)
1302     {
1303       if (v.isValid() && v.type()==TemplateVariant::String)
1304       {
1305         QCString s = v.toString();
1306         if (!s.isEmpty() && s.at(0)=='!') return TRUE;
1307       }
1308       return FALSE;
1309     }
1310 };
1311
1312 //--------------------------------------------------------------------
1313
1314 /** @brief The implementation of the "isRelativeURL" filter */
1315 class FilterIsAbsoluteURL
1316 {
1317   public:
1318     static TemplateVariant apply(const TemplateVariant &v,const TemplateVariant &)
1319     {
1320       if (v.isValid() && v.type()==TemplateVariant::String)
1321       {
1322         QCString s = v.toString();
1323         if (!s.isEmpty() && s.at(0)=='^') return TRUE;
1324       }
1325       return FALSE;
1326     }
1327 };
1328
1329 //--------------------------------------------------------------------
1330
1331 /** @brief The implementation of the "decodeURL" filter
1332  *  The leading character is removed from the value in case it is a ^ or !.
1333  *  - ^ is used to encode a absolute URL
1334  *  - ! is used to encode a relative URL
1335  */
1336 class FilterDecodeURL
1337 {
1338   public:
1339     static TemplateVariant apply(const TemplateVariant &v,const TemplateVariant &)
1340     {
1341       if (v.isValid() && v.type()==TemplateVariant::String)
1342       {
1343         QCString s = v.toString();
1344         if (!s.isEmpty() && (s.at(0)=='^' || s.at(0)=='!'))
1345         {
1346           return s.mid(1);
1347         }
1348       }
1349       return v;
1350     }
1351 };
1352
1353
1354 //--------------------------------------------------------------------
1355
1356 /** @brief Factory singleton for registering and creating filters */
1357 class TemplateFilterFactory
1358 {
1359   public:
1360     typedef TemplateVariant (FilterFunction)(const TemplateVariant &v,const TemplateVariant &arg);
1361
1362     static TemplateFilterFactory *instance()
1363     {
1364       static TemplateFilterFactory *instance = 0;
1365       if (instance==0) instance = new TemplateFilterFactory;
1366       return instance;
1367     }
1368
1369     TemplateVariant apply(const QCString &name,const TemplateVariant &v,const TemplateVariant &arg, bool &ok)
1370     {
1371       FilterFunction *func = (FilterFunction*)m_registry.find(name);
1372       if (func)
1373       {
1374         ok=TRUE;
1375         return (*func)(v,arg);
1376       }
1377       else
1378       {
1379         ok=FALSE;
1380         return v;
1381       }
1382     }
1383
1384     void registerFilter(const QCString &name,FilterFunction *func)
1385     {
1386       m_registry.insert(name,(void*)func);
1387     }
1388
1389     /** @brief Helper class for registering a filter function */
1390     template<class T> class AutoRegister
1391     {
1392       public:
1393         AutoRegister<T>(const QCString &key)
1394         {
1395           TemplateFilterFactory::instance()->registerFilter(key,&T::apply);
1396         }
1397     };
1398
1399   private:
1400     QDict<void> m_registry;
1401 };
1402
1403 // register a handlers for each filter we support
1404 static TemplateFilterFactory::AutoRegister<FilterAdd>                fAdd("add");
1405 static TemplateFilterFactory::AutoRegister<FilterGet>                fGet("get");
1406 static TemplateFilterFactory::AutoRegister<FilterRaw>                fRaw("raw");
1407 static TemplateFilterFactory::AutoRegister<FilterList>               fList("list");
1408 static TemplateFilterFactory::AutoRegister<FilterAppend>             fAppend("append");
1409 static TemplateFilterFactory::AutoRegister<FilterLength>             fLength("length");
1410 static TemplateFilterFactory::AutoRegister<FilterNoWrap>             fNoWrap("nowrap");
1411 static TemplateFilterFactory::AutoRegister<FilterFlatten>            fFlatten("flatten");
1412 static TemplateFilterFactory::AutoRegister<FilterDefault>            fDefault("default");
1413 static TemplateFilterFactory::AutoRegister<FilterPrepend>            fPrepend("prepend");
1414 static TemplateFilterFactory::AutoRegister<FilterGroupBy>            fGroupBy("groupBy");
1415 static TemplateFilterFactory::AutoRegister<FilterRelative>           fRelative("relative");
1416 static TemplateFilterFactory::AutoRegister<FilterListSort>           fListSort("listsort");
1417 static TemplateFilterFactory::AutoRegister<FilterTexLabel>           fTexLabel("texLabel");
1418 static TemplateFilterFactory::AutoRegister<FilterTexIndex>           fTexIndex("texIndex");
1419 static TemplateFilterFactory::AutoRegister<FilterPaginate>           fPaginate("paginate");
1420 static TemplateFilterFactory::AutoRegister<FilterStripPath>          fStripPath("stripPath");
1421 static TemplateFilterFactory::AutoRegister<FilterDecodeURL>          fDecodeURL("decodeURL");
1422 static TemplateFilterFactory::AutoRegister<FilterAlphaIndex>         fAlphaIndex("alphaIndex");
1423 static TemplateFilterFactory::AutoRegister<FilterDivisibleBy>        fDivisibleBy("divisibleby");
1424 static TemplateFilterFactory::AutoRegister<FilterIsRelativeURL>      fIsRelativeURL("isRelativeURL");
1425 static TemplateFilterFactory::AutoRegister<FilterIsAbsoluteURL>      fIsAbsoluteURL("isAbsoluteURL");
1426
1427 //--------------------------------------------------------------------
1428
1429 /** @brief Base class for all nodes in the abstract syntax tree of an
1430  *  expression.
1431  */
1432 class ExprAst
1433 {
1434   public:
1435     virtual ~ExprAst() {}
1436     virtual TemplateVariant resolve(TemplateContext *) { return TemplateVariant(); }
1437 };
1438
1439 /** @brief Class representing a number in the AST */
1440 class ExprAstNumber : public ExprAst
1441 {
1442   public:
1443     ExprAstNumber(int num) : m_number(num)
1444     { TRACE(("ExprAstNumber(%d)\n",num)); }
1445     int number() const { return m_number; }
1446     virtual TemplateVariant resolve(TemplateContext *) { return TemplateVariant(m_number); }
1447   private:
1448     int m_number;
1449 };
1450
1451 /** @brief Class representing a variable in the AST */
1452 class ExprAstVariable : public ExprAst
1453 {
1454   public:
1455     ExprAstVariable(const char *name) : m_name(name)
1456     { TRACE(("ExprAstVariable(%s)\n",name)); }
1457     const QCString &name() const { return m_name; }
1458     virtual TemplateVariant resolve(TemplateContext *c)
1459     {
1460       TemplateVariant v = c->get(m_name);
1461       TemplateContextImpl *ci = dynamic_cast<TemplateContextImpl*>(c);
1462       if (!v.isValid())
1463       {
1464         if (ci) ci->warn(ci->templateName(),ci->line(),"undefined variable '%s' in expression",m_name.data());
1465       }
1466       return v;
1467     }
1468   private:
1469     QCString m_name;
1470 };
1471
1472 class ExprAstFunctionVariable : public ExprAst
1473 {
1474   public:
1475     ExprAstFunctionVariable(ExprAst *var,const QList<ExprAst> &args)
1476       : m_var(var), m_args(args)
1477     { TRACE(("ExprAstFunctionVariable()\n"));
1478       m_args.setAutoDelete(TRUE);
1479     }
1480    ~ExprAstFunctionVariable()
1481     {
1482       delete m_var;
1483     }
1484     virtual TemplateVariant resolve(TemplateContext *c)
1485     {
1486       QValueList<TemplateVariant> args;
1487       for (uint i=0;i<m_args.count();i++)
1488       {
1489         TemplateVariant v = m_args.at(i)->resolve(c);
1490         args.append(v);
1491       }
1492       TemplateVariant v = m_var->resolve(c);
1493       if (v.type()==TemplateVariant::Function)
1494       {
1495         v = v.call(args);
1496       }
1497       return v;
1498     }
1499   private:
1500     ExprAst *m_var;
1501     QList<ExprAst> m_args;
1502 };
1503
1504 /** @brief Class representing a filter in the AST */
1505 class ExprAstFilter : public ExprAst
1506 {
1507   public:
1508     ExprAstFilter(const char *name,ExprAst *arg) : m_name(name), m_arg(arg)
1509     { TRACE(("ExprAstFilter(%s)\n",name)); }
1510    ~ExprAstFilter() { delete m_arg; }
1511     const QCString &name() const { return m_name; }
1512     TemplateVariant apply(const TemplateVariant &v,TemplateContext *c)
1513     {
1514       TemplateContextImpl *ci = dynamic_cast<TemplateContextImpl*>(c);
1515       if (ci==0) return v; // should not happen
1516       TRACE(("Applying filter '%s' to '%s' (type=%d)\n",m_name.data(),v.toString().data(),v.type()));
1517       TemplateVariant arg;
1518       if (m_arg) arg = m_arg->resolve(c);
1519       bool ok;
1520       TemplateVariant result = TemplateFilterFactory::instance()->apply(m_name,v,arg,ok);
1521       if (!ok)
1522       {
1523         ci->warn(ci->templateName(),ci->line(),"unknown filter '%s'",m_name.data());
1524       }
1525       return result;
1526     }
1527   private:
1528     QCString m_name;
1529     ExprAst *m_arg;
1530 };
1531
1532 /** @brief Class representing a filter applied to an expression in the AST */
1533 class ExprAstFilterAppl : public ExprAst
1534 {
1535   public:
1536     ExprAstFilterAppl(ExprAst *expr,ExprAstFilter *filter)
1537       : m_expr(expr), m_filter(filter)
1538     { TRACE(("ExprAstFilterAppl\n")); }
1539    ~ExprAstFilterAppl() { delete m_expr; delete m_filter; }
1540     virtual TemplateVariant resolve(TemplateContext *c)
1541     {
1542       return m_filter->apply(m_expr->resolve(c),c);
1543     }
1544   private:
1545     ExprAst *m_expr;
1546     ExprAstFilter *m_filter;
1547 };
1548
1549 /** @brief Class representing a string literal in the AST */
1550 class ExprAstLiteral : public ExprAst
1551 {
1552   public:
1553     ExprAstLiteral(const char *lit) : m_literal(lit)
1554     { TRACE(("ExprAstLiteral(%s)\n",lit)); }
1555     const QCString &literal() const { return m_literal; }
1556     virtual TemplateVariant resolve(TemplateContext *) { return TemplateVariant(m_literal); }
1557   private:
1558     QCString m_literal;
1559 };
1560
1561 /** @brief Class representing a negation (not) operator in the AST */
1562 class ExprAstNegate : public ExprAst
1563 {
1564   public:
1565     ExprAstNegate(ExprAst *expr) : m_expr(expr)
1566     { TRACE(("ExprAstNegate\n")); }
1567    ~ExprAstNegate() { delete m_expr; }
1568     virtual TemplateVariant resolve(TemplateContext *c)
1569     { return TemplateVariant(!m_expr->resolve(c).toBool()); }
1570   private:
1571     ExprAst *m_expr;
1572 };
1573
1574 class ExprAstUnary : public ExprAst
1575 {
1576   public:
1577     ExprAstUnary(Operator::Type op,ExprAst *exp) : m_operator(op), m_exp(exp)
1578     { TRACE(("ExprAstUnary %s\n",Operator::toString(op))); }
1579    ~ExprAstUnary() { delete m_exp; }
1580     virtual TemplateVariant resolve(TemplateContext *c)
1581     {
1582       TemplateVariant exp = m_exp->resolve(c);
1583       switch (m_operator)
1584       {
1585         case Operator::Minus:
1586           return -exp.toInt();
1587         default:
1588           return TemplateVariant();
1589       }
1590     }
1591   private:
1592     Operator::Type m_operator;
1593     ExprAst *m_exp;
1594 };
1595
1596 /** @brief Class representing a binary operator in the AST */
1597 class ExprAstBinary : public ExprAst
1598 {
1599   public:
1600     ExprAstBinary(Operator::Type op,ExprAst *lhs,ExprAst *rhs)
1601       : m_operator(op), m_lhs(lhs), m_rhs(rhs)
1602     { TRACE(("ExprAstBinary %s\n",Operator::toString(op))); }
1603    ~ExprAstBinary() { delete m_lhs; delete m_rhs; }
1604     virtual TemplateVariant resolve(TemplateContext *c)
1605     {
1606       TemplateContextImpl *ci = dynamic_cast<TemplateContextImpl*>(c);
1607       if (ci==0) return TemplateVariant(); // should not happen
1608       TemplateVariant lhs = m_lhs->resolve(c);
1609       TemplateVariant rhs = m_rhs ? m_rhs->resolve(c) : TemplateVariant();
1610       switch(m_operator)
1611       {
1612         case Operator::Or:
1613           return TemplateVariant(lhs.toBool() || rhs.toBool());
1614         case Operator::And:
1615           return TemplateVariant(lhs.toBool() && rhs.toBool());
1616         case Operator::Equal:
1617           return TemplateVariant(lhs == rhs);
1618         case Operator::NotEqual:
1619           return TemplateVariant(!(lhs == rhs));
1620         case Operator::Less:
1621           if (lhs.type()==TemplateVariant::String && rhs.type()==TemplateVariant::String)
1622           {
1623             return lhs.toString()<rhs.toString();
1624           }
1625           else
1626           {
1627             return lhs.toInt()<rhs.toInt();
1628           }
1629         case Operator::Greater:
1630           if (lhs.type()==TemplateVariant::String && rhs.type()==TemplateVariant::String)
1631           {
1632             return !(lhs.toString()<rhs.toString());
1633           }
1634           else
1635           {
1636             return lhs.toInt()>rhs.toInt();
1637           }
1638         case Operator::LessEqual:
1639           if (lhs.type()==TemplateVariant::String && rhs.type()==TemplateVariant::String)
1640           {
1641             return lhs.toString()==rhs.toString() || lhs.toString()<rhs.toString();
1642           }
1643           else
1644           {
1645             return lhs.toInt()<=rhs.toInt();
1646           }
1647         case Operator::GreaterEqual:
1648           if (lhs.type()==TemplateVariant::String && rhs.type()==TemplateVariant::String)
1649           {
1650             return lhs.toString()==rhs.toString() || !(lhs.toString()<rhs.toString());
1651           }
1652           else
1653           {
1654             return lhs.toInt()>=rhs.toInt();
1655           }
1656         case Operator::Plus:
1657           {
1658             return TemplateVariant(lhs.toInt() + rhs.toInt());
1659           }
1660         case Operator::Minus:
1661           {
1662             return TemplateVariant(lhs.toInt() - rhs.toInt());
1663           }
1664         case Operator::Multiply:
1665           {
1666             return TemplateVariant(lhs.toInt() * rhs.toInt());
1667           }
1668         case Operator::Divide:
1669           {
1670             int denom = rhs.toInt();
1671             if (denom!=0)
1672             {
1673               return TemplateVariant(lhs.toInt() / denom);
1674             }
1675             else // divide by zero
1676             {
1677               ci->warn(ci->templateName(),ci->line(),"division by zero while evaluating expression is undefined");
1678               return 0;
1679             }
1680           }
1681         case Operator::Modulo:
1682           {
1683             int denom = rhs.toInt();
1684             if (denom!=0)
1685             {
1686               return TemplateVariant(lhs.toInt() % denom);
1687             }
1688             else // module zero
1689             {
1690               ci->warn(ci->templateName(),ci->line(),"modulo zero while evaluating expression is undefined");
1691               return 0;
1692             }
1693           }
1694         default:
1695           return TemplateVariant();
1696       }
1697     }
1698   private:
1699     Operator::Type m_operator;
1700     ExprAst *m_lhs;
1701     ExprAst *m_rhs;
1702 };
1703
1704 //----------------------------------------------------------
1705
1706 /** @brief Base class of all nodes in a template's AST */
1707 class TemplateNode
1708 {
1709   public:
1710     TemplateNode(TemplateNode *parent) : m_parent(parent) {}
1711     virtual ~TemplateNode() {}
1712
1713     virtual void render(FTextStream &ts, TemplateContext *c) = 0;
1714
1715     TemplateNode *parent() { return m_parent; }
1716
1717   private:
1718     TemplateNode *m_parent;
1719 };
1720
1721 //----------------------------------------------------------
1722
1723 /** @brief Parser for templates */
1724 class TemplateParser
1725 {
1726   public:
1727     TemplateParser(const TemplateEngine *engine,
1728                    const QCString &templateName,QList<TemplateToken> &tokens);
1729     void parse(TemplateNode *parent,int line,const QStrList &stopAt,
1730                QList<TemplateNode> &nodes);
1731     bool hasNextToken() const;
1732     TemplateToken *takeNextToken();
1733     void removeNextToken();
1734     void prependToken(const TemplateToken *token);
1735     const TemplateToken *currentToken() const;
1736     QCString templateName() const { return m_templateName; }
1737     void warn(const char *fileName,int line,const char *fmt,...) const;
1738   private:
1739     const TemplateEngine *m_engine;
1740     QCString m_templateName;
1741     QList<TemplateToken> &m_tokens;
1742 };
1743
1744 //--------------------------------------------------------------------
1745
1746 /** @brief Recursive decent parser for Django style template expressions.
1747  */
1748 class ExpressionParser
1749 {
1750   public:
1751     ExpressionParser(const TemplateParser *parser,int line)
1752       : m_parser(parser), m_line(line), m_tokenStream(0)
1753     {
1754     }
1755     virtual ~ExpressionParser()
1756     {
1757     }
1758
1759     ExprAst *parse(const char *expr)
1760     {
1761       if (expr==0) return 0;
1762       m_tokenStream = expr;
1763       getNextToken();
1764       return parseExpression();
1765     }
1766
1767   private:
1768
1769     /** @brief Class representing a token within an expression. */
1770     class ExprToken
1771     {
1772       public:
1773         ExprToken() : type(Unknown), num(-1), op(Operator::Or)
1774         {
1775         }
1776         enum Type
1777         {
1778           Unknown, Operator, Number, Identifier, Literal
1779         };
1780
1781         Type type;
1782         int num;
1783         QCString id;
1784         Operator::Type op;
1785     };
1786
1787     ExprAst *parseExpression()
1788     {
1789       TRACE(("{parseExpression(%s)\n",m_tokenStream));
1790       ExprAst *result = parseOrExpression();
1791       TRACE(("}parseExpression(%s)\n",m_tokenStream));
1792       return result;
1793     }
1794
1795     ExprAst *parseOrExpression()
1796     {
1797       TRACE(("{parseOrExpression(%s)\n",m_tokenStream));
1798       ExprAst *lhs = parseAndExpression();
1799       if (lhs)
1800       {
1801         while (m_curToken.type==ExprToken::Operator &&
1802             m_curToken.op==Operator::Or)
1803         {
1804           getNextToken();
1805           ExprAst *rhs = parseAndExpression();
1806           lhs = new ExprAstBinary(Operator::Or,lhs,rhs);
1807         }
1808       }
1809       TRACE(("}parseOrExpression(%s)\n",m_tokenStream));
1810       return lhs;
1811     }
1812
1813     ExprAst *parseAndExpression()
1814     {
1815       TRACE(("{parseAndExpression(%s)\n",m_tokenStream));
1816       ExprAst *lhs = parseNotExpression();
1817       if (lhs)
1818       {
1819         while (m_curToken.type==ExprToken::Operator &&
1820                m_curToken.op==Operator::And)
1821         {
1822           getNextToken();
1823           ExprAst *rhs = parseNotExpression();
1824           lhs = new ExprAstBinary(Operator::And,lhs,rhs);
1825         }
1826       }
1827       TRACE(("}parseAndExpression(%s)\n",m_tokenStream));
1828       return lhs;
1829     }
1830
1831     ExprAst *parseNotExpression()
1832     {
1833       TRACE(("{parseNotExpression(%s)\n",m_tokenStream));
1834       ExprAst *result=0;
1835       if (m_curToken.type==ExprToken::Operator &&
1836           m_curToken.op==Operator::Not)
1837       {
1838         getNextToken();
1839         ExprAst *expr = parseCompareExpression();
1840         if (expr==0)
1841         {
1842           warn(m_parser->templateName(),m_line,"argument missing for not operator");
1843           return 0;
1844         }
1845         result = new ExprAstNegate(expr);
1846       }
1847       else
1848       {
1849         result = parseCompareExpression();
1850       }
1851       TRACE(("}parseNotExpression(%s)\n",m_tokenStream));
1852       return result;
1853     }
1854
1855     ExprAst *parseCompareExpression()
1856     {
1857       TRACE(("{parseCompareExpression(%s)\n",m_tokenStream));
1858       ExprAst *lhs = parseAdditiveExpression();
1859       if (lhs)
1860       {
1861         Operator::Type op = m_curToken.op;
1862         if (m_curToken.type==ExprToken::Operator &&
1863             (op==Operator::Less      ||
1864              op==Operator::Greater   ||
1865              op==Operator::Equal     ||
1866              op==Operator::NotEqual  ||
1867              op==Operator::LessEqual ||
1868              op==Operator::GreaterEqual
1869             )
1870            )
1871         {
1872           getNextToken();
1873           ExprAst *rhs = parseNotExpression();
1874           lhs = new ExprAstBinary(op,lhs,rhs);
1875         }
1876       }
1877       TRACE(("}parseCompareExpression(%s)\n",m_tokenStream));
1878       return lhs;
1879     }
1880
1881     ExprAst *parseAdditiveExpression()
1882     {
1883       TRACE(("{parseAdditiveExpression(%s)\n",m_tokenStream));
1884       ExprAst *lhs = parseMultiplicativeExpression();
1885       if (lhs)
1886       {
1887         while (m_curToken.type==ExprToken::Operator &&
1888                (m_curToken.op==Operator::Plus || m_curToken.op==Operator::Minus))
1889         {
1890           Operator::Type op = m_curToken.op;
1891           getNextToken();
1892           ExprAst *rhs = parseMultiplicativeExpression();
1893           lhs = new ExprAstBinary(op,lhs,rhs);
1894         }
1895       }
1896       TRACE(("}parseAdditiveExpression(%s)\n",m_tokenStream));
1897       return lhs;
1898     }
1899
1900     ExprAst *parseMultiplicativeExpression()
1901     {
1902       TRACE(("{parseMultiplicativeExpression(%s)\n",m_tokenStream));
1903       ExprAst *lhs = parseUnaryExpression();
1904       if (lhs)
1905       {
1906         while (m_curToken.type==ExprToken::Operator &&
1907                (m_curToken.op==Operator::Multiply || m_curToken.op==Operator::Divide || m_curToken.op==Operator::Modulo))
1908         {
1909           Operator::Type op = m_curToken.op;
1910           getNextToken();
1911           ExprAst *rhs = parseUnaryExpression();
1912           lhs = new ExprAstBinary(op,lhs,rhs);
1913         }
1914       }
1915       TRACE(("}parseMultiplicativeExpression(%s)\n",m_tokenStream));
1916       return lhs;
1917     }
1918
1919     ExprAst *parseUnaryExpression()
1920     {
1921       TRACE(("{parseUnaryExpression(%s)\n",m_tokenStream));
1922       ExprAst *result=0;
1923       if (m_curToken.type==ExprToken::Operator)
1924       {
1925         if (m_curToken.op==Operator::Plus)
1926         {
1927           getNextToken();
1928           result = parsePrimaryExpression();
1929         }
1930         else if (m_curToken.op==Operator::Minus)
1931         {
1932           getNextToken();
1933           ExprAst *rhs = parsePrimaryExpression();
1934           result = new ExprAstUnary(m_curToken.op,rhs);
1935         }
1936         else
1937         {
1938           result = parsePrimaryExpression();
1939         }
1940       }
1941       else
1942       {
1943         result = parsePrimaryExpression();
1944       }
1945       TRACE(("}parseUnaryExpression(%s)\n",m_tokenStream));
1946       return result;
1947     }
1948
1949     ExprAst *parsePrimaryExpression()
1950     {
1951       TRACE(("{parsePrimary(%s)\n",m_tokenStream));
1952       ExprAst *result=0;
1953       switch (m_curToken.type)
1954       {
1955         case ExprToken::Number:
1956           result = parseNumber();
1957           break;
1958         case ExprToken::Identifier:
1959           result = parseFilteredVariable();
1960           break;
1961         case ExprToken::Literal:
1962           result = parseLiteral();
1963           break;
1964         case ExprToken::Operator:
1965           if (m_curToken.op==Operator::LeftParen)
1966           {
1967             getNextToken(); // skip over opening bracket
1968             result = parseExpression();
1969             if (m_curToken.type!=ExprToken::Operator ||
1970                 m_curToken.op!=Operator::RightParen)
1971             {
1972               warn(m_parser->templateName(),m_line,"missing closing parenthesis");
1973             }
1974             else
1975             {
1976               getNextToken(); // skip over closing bracket
1977             }
1978           }
1979           else
1980           {
1981             warn(m_parser->templateName(),m_line,"unexpected operator '%s' in expression",
1982                 Operator::toString(m_curToken.op));
1983           }
1984           break;
1985         default:
1986           warn(m_parser->templateName(),m_line,"unexpected token in expression");
1987       }
1988       TRACE(("}parsePrimary(%s)\n",m_tokenStream));
1989       return result;
1990     }
1991
1992     ExprAst *parseNumber()
1993     {
1994       TRACE(("{parseNumber(%d)\n",m_curToken.num));
1995       ExprAst *num = new ExprAstNumber(m_curToken.num);
1996       getNextToken();
1997       TRACE(("}parseNumber()\n"));
1998       return num;
1999     }
2000
2001     ExprAst *parseIdentifier()
2002     {
2003       TRACE(("{parseIdentifier(%s)\n",m_curToken.id.data()));
2004       ExprAst *id = new ExprAstVariable(m_curToken.id);
2005       getNextToken();
2006       TRACE(("}parseIdentifier()\n"));
2007       return id;
2008     }
2009
2010     ExprAst *parseLiteral()
2011     {
2012       TRACE(("{parseLiteral(%s)\n",m_curToken.id.data()));
2013       ExprAst *expr = new ExprAstLiteral(m_curToken.id);
2014       getNextToken();
2015       TRACE(("}parseLiteral()\n"));
2016       return expr;
2017     }
2018
2019     ExprAst *parseIdentifierOptionalArgs()
2020     {
2021       TRACE(("{parseIdentifierOptionalArgs(%s)\n",m_curToken.id.data()));
2022       ExprAst *expr = parseIdentifier();
2023       if (expr)
2024       {
2025         if (m_curToken.type==ExprToken::Operator &&
2026             m_curToken.op==Operator::Colon)
2027         {
2028           getNextToken();
2029           ExprAst *argExpr = parsePrimaryExpression();
2030           QList<ExprAst> args;
2031           args.append(argExpr);
2032           while (m_curToken.type==ExprToken::Operator &&
2033                  m_curToken.op==Operator::Comma)
2034           {
2035             getNextToken();
2036             argExpr = parsePrimaryExpression();
2037             args.append(argExpr);
2038           }
2039           expr = new ExprAstFunctionVariable(expr,args);
2040         }
2041       }
2042       TRACE(("}parseIdentifierOptionalArgs()\n"));
2043       return expr;
2044     }
2045
2046     ExprAst *parseFilteredVariable()
2047     {
2048       TRACE(("{parseFilteredVariable()\n"));
2049       ExprAst *expr = parseIdentifierOptionalArgs();
2050       if (expr)
2051       {
2052         while (m_curToken.type==ExprToken::Operator &&
2053                m_curToken.op==Operator::Filter)
2054         {
2055           getNextToken();
2056           ExprAstFilter *filter = parseFilter();
2057           if (!filter) break;
2058           expr = new ExprAstFilterAppl(expr,filter);
2059         }
2060       }
2061       TRACE(("}parseFilteredVariable()\n"));
2062       return expr;
2063     }
2064
2065     ExprAstFilter *parseFilter()
2066     {
2067       TRACE(("{parseFilter(%s)\n",m_curToken.id.data()));
2068       QCString filterName = m_curToken.id;
2069       getNextToken();
2070       ExprAst *argExpr=0;
2071       if (m_curToken.type==ExprToken::Operator &&
2072           m_curToken.op==Operator::Colon)
2073       {
2074         getNextToken();
2075         argExpr = parsePrimaryExpression();
2076       }
2077       ExprAstFilter *filter = new ExprAstFilter(filterName,argExpr);
2078       TRACE(("}parseFilter()\n"));
2079       return filter;
2080     }
2081
2082
2083     bool getNextToken()
2084     {
2085       const char *p = m_tokenStream;
2086       char s[2];
2087       s[1]=0;
2088       if (p==0 || *p=='\0') return FALSE;
2089       while (*p==' ') p++; // skip over spaces
2090       char c=*p;
2091       if (*p=='\0') // only spaces...
2092       {
2093         m_tokenStream = p;
2094         return FALSE;
2095       }
2096       const char *q = p;
2097       switch (c)
2098       {
2099         case '=':
2100           if (c=='=' && *(p+1)=='=') // equal
2101           {
2102             m_curToken.op = Operator::Equal;
2103             p+=2;
2104           }
2105           break;
2106         case '!':
2107           if (c=='!' && *(p+1)=='=') // not equal
2108           {
2109             m_curToken.op = Operator::NotEqual;
2110             p+=2;
2111           }
2112           break;
2113         case '<':
2114           if (c=='<' && *(p+1)=='=') // less or equal
2115           {
2116             m_curToken.op = Operator::LessEqual;
2117             p+=2;
2118           }
2119           else // less
2120           {
2121             m_curToken.op = Operator::Less;
2122             p++;
2123           }
2124           break;
2125         case '>':
2126           if (c=='>' && *(p+1)=='=') // greater or equal
2127           {
2128             m_curToken.op = Operator::GreaterEqual;
2129             p+=2;
2130           }
2131           else // greater
2132           {
2133             m_curToken.op = Operator::Greater;
2134             p++;
2135           }
2136           break;
2137         case '(':
2138           m_curToken.op = Operator::LeftParen;
2139           p++;
2140           break;
2141         case ')':
2142           m_curToken.op = Operator::RightParen;
2143           p++;
2144           break;
2145         case '|':
2146           m_curToken.op = Operator::Filter;
2147           p++;
2148           break;
2149         case '+':
2150           m_curToken.op = Operator::Plus;
2151           p++;
2152           break;
2153         case '-':
2154           m_curToken.op = Operator::Minus;
2155           p++;
2156           break;
2157         case '*':
2158           m_curToken.op = Operator::Multiply;
2159           p++;
2160           break;
2161         case '/':
2162           m_curToken.op = Operator::Divide;
2163           p++;
2164           break;
2165         case '%':
2166           m_curToken.op = Operator::Modulo;
2167           p++;
2168           break;
2169         case ':':
2170           m_curToken.op = Operator::Colon;
2171           p++;
2172           break;
2173         case ',':
2174           m_curToken.op = Operator::Comma;
2175           p++;
2176           break;
2177         case 'n':
2178           if (strncmp(p,"not ",4)==0)
2179           {
2180             m_curToken.op = Operator::Not;
2181             p+=4;
2182           }
2183           break;
2184         case 'a':
2185           if (strncmp(p,"and ",4)==0)
2186           {
2187             m_curToken.op = Operator::And;
2188             p+=4;
2189           }
2190           break;
2191         case 'o':
2192           if (strncmp(p,"or ",3)==0)
2193           {
2194             m_curToken.op = Operator::Or;
2195             p+=3;
2196           }
2197           break;
2198         default:
2199           break;
2200       }
2201       if (p!=q) // found an operator
2202       {
2203         m_curToken.type = ExprToken::Operator;
2204       }
2205       else // no token found yet
2206       {
2207         if (c>='0' && c<='9') // number?
2208         {
2209           m_curToken.type = ExprToken::Number;
2210           const char *np = p;
2211           m_curToken.num = 0;
2212           while (*np>='0' && *np<='9')
2213           {
2214             m_curToken.num*=10;
2215             m_curToken.num+=*np-'0';
2216             np++;
2217           }
2218           p=np;
2219         }
2220         else if (c=='_' || (c>='a' && c<='z') || (c>='A' && c<='Z')) // identifier?
2221         {
2222           m_curToken.type = ExprToken::Identifier;
2223           s[0]=c;
2224           m_curToken.id = s;
2225           p++;
2226           while ((c=*p) &&
2227               (c=='_' || c=='.' ||
2228                (c>='a' && c<='z') ||
2229                (c>='A' && c<='Z') ||
2230                (c>='0' && c<='9'))
2231               )
2232           {
2233             s[0]=c;
2234             m_curToken.id+=s;
2235             p++;
2236           }
2237           if (m_curToken.id=="True") // treat true literal as numerical 1
2238           {
2239             m_curToken.type = ExprToken::Number;
2240             m_curToken.num = 1;
2241           }
2242           else if (m_curToken.id=="False") // treat false literal as numerical 0
2243           {
2244             m_curToken.type = ExprToken::Number;
2245             m_curToken.num = 0;
2246           }
2247         }
2248         else if (c=='"' || c=='\'') // string literal
2249         {
2250           m_curToken.type = ExprToken::Literal;
2251           m_curToken.id.resize(0);
2252           p++;
2253           char tokenChar = c;
2254           char cp=0;
2255           while ((c=*p) && (c!=tokenChar || (c==tokenChar && cp=='\\')))
2256           {
2257             s[0]=c;
2258             if (c!='\\' || cp=='\\') // don't add escapes
2259             {
2260               m_curToken.id+=s;
2261             }
2262             cp=c;
2263             p++;
2264           }
2265           if (*p==tokenChar) p++;
2266         }
2267       }
2268       if (p==q) // still no valid token found -> error
2269       {
2270         m_curToken.type = ExprToken::Unknown;
2271         char s[2];
2272         s[0]=c;
2273         s[1]=0;
2274         warn(m_parser->templateName(),m_line,"Found unknown token '%s' (%d) while parsing %s",s,c,m_tokenStream);
2275         m_curToken.id = s;
2276         p++;
2277       }
2278       //TRACE(("token type=%d op=%d num=%d id=%s\n",
2279       //    m_curToken.type,m_curToken.op,m_curToken.num,m_curToken.id.data()));
2280
2281       m_tokenStream = p;
2282       return TRUE;
2283     }
2284
2285     const TemplateParser *m_parser;
2286     ExprToken m_curToken;
2287     int m_line;
2288     const char *m_tokenStream;
2289 };
2290
2291 //----------------------------------------------------------
2292
2293 /** @brief Class representing a lexical token in a template */
2294 class TemplateToken
2295 {
2296   public:
2297     enum Type { Text, Variable, Block };
2298     TemplateToken(Type t,const char *d,int l) : type(t), data(d), line(l) {}
2299     Type type;
2300     QCString data;
2301     int line;
2302 };
2303
2304 //----------------------------------------------------------
2305
2306 /** @brief Class representing a list of AST nodes in a template */
2307 class TemplateNodeList : public QList<TemplateNode>
2308 {
2309   public:
2310     TemplateNodeList()
2311     {
2312       setAutoDelete(TRUE);
2313     }
2314     void render(FTextStream &ts,TemplateContext *c)
2315     {
2316       TRACE(("{TemplateNodeList::render\n"));
2317       QListIterator<TemplateNode> it(*this);
2318       TemplateNode *tn=0;
2319       for (it.toFirst();(tn=it.current());++it)
2320       {
2321         tn->render(ts,c);
2322       }
2323       TRACE(("}TemplateNodeList::render\n"));
2324     }
2325 };
2326
2327 //----------------------------------------------------------
2328
2329 /** @brief Internal class representing the implementation of a template */
2330 class TemplateImpl : public TemplateNode, public Template
2331 {
2332   public:
2333     TemplateImpl(TemplateEngine *e,const QCString &name,const QCString &data,
2334                  const QCString &extension);
2335    ~TemplateImpl();
2336     void render(FTextStream &ts, TemplateContext *c);
2337
2338     TemplateEngine *engine() const { return m_engine; }
2339     TemplateBlockContext *blockContext() { return &m_blockContext; }
2340
2341   private:
2342     TemplateEngine *m_engine;
2343     QCString m_name;
2344     TemplateNodeList m_nodes;
2345     TemplateBlockContext m_blockContext;
2346 };
2347
2348 //----------------------------------------------------------
2349
2350 /** @brief Weak reference wrapper for TemplateStructIntf that provides access to the
2351  *  wrapped struct without holding a reference.
2352  */
2353 class TemplateStructWeakRef : public TemplateStructIntf
2354 {
2355   public:
2356     TemplateStructWeakRef(TemplateStructIntf *ref) : m_ref(ref), m_refCount(0) {}
2357     virtual TemplateVariant get(const char *name) const { return m_ref->get(name); }
2358     virtual int addRef() { return ++m_refCount; }
2359     virtual int release() { int count=--m_refCount; if (count<=0) { delete this; } return count; }
2360   private:
2361     TemplateStructIntf *m_ref;
2362     int m_refCount;
2363 };
2364
2365 //----------------------------------------------------------
2366
2367 TemplateContextImpl::TemplateContextImpl(const TemplateEngine *e)
2368   : m_engine(e), m_templateName("<unknown>"), m_line(1), m_activeEscapeIntf(0),
2369     m_spacelessIntf(0), m_spacelessEnabled(FALSE), m_tabbingEnabled(FALSE), m_indices(TemplateStruct::alloc())
2370 {
2371   m_indexStacks.setAutoDelete(TRUE);
2372   m_contextStack.setAutoDelete(TRUE);
2373   m_escapeIntfDict.setAutoDelete(TRUE);
2374   m_fromUtf8 = (void*)(-1);
2375   push();
2376   set("index",m_indices.get());
2377 }
2378
2379 TemplateContextImpl::~TemplateContextImpl()
2380 {
2381   pop();
2382 }
2383
2384 void TemplateContextImpl::setEncoding(const QCString &templateName,int line,const QCString &enc)
2385 {
2386   if (enc==m_encoding) return; // nothing changed
2387   if (m_fromUtf8!=(void *)(-1))
2388   {
2389     portable_iconv_close(m_fromUtf8);
2390     m_fromUtf8 = (void*)(-1);
2391   }
2392   m_encoding=enc;
2393   if (!enc.isEmpty())
2394   {
2395     m_fromUtf8 = portable_iconv_open(enc,"UTF-8");
2396     if (m_fromUtf8==(void*)(-1))
2397     {
2398       warn(templateName,line,"unsupported character conversion: '%s'->'UTF-8'\n", enc.data());
2399     }
2400   }
2401   //printf("TemplateContextImpl::setEncoding(%s)\n",enc.data());
2402 }
2403
2404 QCString TemplateContextImpl::recode(const QCString &s)
2405 {
2406   //printf("TemplateContextImpl::recode(%s)\n",s.data());
2407   int iSize        = s.length();
2408   int oSize        = iSize*4+1;
2409   QCString output(oSize);
2410   size_t iLeft     = iSize;
2411   size_t oLeft     = oSize;
2412   char *iPtr       = s.rawData();
2413   char *oPtr       = output.rawData();
2414   if (!portable_iconv(m_fromUtf8,&iPtr,&iLeft,&oPtr,&oLeft))
2415   {
2416     oSize -= (int)oLeft;
2417     output.resize(oSize+1);
2418     output.at(oSize)='\0';
2419     return output;
2420   }
2421   else
2422   {
2423     return s;
2424   }
2425 }
2426
2427 void TemplateContextImpl::set(const char *name,const TemplateVariant &v)
2428 {
2429   TemplateVariant *pv = m_contextStack.getFirst()->find(name);
2430   if (pv)
2431   {
2432     m_contextStack.getFirst()->remove(name);
2433   }
2434   m_contextStack.getFirst()->insert(name,new TemplateVariant(v));
2435 }
2436
2437 TemplateVariant TemplateContextImpl::get(const QCString &name) const
2438 {
2439   int i=name.find('.');
2440   if (i==-1) // simple name
2441   {
2442     return getPrimary(name);
2443   }
2444   else // obj.prop
2445   {
2446     QCString objName = name.left(i);
2447     TemplateVariant v = getPrimary(objName);
2448     QCString propName = name.mid(i+1);
2449     while (!propName.isEmpty())
2450     {
2451       //printf("getPrimary(%s) type=%d:%s\n",objName.data(),v.type(),v.toString().data());
2452       if (v.type()==TemplateVariant::Struct)
2453       {
2454         i = propName.find(".");
2455         int l = i==-1 ? propName.length() : i;
2456         v = v.toStruct()->get(propName.left(l));
2457         if (!v.isValid())
2458         {
2459           warn(m_templateName,m_line,"requesting non-existing property '%s' for object '%s'",propName.left(l).data(),objName.data());
2460         }
2461         if (i!=-1)
2462         {
2463           objName = propName.left(i);
2464           propName = propName.mid(i+1);
2465         }
2466         else
2467         {
2468           propName.resize(0);
2469         }
2470       }
2471       else if (v.type()==TemplateVariant::List)
2472       {
2473         i = propName.find(".");
2474         int l = i==-1 ? propName.length() : i;
2475         bool b;
2476         int index = propName.left(l).toInt(&b);
2477         if (b)
2478         {
2479           v = v.toList()->at(index);
2480         }
2481         else
2482         {
2483           warn(m_templateName,m_line,"list index '%s' is not valid",propName.data());
2484           break;
2485         }
2486         if (i!=-1)
2487         {
2488           propName = propName.mid(i+1);
2489         }
2490         else
2491         {
2492           propName.resize(0);
2493         }
2494       }
2495       else
2496       {
2497         warn(m_templateName,m_line,"using . on an object '%s' is not an struct or list",objName.data());
2498         return TemplateVariant();
2499       }
2500     }
2501     return v;
2502   }
2503 }
2504
2505 const TemplateVariant *TemplateContextImpl::getRef(const QCString &name) const
2506 {
2507   QListIterator< QDict<TemplateVariant> > it(m_contextStack);
2508   QDict<TemplateVariant> *dict;
2509   for (it.toFirst();(dict=it.current());++it)
2510   {
2511     TemplateVariant *v = dict->find(name);
2512     if (v) return v;
2513   }
2514   return 0; // not found
2515 }
2516
2517 TemplateVariant TemplateContextImpl::getPrimary(const QCString &name) const
2518 {
2519   const TemplateVariant *v = getRef(name);
2520   return v ? *v : TemplateVariant();
2521 }
2522
2523 void TemplateContextImpl::push()
2524 {
2525   QDict<TemplateVariant> *dict = new QDict<TemplateVariant>;
2526   dict->setAutoDelete(TRUE);
2527   m_contextStack.prepend(dict);
2528 }
2529
2530 void TemplateContextImpl::pop()
2531 {
2532   if (!m_contextStack.removeFirst())
2533   {
2534     warn(m_templateName,m_line,"pop() called on empty context stack!\n");
2535   }
2536 }
2537
2538 TemplateBlockContext *TemplateContextImpl::blockContext()
2539 {
2540   return &m_blockContext;
2541 }
2542
2543 void TemplateContextImpl::warn(const char *fileName,int line,const char *fmt,...) const
2544 {
2545   va_list args;
2546   va_start(args,fmt);
2547   va_warn(fileName,line,fmt,args);
2548   va_end(args);
2549   m_engine->printIncludeContext(fileName,line);
2550 }
2551
2552 void TemplateContextImpl::openSubIndex(const QCString &indexName)
2553 {
2554   //printf("TemplateContextImpl::openSubIndex(%s)\n",indexName.data());
2555   QStack<TemplateVariant> *stack = m_indexStacks.find(indexName);
2556   if (!stack || stack->isEmpty() || stack->top()->type()==TemplateVariant::List) // error: no stack yet or no entry
2557   {
2558     warn(m_templateName,m_line,"opensubindex for index %s without preceding indexentry",indexName.data());
2559     return;
2560   }
2561   // get the parent entry to add the list to
2562   TemplateStruct *entry = dynamic_cast<TemplateStruct*>(stack->top()->toStruct());
2563   if (entry)
2564   {
2565     // add new list to the stack
2566     TemplateList *list = TemplateList::alloc();
2567     stack->push(new TemplateVariant(list));
2568     entry->set("children",list);
2569     entry->set("is_leaf_node",false);
2570   }
2571 }
2572
2573 void TemplateContextImpl::closeSubIndex(const QCString &indexName)
2574 {
2575   //printf("TemplateContextImpl::closeSubIndex(%s)\n",indexName.data());
2576   QStack<TemplateVariant> *stack = m_indexStacks.find(indexName);
2577   if (!stack || stack->count()<3)
2578   {
2579     warn(m_templateName,m_line,"closesubindex for index %s without matching open",indexName.data());
2580   }
2581   else // stack->count()>=2
2582   {
2583     if (stack->top()->type()==TemplateVariant::Struct)
2584     {
2585       delete stack->pop(); // pop struct
2586       delete stack->pop(); // pop list
2587     }
2588     else // empty list! correct "is_left_node" attribute of the parent entry
2589     {
2590       delete stack->pop(); // pop list
2591       TemplateStruct *entry = dynamic_cast<TemplateStruct*>(stack->top()->toStruct());
2592       if (entry)
2593       {
2594         entry->set("is_leaf_node",true);
2595       }
2596     }
2597   }
2598   //fprintf(stderr,"TemplateContextImpl::closeSubIndex(%s) end g_count=%d\n\n",indexName.data(),g_count);
2599 }
2600
2601 static void getPathListFunc(TemplateStructIntf *entry,TemplateList *list)
2602 {
2603   TemplateVariant parent = entry->get("parent");
2604   if (parent.type()==TemplateVariant::Struct)
2605   {
2606     getPathListFunc(parent.toStruct(),list);
2607   }
2608   list->append(entry);
2609 }
2610
2611 static TemplateVariant getPathFunc(const void *ctx, const QValueList<TemplateVariant> &)
2612 {
2613   TemplateStruct *entry = (TemplateStruct*)ctx;
2614   TemplateList *result = TemplateList::alloc();
2615   getPathListFunc(entry,result);
2616   return result;
2617 }
2618
2619 void TemplateContextImpl::addIndexEntry(const QCString &indexName,const QValueList<TemplateKeyValue> &arguments)
2620 {
2621   QValueListConstIterator<TemplateKeyValue> it = arguments.begin();
2622   //printf("TemplateContextImpl::addIndexEntry(%s)\n",indexName.data());
2623   //while (it!=arguments.end())
2624   //{
2625   //  printf("  key=%s value=%s\n",(*it).key.data(),(*it).value.toString().data());
2626   //  ++it;
2627   //}
2628   TemplateVariant parent(FALSE);
2629   QStack<TemplateVariant> *stack = m_indexStacks.find(indexName);
2630   if (!stack) // no stack yet, create it!
2631   {
2632     stack = new QStack<TemplateVariant>;
2633     stack->setAutoDelete(TRUE);
2634     m_indexStacks.insert(indexName,stack);
2635   }
2636   TemplateList *list  = 0;
2637   if (stack->isEmpty()) // first item, create empty list and add it to the index
2638   {
2639     list = TemplateList::alloc();
2640     stack->push(new TemplateVariant(list));
2641     m_indices->set(indexName,list); // make list available under index
2642   }
2643   else // stack not empty
2644   {
2645     if (stack->top()->type()==TemplateVariant::Struct) // already an entry in the list
2646     {
2647       // remove current entry from the stack
2648       delete stack->pop();
2649     }
2650     else // first entry after opensubindex
2651     {
2652       ASSERT(stack->top()->type()==TemplateVariant::List);
2653     }
2654     if (stack->count()>1)
2655     {
2656       TemplateVariant *tmp = stack->pop();
2657       // To prevent a cyclic dependency between parent and child which causes a memory
2658       // leak, we wrap the parent into a weak reference version.
2659       parent = new TemplateStructWeakRef(stack->top()->toStruct());
2660       stack->push(tmp);
2661       ASSERT(parent.type()==TemplateVariant::Struct);
2662     }
2663     // get list to add new item
2664     list = dynamic_cast<TemplateList*>(stack->top()->toList());
2665   }
2666   TemplateStruct *entry = TemplateStruct::alloc();
2667   // add user specified fields to the entry
2668   for (it=arguments.begin();it!=arguments.end();++it)
2669   {
2670     entry->set((*it).key,(*it).value);
2671   }
2672   if (list->count()>0)
2673   {
2674     TemplateStruct *lastEntry = dynamic_cast<TemplateStruct*>(list->at(list->count()-1).toStruct());
2675     lastEntry->set("last",false);
2676   }
2677   entry->set("is_leaf_node",true);
2678   entry->set("first",list->count()==0);
2679   entry->set("index",list->count());
2680   entry->set("parent",parent);
2681   entry->set("path",TemplateVariant::Delegate::fromFunction(entry,getPathFunc));
2682   entry->set("last",true);
2683   stack->push(new TemplateVariant(entry));
2684   list->append(entry);
2685 }
2686
2687 //----------------------------------------------------------
2688
2689 /** @brief Class representing a piece of plain text in a template */
2690 class TemplateNodeText : public TemplateNode
2691 {
2692   public:
2693     TemplateNodeText(TemplateParser *,TemplateNode *parent,int,const QCString &data)
2694       : TemplateNode(parent), m_data(data)
2695     {
2696       TRACE(("TemplateNodeText('%s')\n",replace(data,'\n',' ').data()));
2697     }
2698
2699     void render(FTextStream &ts, TemplateContext *c)
2700     {
2701       TemplateContextImpl* ci = dynamic_cast<TemplateContextImpl*>(c);
2702       if (ci==0) return; // should not happen
2703       //printf("TemplateNodeText::render(%s) needsRecoding=%d ci=%p\n",m_data.data(),ci->needsRecoding(),ci);
2704       if (ci->spacelessEnabled())
2705       {
2706         if (ci->needsRecoding())
2707         {
2708           ts << ci->recode(ci->spacelessIntf()->remove(m_data));
2709         }
2710         else
2711         {
2712           ts << ci->spacelessIntf()->remove(m_data);
2713         }
2714       }
2715       else
2716       {
2717         if (ci->needsRecoding())
2718         {
2719           ts << ci->recode(m_data);
2720         }
2721         else
2722         {
2723           ts << m_data;
2724         }
2725       }
2726     }
2727   private:
2728     QCString m_data;
2729 };
2730
2731 //----------------------------------------------------------
2732
2733 /** @brief Class representing a variable in a template */
2734 class TemplateNodeVariable : public TemplateNode
2735 {
2736   public:
2737     TemplateNodeVariable(TemplateParser *parser,TemplateNode *parent,int line,const QCString &var)
2738       : TemplateNode(parent), m_templateName(parser->templateName()), m_line(line)
2739     {
2740       TRACE(("TemplateNodeVariable(%s)\n",var.data()));
2741       ExpressionParser expParser(parser,line);
2742       m_var = expParser.parse(var);
2743       if (m_var==0)
2744       {
2745         parser->warn(m_templateName,line,"invalid expression '%s' for variable",var.data());
2746       }
2747     }
2748     ~TemplateNodeVariable()
2749     {
2750       delete m_var;
2751     }
2752
2753     void render(FTextStream &ts, TemplateContext *c)
2754     {
2755       TemplateContextImpl* ci = dynamic_cast<TemplateContextImpl*>(c);
2756       if (ci==0) return; // should not happen
2757       ci->setLocation(m_templateName,m_line);
2758       if (m_var)
2759       {
2760         TemplateVariant v = m_var->resolve(c);
2761         if (v.type()==TemplateVariant::Function)
2762         {
2763           v = v.call(QValueList<TemplateVariant>());
2764         }
2765         if (ci->escapeIntf() && !v.raw())
2766         {
2767           if (ci->needsRecoding())
2768           {
2769             ts << ci->recode(ci->escapeIntf()->escape(v.toString()));
2770           }
2771           else
2772           {
2773             ts << ci->escapeIntf()->escape(v.toString());
2774           }
2775         }
2776         else
2777         {
2778           if (ci->needsRecoding())
2779           {
2780             ts << ci->recode(v.toString());
2781           }
2782           else
2783           {
2784             ts << v.toString();
2785           }
2786         }
2787       }
2788     }
2789
2790   private:
2791     QCString m_templateName;
2792     int m_line;
2793     ExprAst *m_var;
2794     QList<ExprAst> m_args;
2795 };
2796
2797 //----------------------------------------------------------
2798
2799 /** @brief Helper class for creating template AST tag nodes and returning
2800   * the template for a given node.
2801  */
2802 template<class T> class TemplateNodeCreator : public TemplateNode
2803 {
2804   public:
2805     TemplateNodeCreator(TemplateParser *parser,TemplateNode *parent,int line)
2806       : TemplateNode(parent), m_templateName(parser->templateName()), m_line(line) {}
2807     static TemplateNode *createInstance(TemplateParser *parser,
2808                                         TemplateNode *parent,
2809                                         int line,
2810                                         const QCString &data)
2811     {
2812       return new T(parser,parent,line,data);
2813     }
2814     TemplateImpl *getTemplate()
2815     {
2816       TemplateNode *root = this;
2817       while (root && root->parent())
2818       {
2819         root = root->parent();
2820       }
2821       return dynamic_cast<TemplateImpl*>(root);
2822     }
2823   protected:
2824     void mkpath(TemplateContextImpl *ci,const QCString &fileName)
2825     {
2826       int i=fileName.find('/');
2827       QCString outputDir = ci->outputDirectory();
2828       QDir d(outputDir);
2829       if (!d.exists())
2830       {
2831         QDir rootDir;
2832         rootDir.setPath(QDir::currentDirPath());
2833         if (!rootDir.mkdir(outputDir))
2834         {
2835           err("tag OUTPUT_DIRECTORY: Output directory `%s' does not "
2836               "exist and cannot be created\n",outputDir.data());
2837           return;
2838         }
2839         d.setPath(outputDir);
2840       }
2841       int j=0;
2842       while (i!=-1) // fileName contains path part
2843       {
2844         if (d.exists())
2845         {
2846           bool ok = d.mkdir(fileName.mid(j,i-j));
2847           if (!ok) break;
2848           QCString dirName = outputDir+'/'+fileName.left(i);
2849           d = QDir(dirName);
2850           j = i+1;
2851         }
2852         i=fileName.find('/',i+1);
2853       }
2854     }
2855     QCString m_templateName;
2856     int m_line;
2857 };
2858
2859 //----------------------------------------------------------
2860
2861 /** @brief Class representing an 'if' tag in a template */
2862 class TemplateNodeIf : public TemplateNodeCreator<TemplateNodeIf>
2863 {
2864   public:
2865     TemplateNodeIf(TemplateParser *parser,TemplateNode *parent,int line,const QCString &data) :
2866       TemplateNodeCreator<TemplateNodeIf>(parser,parent,line)
2867     {
2868       m_ifGuardedNodes.setAutoDelete(TRUE);
2869       TRACE(("{TemplateNodeIf(%s)\n",data.data()));
2870       if (data.isEmpty())
2871       {
2872         parser->warn(m_templateName,line,"missing argument for if tag");
2873       }
2874       QStrList stopAt;
2875       stopAt.append("endif");
2876       stopAt.append("elif");
2877       stopAt.append("else");
2878
2879       // if 'nodes'
2880       GuardedNodes *guardedNodes = new GuardedNodes;
2881       ExpressionParser ex(parser,line);
2882       guardedNodes->line = line;
2883       guardedNodes->guardAst = ex.parse(data);
2884       parser->parse(this,line,stopAt,guardedNodes->trueNodes);
2885       m_ifGuardedNodes.append(guardedNodes);
2886       TemplateToken *tok = parser->takeNextToken();
2887
2888       // elif 'nodes'
2889       while (tok && tok->data.left(5)=="elif ")
2890       {
2891         ExpressionParser ex(parser,line);
2892         guardedNodes = new GuardedNodes;
2893         guardedNodes->line = tok->line;
2894         guardedNodes->guardAst = ex.parse(tok->data.mid(5));
2895         parser->parse(this,tok->line,stopAt,guardedNodes->trueNodes);
2896         m_ifGuardedNodes.append(guardedNodes);
2897         // proceed to the next token
2898         delete tok;
2899         tok = parser->takeNextToken();
2900       }
2901
2902       // else 'nodes'
2903       if (tok && tok->data=="else")
2904       {
2905         stopAt.removeLast(); // remove "else"
2906         stopAt.removeLast(); // remove "elif"
2907         parser->parse(this,line,stopAt,m_falseNodes);
2908         parser->removeNextToken(); // skip over endif
2909       }
2910       delete tok;
2911       TRACE(("}TemplateNodeIf(%s)\n",data.data()));
2912     }
2913     ~TemplateNodeIf()
2914     {
2915     }
2916
2917     void render(FTextStream &ts, TemplateContext *c)
2918     {
2919       TemplateContextImpl* ci = dynamic_cast<TemplateContextImpl*>(c);
2920       if (ci==0) return; // should not happen
2921       ci->setLocation(m_templateName,m_line);
2922       //printf("TemplateNodeIf::render #trueNodes=%d #falseNodes=%d\n",m_trueNodes.count(),m_falseNodes.count());
2923       bool processed=FALSE;
2924       QListIterator<GuardedNodes> li(m_ifGuardedNodes);
2925       GuardedNodes *nodes;
2926       for (li.toFirst();(nodes=li.current()) && !processed;++li)
2927       {
2928         if (nodes->guardAst)
2929         {
2930           TemplateVariant guardValue = nodes->guardAst->resolve(c);
2931           if (guardValue.toBool()) // render nodes for the first guard that evaluated to 'true'
2932           {
2933             nodes->trueNodes.render(ts,c);
2934             processed=TRUE;
2935           }
2936         }
2937         else
2938         {
2939           ci->warn(m_templateName,nodes->line,"invalid expression for if/elif");
2940         }
2941       }
2942       if (!processed)
2943       {
2944         // all guards are false, render 'else' nodes
2945         m_falseNodes.render(ts,c);
2946       }
2947     }
2948   private:
2949     struct GuardedNodes
2950     {
2951       GuardedNodes() : guardAst(0) {}
2952      ~GuardedNodes() { delete guardAst; }
2953       int line;
2954       ExprAst *guardAst;
2955       TemplateNodeList trueNodes;
2956     };
2957     QList<GuardedNodes> m_ifGuardedNodes;
2958     TemplateNodeList m_falseNodes;
2959 };
2960
2961 //----------------------------------------------------------
2962 /** @brief Class representing a 'for' tag in a template */
2963 class TemplateNodeRepeat : public TemplateNodeCreator<TemplateNodeRepeat>
2964 {
2965   public:
2966     TemplateNodeRepeat(TemplateParser *parser,TemplateNode *parent,int line,const QCString &data)
2967       : TemplateNodeCreator<TemplateNodeRepeat>(parser,parent,line)
2968     {
2969       TRACE(("{TemplateNodeRepeat(%s)\n",data.data()));
2970       ExpressionParser expParser(parser,line);
2971       m_expr = expParser.parse(data);
2972       QStrList stopAt;
2973       stopAt.append("endrepeat");
2974       parser->parse(this,line,stopAt,m_repeatNodes);
2975       parser->removeNextToken(); // skip over endrepeat
2976       TRACE(("}TemplateNodeRepeat(%s)\n",data.data()));
2977     }
2978     ~TemplateNodeRepeat()
2979     {
2980       delete m_expr;
2981     }
2982     void render(FTextStream &ts, TemplateContext *c)
2983     {
2984       TemplateContextImpl* ci = dynamic_cast<TemplateContextImpl*>(c);
2985       if (ci==0) return; // should not happen
2986       ci->setLocation(m_templateName,m_line);
2987       TemplateVariant v;
2988       if (m_expr && (v=m_expr->resolve(c)).type()==TemplateVariant::Integer)
2989       {
2990         int i, n = v.toInt();
2991         for (i=0;i<n;i++)
2992         {
2993           TemplateAutoRef<TemplateStruct> s(TemplateStruct::alloc());
2994           s->set("counter0",    (int)i);
2995           s->set("counter",     (int)(i+1));
2996           s->set("revcounter",  (int)(n-i));
2997           s->set("revcounter0", (int)(n-i-1));
2998           s->set("first",i==0);
2999           s->set("last", i==n-1);
3000           c->set("repeatloop",s.get());
3001           // render all items for this iteration of the loop
3002           m_repeatNodes.render(ts,c);
3003         }
3004       }
3005       else // simple type...
3006       {
3007         ci->warn(m_templateName,m_line,"for requires a variable of list type!");
3008       }
3009     }
3010   private:
3011     TemplateNodeList m_repeatNodes;
3012     ExprAst *m_expr;
3013 };
3014
3015 //----------------------------------------------------------
3016
3017 /** @brief Class representing a 'range' tag in a template */
3018 class TemplateNodeRange : public TemplateNodeCreator<TemplateNodeRange>
3019 {
3020   public:
3021     TemplateNodeRange(TemplateParser *parser,TemplateNode *parent,int line,const QCString &data)
3022       : TemplateNodeCreator<TemplateNodeRange>(parser,parent,line), m_down(FALSE)
3023     {
3024       TRACE(("{TemplateNodeRange(%s)\n",data.data()));
3025       QCString start,end;
3026       int i1 = data.find(" from ");
3027       int i2 = data.find(" to ");
3028       int i3 = data.find(" downto ");
3029       if (i1==-1)
3030       {
3031         if (data.right(5)==" from")
3032         {
3033           parser->warn(m_templateName,line,"range missing after 'from' keyword");
3034         }
3035         else if (data=="from")
3036         {
3037           parser->warn(m_templateName,line,"range needs an iterator variable and a range");
3038         }
3039         else
3040         {
3041           parser->warn(m_templateName,line,"range is missing 'from' keyword");
3042         }
3043       }
3044       else if (i2==-1 && i3==-1)
3045       {
3046         if (data.right(3)==" to")
3047         {
3048           parser->warn(m_templateName,line,"range is missing end value after 'to' keyword");
3049         }
3050         else if (data.right(7)==" downto")
3051         {
3052           parser->warn(m_templateName,line,"range is missing end value after 'downto' keyword");
3053         }
3054         else
3055         {
3056           parser->warn(m_templateName,line,"range is missing 'to' or 'downto' keyword");
3057         }
3058       }
3059       else
3060       {
3061         m_var = data.left(i1).stripWhiteSpace();
3062         if (m_var.isEmpty())
3063         {
3064           parser->warn(m_templateName,line,"range needs an iterator variable");
3065         }
3066         start = data.mid(i1+6,i2-i1-6).stripWhiteSpace();
3067         if (i2!=-1)
3068         {
3069           end = data.right(data.length()-i2-4).stripWhiteSpace();
3070           m_down = FALSE;
3071         }
3072         else if (i3!=-1)
3073         {
3074           end = data.right(data.length()-i3-8).stripWhiteSpace();
3075           m_down = TRUE;
3076         }
3077       }
3078       ExpressionParser expParser(parser,line);
3079       m_startExpr = expParser.parse(start);
3080       m_endExpr   = expParser.parse(end);
3081
3082       QStrList stopAt;
3083       stopAt.append("endrange");
3084       parser->parse(this,line,stopAt,m_loopNodes);
3085       parser->removeNextToken(); // skip over endrange
3086       TRACE(("}TemplateNodeRange(%s)\n",data.data()));
3087     }
3088
3089     ~TemplateNodeRange()
3090     {
3091       delete m_startExpr;
3092       delete m_endExpr;
3093     }
3094
3095     void render(FTextStream &ts, TemplateContext *c)
3096     {
3097       TemplateContextImpl* ci = dynamic_cast<TemplateContextImpl*>(c);
3098       if (ci==0) return; // should not happen
3099       ci->setLocation(m_templateName,m_line);
3100       //printf("TemplateNodeRange::render #loopNodes=%d\n",
3101       //    m_loopNodes.count());
3102       if (m_startExpr && m_endExpr)
3103       {
3104         TemplateVariant vs = m_startExpr->resolve(c);
3105         TemplateVariant ve = m_endExpr->resolve(c);
3106         if (vs.type()==TemplateVariant::Integer && ve.type()==TemplateVariant::Integer)
3107         {
3108           int s = vs.toInt();
3109           int e = ve.toInt();
3110           int l = m_down ? s-e+1 : e-s+1;
3111           if (l>0)
3112           {
3113             c->push();
3114             //int index = m_reversed ? list.count() : 0;
3115             const TemplateVariant *parentLoop = c->getRef("forloop");
3116             uint index = 0;
3117             int i = m_down ? e : s;
3118             bool done=false;
3119             while (!done)
3120             {
3121               // set the forloop meta-data variable
3122               TemplateAutoRef<TemplateStruct> s(TemplateStruct::alloc());
3123               s->set("counter0",    (int)index);
3124               s->set("counter",     (int)(index+1));
3125               s->set("revcounter",  (int)(l-index));
3126               s->set("revcounter0", (int)(l-index-1));
3127               s->set("first",index==0);
3128               s->set("last", (int)index==l-1);
3129               s->set("parentloop",parentLoop ? *parentLoop : TemplateVariant());
3130               c->set("forloop",s.get());
3131
3132               // set the iterator variable
3133               c->set(m_var,i);
3134
3135               // render all items for this iteration of the loop
3136               m_loopNodes.render(ts,c);
3137
3138               index++;
3139               if (m_down)
3140               {
3141                 i--;
3142                 done = i<e;
3143               }
3144               else
3145               {
3146                 i++;
3147                 done = i>e;
3148               }
3149             }
3150             c->pop();
3151           }
3152           else
3153           {
3154             ci->warn(m_templateName,m_line,"range %d %s %d is empty!",
3155                 s,m_down?"downto":"to",e);
3156           }
3157         }
3158         else if (vs.type()!=TemplateVariant::Integer)
3159         {
3160           ci->warn(m_templateName,m_line,"range requires a start value of integer type!");
3161         }
3162         else if (ve.type()!=TemplateVariant::Integer)
3163         {
3164           ci->warn(m_templateName,m_line,"range requires an end value of integer type!");
3165         }
3166       }
3167       else if (!m_startExpr)
3168       {
3169         ci->warn(m_templateName,m_line,"range has empty start value");
3170       }
3171       else if (!m_endExpr)
3172       {
3173         ci->warn(m_templateName,m_line,"range has empty end value");
3174       }
3175     }
3176
3177   private:
3178     bool m_down;
3179     ExprAst *m_startExpr;
3180     ExprAst *m_endExpr;
3181     QCString m_var;
3182     TemplateNodeList m_loopNodes;
3183 };
3184
3185 //----------------------------------------------------------
3186
3187 /** @brief Class representing a 'for' tag in a template */
3188 class TemplateNodeFor : public TemplateNodeCreator<TemplateNodeFor>
3189 {
3190   public:
3191     TemplateNodeFor(TemplateParser *parser,TemplateNode *parent,int line,const QCString &data)
3192       : TemplateNodeCreator<TemplateNodeFor>(parser,parent,line), m_reversed(FALSE)
3193     {
3194       TRACE(("{TemplateNodeFor(%s)\n",data.data()));
3195       QCString exprStr;
3196       int i = data.find(" in ");
3197       if (i==-1)
3198       {
3199         if (data.right(3)==" in")
3200         {
3201           parser->warn(m_templateName,line,"for is missing container after 'in' keyword");
3202         }
3203         else if (data=="in")
3204         {
3205           parser->warn(m_templateName,line,"for needs at least one iterator variable");
3206         }
3207         else
3208         {
3209           parser->warn(m_templateName,line,"for is missing 'in' keyword");
3210         }
3211       }
3212       else
3213       {
3214         m_vars = split(data.left(i),",");
3215         if (m_vars.count()==0)
3216         {
3217           parser->warn(m_templateName,line,"for needs at least one iterator variable");
3218         }
3219
3220         int j = data.find(" reversed",i);
3221         m_reversed = (j!=-1);
3222
3223         if (j==-1) j=data.length();
3224         if (j>i+4)
3225         {
3226           exprStr = data.mid(i+4,j-i-4); // skip over " in " part
3227         }
3228         if (exprStr.isEmpty())
3229         {
3230           parser->warn(m_templateName,line,"for is missing container after 'in' keyword");
3231         }
3232       }
3233       ExpressionParser expParser(parser,line);
3234       m_expr = expParser.parse(exprStr);
3235
3236       QStrList stopAt;
3237       stopAt.append("endfor");
3238       stopAt.append("empty");
3239       parser->parse(this,line,stopAt,m_loopNodes);
3240       TemplateToken *tok = parser->takeNextToken();
3241       if (tok && tok->data=="empty")
3242       {
3243         stopAt.removeLast();
3244         parser->parse(this,line,stopAt,m_emptyNodes);
3245         parser->removeNextToken(); // skip over endfor
3246       }
3247       delete tok;
3248       TRACE(("}TemplateNodeFor(%s)\n",data.data()));
3249     }
3250
3251     ~TemplateNodeFor()
3252     {
3253       delete m_expr;
3254     }
3255
3256     void render(FTextStream &ts, TemplateContext *c)
3257     {
3258       TemplateContextImpl* ci = dynamic_cast<TemplateContextImpl*>(c);
3259       if (ci==0) return; // should not happen
3260       ci->setLocation(m_templateName,m_line);
3261       //printf("TemplateNodeFor::render #loopNodes=%d #emptyNodes=%d\n",
3262       //    m_loopNodes.count(),m_emptyNodes.count());
3263       if (m_expr)
3264       {
3265         TemplateVariant v = m_expr->resolve(c);
3266         if (v.type()==TemplateVariant::Function)
3267         {
3268           v = v.call(QValueList<TemplateVariant>());
3269         }
3270         const TemplateListIntf *list = v.toList();
3271         if (list)
3272         {
3273           uint listSize = list->count();
3274           if (listSize==0) // empty for loop
3275           {
3276             m_emptyNodes.render(ts,c);
3277             return;
3278           }
3279           c->push();
3280           //int index = m_reversed ? list.count() : 0;
3281           TemplateVariant v;
3282           const TemplateVariant *parentLoop = c->getRef("forloop");
3283           uint index = m_reversed ? listSize-1 : 0;
3284           TemplateListIntf::ConstIterator *it = list->createIterator();
3285           for (m_reversed ? it->toLast() : it->toFirst();
3286               (it->current(v));
3287               m_reversed ? it->toPrev() : it->toNext())
3288           {
3289             TemplateAutoRef<TemplateStruct> s(TemplateStruct::alloc());
3290             s->set("counter0",    (int)index);
3291             s->set("counter",     (int)(index+1));
3292             s->set("revcounter",  (int)(listSize-index));
3293             s->set("revcounter0", (int)(listSize-index-1));
3294             s->set("first",index==0);
3295             s->set("last", index==listSize-1);
3296             s->set("parentloop",parentLoop ? *parentLoop : TemplateVariant());
3297             c->set("forloop",s.get());
3298
3299             // add variables for this loop to the context
3300             //obj->addVariableToContext(index,m_vars,c);
3301             uint vi=0;
3302             if (m_vars.count()==1) // loop variable represents an item
3303             {
3304               c->set(m_vars[vi++],v);
3305             }
3306             else if (m_vars.count()>1 && v.type()==TemplateVariant::Struct)
3307               // loop variables represent elements in a list item
3308             {
3309               for (uint i=0;i<m_vars.count();i++,vi++)
3310               {
3311                 c->set(m_vars[vi],v.toStruct()->get(m_vars[vi]));
3312               }
3313             }
3314             for (;vi<m_vars.count();vi++)
3315             {
3316               c->set(m_vars[vi],TemplateVariant());
3317             }
3318
3319             // render all items for this iteration of the loop
3320             m_loopNodes.render(ts,c);
3321
3322             if (m_reversed) index--; else index++;
3323           }
3324           c->pop();
3325           delete it;
3326         }
3327         else // simple type...
3328         {
3329           ci->warn(m_templateName,m_line,"for requires a variable of list type, got type '%s'!",v.typeAsString().data());
3330         }
3331       }
3332     }
3333
3334   private:
3335     bool m_reversed;
3336     ExprAst *m_expr;
3337     QValueList<QCString> m_vars;
3338     TemplateNodeList m_loopNodes;
3339     TemplateNodeList m_emptyNodes;
3340 };
3341
3342 //----------------------------------------------------------
3343
3344 /** @brief Class representing an 'markers' tag in a template */
3345 class TemplateNodeMsg : public TemplateNodeCreator<TemplateNodeMsg>
3346 {
3347   public:
3348     TemplateNodeMsg(TemplateParser *parser,TemplateNode *parent,int line,const QCString &)
3349       : TemplateNodeCreator<TemplateNodeMsg>(parser,parent,line)
3350     {
3351       TRACE(("{TemplateNodeMsg()\n"));
3352       QStrList stopAt;
3353       stopAt.append("endmsg");
3354       parser->parse(this,line,stopAt,m_nodes);
3355       parser->removeNextToken(); // skip over endmsg
3356       TRACE(("}TemplateNodeMsg()\n"));
3357     }
3358     void render(FTextStream &, TemplateContext *c)
3359     {
3360       TemplateContextImpl* ci = dynamic_cast<TemplateContextImpl*>(c);
3361       if (ci==0) return; // should not happen
3362       ci->setLocation(m_templateName,m_line);
3363       TemplateEscapeIntf *escIntf = ci->escapeIntf();
3364       ci->setActiveEscapeIntf(0); // avoid escaping things we send to standard out
3365       bool enable = ci->spacelessEnabled();
3366       ci->enableSpaceless(FALSE);
3367       FTextStream ts(stdout);
3368       m_nodes.render(ts,c);
3369       ts << endl;
3370       ci->setActiveEscapeIntf(escIntf);
3371       ci->enableSpaceless(enable);
3372     }
3373   private:
3374     TemplateNodeList m_nodes;
3375 };
3376
3377
3378 //----------------------------------------------------------
3379
3380 /** @brief Class representing a 'block' tag in a template */
3381 class TemplateNodeBlock : public TemplateNodeCreator<TemplateNodeBlock>
3382 {
3383   public:
3384     TemplateNodeBlock(TemplateParser *parser,TemplateNode *parent,int line,const QCString &data)
3385       : TemplateNodeCreator<TemplateNodeBlock>(parser,parent,line)
3386     {
3387       TRACE(("{TemplateNodeBlock(%s)\n",data.data()));
3388       m_blockName = data;
3389       if (m_blockName.isEmpty())
3390       {
3391         parser->warn(parser->templateName(),line,"block tag without name");
3392       }
3393       QStrList stopAt;
3394       stopAt.append("endblock");
3395       parser->parse(this,line,stopAt,m_nodes);
3396       parser->removeNextToken(); // skip over endblock
3397       TRACE(("}TemplateNodeBlock(%s)\n",data.data()));
3398     }
3399
3400     void render(FTextStream &ts, TemplateContext *c)
3401     {
3402       TemplateContextImpl *ci = dynamic_cast<TemplateContextImpl*>(c);
3403       if (ci==0) return; // should not happen
3404       ci->setLocation(m_templateName,m_line);
3405       TemplateImpl *t = getTemplate();
3406       if (t)
3407       {
3408         // remove block from the context, so block.super can work
3409         TemplateNodeBlock *nb = ci->blockContext()->pop(m_blockName);
3410         if (nb) // block is overruled
3411         {
3412           ci->push();
3413           QGString super;
3414           FTextStream ss(&super);
3415           // get super block of block nb
3416           TemplateNodeBlock *sb = ci->blockContext()->get(m_blockName);
3417           if (sb && sb!=nb && sb!=this) // nb and sb both overrule this block
3418           {
3419             sb->render(ss,c); // render parent of nb to string
3420           }
3421           else if (nb!=this) // only nb overrules this block
3422           {
3423             m_nodes.render(ss,c); // render parent of nb to string
3424           }
3425           // add 'block.super' variable to allow access to parent block content
3426           TemplateAutoRef<TemplateStruct> superBlock(TemplateStruct::alloc());
3427           superBlock->set("super",TemplateVariant(super.data(),TRUE));
3428           ci->set("block",superBlock.get());
3429           // render the overruled block contents
3430           t->engine()->enterBlock(nb->m_templateName,nb->m_blockName,nb->m_line);
3431           nb->m_nodes.render(ts,c);
3432           t->engine()->leaveBlock();
3433           ci->pop();
3434           // re-add block to the context
3435           ci->blockContext()->push(nb);
3436         }
3437         else // block has no overrule
3438         {
3439           t->engine()->enterBlock(m_templateName,m_blockName,m_line);
3440           m_nodes.render(ts,c);
3441           t->engine()->leaveBlock();
3442         }
3443       }
3444     }
3445
3446     QCString name() const
3447     {
3448       return m_blockName;
3449     }
3450
3451   private:
3452     QCString m_blockName;
3453     TemplateNodeList m_nodes;
3454 };
3455
3456 //----------------------------------------------------------
3457
3458 /** @brief Class representing a 'extend' tag in a template */
3459 class TemplateNodeExtend : public TemplateNodeCreator<TemplateNodeExtend>
3460 {
3461   public:
3462     TemplateNodeExtend(TemplateParser *parser,TemplateNode *parent,int line,const QCString &data)
3463       : TemplateNodeCreator<TemplateNodeExtend>(parser,parent,line)
3464     {
3465       TRACE(("{TemplateNodeExtend(%s)\n",data.data()));
3466       ExpressionParser ep(parser,line);
3467       if (data.isEmpty())
3468       {
3469         parser->warn(m_templateName,line,"extend tag is missing template file argument");
3470       }
3471       m_extendExpr = ep.parse(data);
3472       QStrList stopAt;
3473       parser->parse(this,line,stopAt,m_nodes);
3474       TRACE(("}TemplateNodeExtend(%s)\n",data.data()));
3475     }
3476    ~TemplateNodeExtend()
3477     {
3478       delete m_extendExpr;
3479     }
3480
3481     void render(FTextStream &ts, TemplateContext *c)
3482     {
3483       TemplateContextImpl* ci = dynamic_cast<TemplateContextImpl*>(c);
3484       if (ci==0) return; // should not happen
3485       ci->setLocation(m_templateName,m_line);
3486       if (m_extendExpr==0) return;
3487
3488       QCString extendFile = m_extendExpr->resolve(c).toString();
3489       if (extendFile.isEmpty())
3490       {
3491         ci->warn(m_templateName,m_line,"invalid parameter for extend command");
3492       }
3493
3494       // goto root of tree (template node)
3495       TemplateImpl *t = getTemplate();
3496       if (t)
3497       {
3498         Template *bt = t->engine()->loadByName(extendFile,m_line);
3499         TemplateImpl *baseTemplate = bt ? dynamic_cast<TemplateImpl*>(bt) : 0;
3500         if (baseTemplate)
3501         {
3502           // fill block context
3503           TemplateBlockContext *bc = ci->blockContext();
3504
3505           // add overruling blocks to the context
3506           QListIterator<TemplateNode> li(m_nodes);
3507           TemplateNode *n;
3508           for (li.toFirst();(n=li.current());++li)
3509           {
3510             TemplateNodeBlock *nb = dynamic_cast<TemplateNodeBlock*>(n);
3511             if (nb)
3512             {
3513               bc->add(nb);
3514             }
3515             TemplateNodeMsg *msg = dynamic_cast<TemplateNodeMsg*>(n);
3516             if (msg)
3517             {
3518               msg->render(ts,c);
3519             }
3520           }
3521
3522           // render the base template with the given context
3523           baseTemplate->render(ts,c);
3524
3525           // clean up
3526           bc->clear();
3527           t->engine()->unload(t);
3528         }
3529         else
3530         {
3531           ci->warn(m_templateName,m_line,"failed to load template %s for extend",extendFile.data());
3532         }
3533       }
3534     }
3535
3536   private:
3537     ExprAst *m_extendExpr;
3538     TemplateNodeList m_nodes;
3539 };
3540
3541 /** @brief Class representing an 'include' tag in a template */
3542 class TemplateNodeInclude : public TemplateNodeCreator<TemplateNodeInclude>
3543 {
3544   public:
3545     TemplateNodeInclude(TemplateParser *parser,TemplateNode *parent,int line,const QCString &data)
3546       : TemplateNodeCreator<TemplateNodeInclude>(parser,parent,line)
3547     {
3548       TRACE(("TemplateNodeInclude(%s)\n",data.data()));
3549       ExpressionParser ep(parser,line);
3550       if (data.isEmpty())
3551       {
3552         parser->warn(m_templateName,line,"include tag is missing template file argument");
3553       }
3554       m_includeExpr = ep.parse(data);
3555     }
3556    ~TemplateNodeInclude()
3557     {
3558       delete m_includeExpr;
3559     }
3560     void render(FTextStream &ts, TemplateContext *c)
3561     {
3562       TemplateContextImpl* ci = dynamic_cast<TemplateContextImpl*>(c);
3563       if (ci==0) return; // should not happen
3564       ci->setLocation(m_templateName,m_line);
3565       if (m_includeExpr)
3566       {
3567         QCString includeFile = m_includeExpr->resolve(c).toString();
3568         if (includeFile.isEmpty())
3569         {
3570           ci->warn(m_templateName,m_line,"invalid parameter for include command\n");
3571         }
3572         else
3573         {
3574           TemplateImpl *t = getTemplate();
3575           if (t)
3576           {
3577             Template *it = t->engine()->loadByName(includeFile,m_line);
3578             TemplateImpl *incTemplate = it ? dynamic_cast<TemplateImpl*>(it) : 0;
3579             if (incTemplate)
3580             {
3581               incTemplate->render(ts,c);
3582               t->engine()->unload(t);
3583             }
3584             else
3585             {
3586               ci->warn(m_templateName,m_line,"failed to load template '%s' for include",includeFile.data()?includeFile.data():"");
3587             }
3588           }
3589         }
3590       }
3591     }
3592
3593   private:
3594     ExprAst *m_includeExpr;
3595 };
3596
3597 //----------------------------------------------------------
3598
3599 static void stripLeadingWhiteSpace(QGString &s)
3600 {
3601   const char *src = s.data();
3602   if (src)
3603   {
3604     char *dst = s.data();
3605     char c;
3606     bool skipSpaces=TRUE;
3607     while ((c=*src++))
3608     {
3609       if (c=='\n') { *dst++=c; skipSpaces=TRUE; }
3610       else if (c==' ' && skipSpaces) {}
3611       else { *dst++=c; skipSpaces=FALSE; }
3612     }
3613     *dst='\0';
3614   }
3615 }
3616
3617 /** @brief Class representing an 'create' tag in a template */
3618 class TemplateNodeCreate : public TemplateNodeCreator<TemplateNodeCreate>
3619 {
3620   public:
3621     TemplateNodeCreate(TemplateParser *parser,TemplateNode *parent,int line,const QCString &data)
3622       : TemplateNodeCreator<TemplateNodeCreate>(parser,parent,line), m_templateExpr(0), m_fileExpr(0)
3623     {
3624       TRACE(("TemplateNodeCreate(%s)\n",data.data()));
3625       ExpressionParser ep(parser,line);
3626       if (data.isEmpty())
3627       {
3628         parser->warn(m_templateName,line,"create tag is missing arguments");
3629       }
3630       int i = data.find(" from ");
3631       if (i==-1)
3632       {
3633         if (data.right(3)==" from")
3634         {
3635           parser->warn(m_templateName,line,"create is missing template name after 'from' keyword");
3636         }
3637         else if (data=="from")
3638         {
3639           parser->warn(m_templateName,line,"create needs a file name and a template name");
3640         }
3641         else
3642         {
3643           parser->warn(m_templateName,line,"create is missing 'from' keyword");
3644         }
3645       }
3646       else
3647       {
3648         ExpressionParser ep(parser,line);
3649         m_fileExpr = ep.parse(data.left(i).stripWhiteSpace());
3650         m_templateExpr = ep.parse(data.mid(i+6).stripWhiteSpace());
3651       }
3652     }
3653    ~TemplateNodeCreate()
3654     {
3655       delete m_templateExpr;
3656       delete m_fileExpr;
3657     }
3658     void render(FTextStream &, TemplateContext *c)
3659     {
3660       TemplateContextImpl* ci = dynamic_cast<TemplateContextImpl*>(c);
3661       if (ci==0) return; // should not happen
3662       ci->setLocation(m_templateName,m_line);
3663       if (m_templateExpr && m_fileExpr)
3664       {
3665         QCString templateFile = m_templateExpr->resolve(c).toString();
3666         QCString outputFile = m_fileExpr->resolve(c).toString();
3667         if (templateFile.isEmpty())
3668         {
3669           ci->warn(m_templateName,m_line,"empty template name parameter for create command\n");
3670         }
3671         else if (outputFile.isEmpty())
3672         {
3673           ci->warn(m_templateName,m_line,"empty file name parameter for create command\n");
3674         }
3675         else
3676         {
3677           TemplateImpl *t = getTemplate();
3678           if (t)
3679           {
3680             QCString extension=outputFile;
3681             int i=extension.findRev('.');
3682             if (i!=-1)
3683             {
3684               extension=extension.right(extension.length()-i-1);
3685             }
3686             t->engine()->setOutputExtension(extension);
3687             Template *ct = t->engine()->loadByName(templateFile,m_line);
3688             TemplateImpl *createTemplate = ct ? dynamic_cast<TemplateImpl*>(ct) : 0;
3689             if (createTemplate)
3690             {
3691               mkpath(ci,outputFile);
3692               if (!ci->outputDirectory().isEmpty())
3693               {
3694                 outputFile.prepend(ci->outputDirectory()+"/");
3695               }
3696               //printf("NoteCreate(%s)\n",outputFile.data());
3697               QFile f(outputFile);
3698               if (f.open(IO_WriteOnly))
3699               {
3700                 TemplateEscapeIntf *escIntf = ci->escapeIntf();
3701                 ci->selectEscapeIntf(extension);
3702                 FTextStream ts(&f);
3703                 QGString out;
3704                 FTextStream os(&out);
3705                 createTemplate->render(os,c);
3706                 stripLeadingWhiteSpace(out);
3707                 ts << out;
3708                 t->engine()->unload(t);
3709                 ci->setActiveEscapeIntf(escIntf);
3710               }
3711               else
3712               {
3713                 ci->warn(m_templateName,m_line,"failed to open output file '%s' for create command",outputFile.data());
3714               }
3715             }
3716             else
3717             {
3718               ci->warn(m_templateName,m_line,"failed to load template '%s' for include",templateFile.data());
3719             }
3720             t->engine()->setOutputExtension("");
3721           }
3722         }
3723       }
3724     }
3725
3726   private:
3727     ExprAst *m_templateExpr;
3728     ExprAst *m_fileExpr;
3729 };
3730
3731 //----------------------------------------------------------
3732
3733 /** @brief Class representing an 'tree' tag in a template */
3734 class TemplateNodeTree : public TemplateNodeCreator<TemplateNodeTree>
3735 {
3736     struct TreeContext
3737     {
3738       TreeContext(TemplateNodeTree *o,const TemplateListIntf *l,TemplateContext *c)
3739         : object(o), list(l), templateCtx(c) {}
3740       TemplateNodeTree *object;
3741       const TemplateListIntf *list;
3742       TemplateContext  *templateCtx;
3743     };
3744   public:
3745     TemplateNodeTree(TemplateParser *parser,TemplateNode *parent,int line,const QCString &data)
3746       : TemplateNodeCreator<TemplateNodeTree>(parser,parent,line)
3747     {
3748       TRACE(("{TemplateNodeTree(%s)\n",data.data()));
3749       ExpressionParser ep(parser,line);
3750       if (data.isEmpty())
3751       {
3752         parser->warn(m_templateName,line,"recursetree tag is missing data argument");
3753       }
3754       m_treeExpr = ep.parse(data);
3755       QStrList stopAt;
3756       stopAt.append("endrecursetree");
3757       parser->parse(this,line,stopAt,m_treeNodes);
3758       parser->removeNextToken(); // skip over endrecursetree
3759       TRACE(("}TemplateNodeTree(%s)\n",data.data()));
3760     }
3761     ~TemplateNodeTree()
3762     {
3763       delete m_treeExpr;
3764     }
3765     static TemplateVariant renderChildrenStub(const void *ctx, const QValueList<TemplateVariant> &)
3766     {
3767       return TemplateVariant(((TreeContext*)ctx)->object->
3768                              renderChildren((const TreeContext*)ctx),TRUE);
3769     }
3770     QCString renderChildren(const TreeContext *ctx)
3771     {
3772       //printf("TemplateNodeTree::renderChildren(%d)\n",ctx->list->count());
3773       // render all children of node to a string and return it
3774       TemplateContext *c = ctx->templateCtx;
3775       TemplateContextImpl* ci = dynamic_cast<TemplateContextImpl*>(c);
3776       if (ci==0) return QCString(); // should not happen
3777       QGString result;
3778       FTextStream ss(&result);
3779       c->push();
3780       TemplateVariant node;
3781       TemplateListIntf::ConstIterator *it = ctx->list->createIterator();
3782       for (it->toFirst();(it->current(node));it->toNext())
3783       {
3784         c->set("node",node);
3785         bool hasChildren=FALSE;
3786         const TemplateStructIntf *ns = node.toStruct();
3787         if (ns) // node is a struct
3788         {
3789           TemplateVariant v = ns->get("children");
3790           if (v.isValid()) // with a field 'children'
3791           {
3792             const TemplateListIntf *list = v.toList();
3793             if (list && list->count()>0) // non-empty list
3794             {
3795               TreeContext childCtx(this,list,ctx->templateCtx);
3796               TemplateVariant children(TemplateVariant::Delegate::fromFunction(&childCtx,renderChildrenStub));
3797               children.setRaw(TRUE);
3798               c->set("children",children);
3799               m_treeNodes.render(ss,c);
3800               hasChildren=TRUE;
3801             }
3802             else if (list==0)
3803             {
3804               ci->warn(m_templateName,m_line,"recursetree: children attribute has type '%s' instead of list\n",v.typeAsString().data());
3805             }
3806           }
3807           //else
3808           //{
3809           //  ci->warn(m_templateName,m_line,"recursetree: children attribute is not valid");
3810           //}
3811         }
3812         if (!hasChildren)
3813         {
3814           c->set("children",TemplateVariant("")); // provide default
3815           m_treeNodes.render(ss,c);
3816         }
3817       }
3818       c->pop();
3819       delete it;
3820       return result.data();
3821     }
3822     void render(FTextStream &ts, TemplateContext *c)
3823     {
3824       //printf("TemplateNodeTree::render()\n");
3825       TemplateContextImpl* ci = dynamic_cast<TemplateContextImpl*>(c);
3826       if (ci==0) return; // should not happen
3827       ci->setLocation(m_templateName,m_line);
3828       TemplateVariant v = m_treeExpr->resolve(c);
3829       const TemplateListIntf *list = v.toList();
3830       if (list)
3831       {
3832         TreeContext ctx(this,list,c);
3833         ts << renderChildren(&ctx);
3834       }
3835       else
3836       {
3837         ci->warn(m_templateName,m_line,"recursetree's argument should be a list type");
3838       }
3839     }
3840
3841   private:
3842     ExprAst         *m_treeExpr;
3843     TemplateNodeList m_treeNodes;
3844 };
3845
3846 //----------------------------------------------------------
3847
3848 /** @brief Class representing an 'indexentry' tag in a template */
3849 class TemplateNodeIndexEntry : public TemplateNodeCreator<TemplateNodeIndexEntry>
3850 {
3851     struct Mapping
3852     {
3853       Mapping(const QCString &n,ExprAst *e) : name(n), value(e) {}
3854      ~Mapping() { delete value; }
3855       QCString name;
3856       ExprAst *value;
3857     };
3858   public:
3859     TemplateNodeIndexEntry(TemplateParser *parser,TemplateNode *parent,int line,const QCString &data)
3860       : TemplateNodeCreator<TemplateNodeIndexEntry>(parser,parent,line)
3861     {
3862       TRACE(("{TemplateNodeIndexEntry(%s)\n",data.data()));
3863       m_args.setAutoDelete(TRUE);
3864       ExpressionParser expParser(parser,line);
3865       QValueList<QCString> args = split(data," ");
3866       QValueListIterator<QCString> it = args.begin();
3867       if (it==args.end() || (*it).find('=')!=-1)
3868       {
3869         parser->warn(parser->templateName(),line,"Missing name for indexentry tag");
3870       }
3871       else
3872       {
3873         m_name = *it;
3874         ++it;
3875         while (it!=args.end())
3876         {
3877           QCString arg = *it;
3878           int j=arg.find('=');
3879           if (j>0)
3880           {
3881             ExprAst *expr = expParser.parse(arg.mid(j+1));
3882             if (expr)
3883             {
3884               m_args.append(new Mapping(arg.left(j),expr));
3885             }
3886           }
3887           else
3888           {
3889             parser->warn(parser->templateName(),line,"invalid argument '%s' for indexentry tag",arg.data());
3890           }
3891           ++it;
3892         }
3893       }
3894       TRACE(("}TemplateNodeIndexEntry(%s)\n",data.data()));
3895     }
3896     void render(FTextStream &, TemplateContext *c)
3897     {
3898       TemplateContextImpl *ci = dynamic_cast<TemplateContextImpl*>(c);
3899       if (ci==0) return; // should not happen
3900       if (!m_name.isEmpty())
3901       {
3902         ci->setLocation(m_templateName,m_line);
3903         QListIterator<Mapping> it(m_args);
3904         Mapping *mapping;
3905         QValueList<TemplateKeyValue> list;
3906         for (it.toFirst();(mapping=it.current());++it)
3907         {
3908           list.append(TemplateKeyValue(mapping->name,mapping->value->resolve(c)));
3909         }
3910         ci->addIndexEntry(m_name,list);
3911       }
3912     }
3913   private:
3914     QCString m_name;
3915     QList<Mapping> m_args;
3916 };
3917
3918 //----------------------------------------------------------
3919
3920 /** @brief Class representing an 'opensubindex' tag in a template */
3921 class TemplateNodeOpenSubIndex : public TemplateNodeCreator<TemplateNodeOpenSubIndex>
3922 {
3923   public:
3924     TemplateNodeOpenSubIndex(TemplateParser *parser,TemplateNode *parent,int line,const QCString &data)
3925       : TemplateNodeCreator<TemplateNodeOpenSubIndex>(parser,parent,line)
3926     {
3927       TRACE(("{TemplateNodeOpenSubIndex(%s)\n",data.data()));
3928       m_name = data.stripWhiteSpace();
3929       if (m_name.isEmpty())
3930       {
3931         parser->warn(parser->templateName(),line,"Missing argument for opensubindex tag");
3932       }
3933       else if (m_name.find(' ')!=-1)
3934       {
3935         parser->warn(parser->templateName(),line,"Expected single argument for opensubindex tag got '%s'",data.data());
3936         m_name="";
3937       }
3938       TRACE(("}TemplateNodeOpenSubIndex(%s)\n",data.data()));
3939     }
3940     void render(FTextStream &, TemplateContext *c)
3941     {
3942       TemplateContextImpl *ci = dynamic_cast<TemplateContextImpl*>(c);
3943       if (ci==0) return; // should not happen
3944       if (!m_name.isEmpty())
3945       {
3946         ci->setLocation(m_templateName,m_line);
3947         ci->openSubIndex(m_name);
3948       }
3949     }
3950   private:
3951     QCString m_name;
3952 };
3953
3954 //----------------------------------------------------------
3955
3956 /** @brief Class representing an 'closesubindex' tag in a template */
3957 class TemplateNodeCloseSubIndex : public TemplateNodeCreator<TemplateNodeCloseSubIndex>
3958 {
3959   public:
3960     TemplateNodeCloseSubIndex(TemplateParser *parser,TemplateNode *parent,int line,const QCString &data)
3961       : TemplateNodeCreator<TemplateNodeCloseSubIndex>(parser,parent,line)
3962     {
3963       TRACE(("{TemplateNodeCloseSubIndex(%s)\n",data.data()));
3964       m_name = data.stripWhiteSpace();
3965       if (m_name.isEmpty())
3966       {
3967         parser->warn(parser->templateName(),line,"Missing argument for closesubindex tag");
3968       }
3969       else if (m_name.find(' ')!=-1 || m_name.isEmpty())
3970       {
3971         parser->warn(parser->templateName(),line,"Expected single argument for closesubindex tag got '%s'",data.data());
3972         m_name="";
3973       }
3974       TRACE(("}TemplateNodeCloseSubIndex(%s)\n",data.data()));
3975     }
3976     void render(FTextStream &, TemplateContext *c)
3977     {
3978       TemplateContextImpl *ci = dynamic_cast<TemplateContextImpl*>(c);
3979       if (ci==0) return; // should not happen
3980       if (!m_name.isEmpty())
3981       {
3982         ci->setLocation(m_templateName,m_line);
3983         ci->closeSubIndex(m_name);
3984       }
3985     }
3986   private:
3987     QCString m_name;
3988 };
3989
3990
3991 //----------------------------------------------------------
3992
3993 /** @brief Class representing an 'with' tag in a template */
3994 class TemplateNodeWith : public TemplateNodeCreator<TemplateNodeWith>
3995 {
3996     struct Mapping
3997     {
3998       Mapping(const QCString &n,ExprAst *e) : name(n), value(e) {}
3999      ~Mapping() { delete value; }
4000       QCString name;
4001       ExprAst *value;
4002     };
4003   public:
4004     TemplateNodeWith(TemplateParser *parser,TemplateNode *parent,int line,const QCString &data)
4005       : TemplateNodeCreator<TemplateNodeWith>(parser,parent,line)
4006     {
4007       TRACE(("{TemplateNodeWith(%s)\n",data.data()));
4008       m_args.setAutoDelete(TRUE);
4009       ExpressionParser expParser(parser,line);
4010       QCString filteredData = removeSpacesAroundEquals(data);
4011       QValueList<QCString> args = split(filteredData," ");
4012       QValueListIterator<QCString> it = args.begin();
4013       while (it!=args.end())
4014       {
4015         QCString arg = *it;
4016         int j=arg.find('=');
4017         if (j>0)
4018         {
4019           ExprAst *expr = expParser.parse(arg.mid(j+1));
4020           if (expr)
4021           {
4022             m_args.append(new Mapping(arg.left(j),expr));
4023           }
4024         }
4025         else
4026         {
4027           parser->warn(parser->templateName(),line,"invalid argument '%s' for 'with' tag",arg.data());
4028         }
4029         ++it;
4030       }
4031       QStrList stopAt;
4032       stopAt.append("endwith");
4033       parser->parse(this,line,stopAt,m_nodes);
4034       parser->removeNextToken(); // skip over endwith
4035       TRACE(("}TemplateNodeWith(%s)\n",data.data()));
4036     }
4037     ~TemplateNodeWith()
4038     {
4039     }
4040     void render(FTextStream &ts, TemplateContext *c)
4041     {
4042       TemplateContextImpl *ci = dynamic_cast<TemplateContextImpl*>(c);
4043       if (ci==0) return; // should not happen
4044       ci->setLocation(m_templateName,m_line);
4045       c->push();
4046       QListIterator<Mapping> it(m_args);
4047       Mapping *mapping;
4048       for (it.toFirst();(mapping=it.current());++it)
4049       {
4050         TemplateVariant value = mapping->value->resolve(c);
4051         ci->set(mapping->name,value);
4052       }
4053       m_nodes.render(ts,c);
4054       c->pop();
4055     }
4056   private:
4057     TemplateNodeList m_nodes;
4058     QList<Mapping> m_args;
4059 };
4060
4061 //----------------------------------------------------------
4062
4063 /** @brief Class representing an 'cycle' tag in a template */
4064 class TemplateNodeCycle : public TemplateNodeCreator<TemplateNodeCycle>
4065 {
4066   public:
4067     TemplateNodeCycle(TemplateParser *parser,TemplateNode *parent,int line,const QCString &data)
4068       : TemplateNodeCreator<TemplateNodeCycle>(parser,parent,line)
4069     {
4070       TRACE(("{TemplateNodeCycle(%s)\n",data.data()));
4071       m_args.setAutoDelete(TRUE);
4072       m_index=0;
4073       ExpressionParser expParser(parser,line);
4074       QValueList<QCString> args = split(data," ");
4075       QValueListIterator<QCString> it = args.begin();
4076       while (it!=args.end())
4077       {
4078         ExprAst *expr = expParser.parse(*it);
4079         if (expr)
4080         {
4081           m_args.append(expr);
4082         }
4083         ++it;
4084       }
4085       if (m_args.count()<2)
4086       {
4087           parser->warn(parser->templateName(),line,"expected at least two arguments for cycle command, got %d",m_args.count());
4088       }
4089       TRACE(("}TemplateNodeCycle(%s)\n",data.data()));
4090     }
4091     void render(FTextStream &ts, TemplateContext *c)
4092     {
4093       TemplateContextImpl *ci = dynamic_cast<TemplateContextImpl*>(c);
4094       ci->setLocation(m_templateName,m_line);
4095       if (m_index<m_args.count())
4096       {
4097         TemplateVariant v = m_args.at(m_index)->resolve(c);
4098         if (v.type()==TemplateVariant::Function)
4099         {
4100           v = v.call(QValueList<TemplateVariant>());
4101         }
4102         if (ci->escapeIntf() && !v.raw())
4103         {
4104           if (ci->needsRecoding())
4105           {
4106             ts << ci->recode(ci->escapeIntf()->escape(v.toString()));
4107           }
4108           else
4109           {
4110             ts << ci->escapeIntf()->escape(v.toString());
4111           }
4112         }
4113         else
4114         {
4115           if (ci->needsRecoding())
4116           {
4117             ts << ci->recode(v.toString());
4118           }
4119           else
4120           {
4121             ts << v.toString();
4122           }
4123         }
4124       }
4125       if (++m_index==m_args.count()) // wrap around
4126       {
4127         m_index=0;
4128       }
4129     }
4130   private:
4131     uint m_index;
4132     QList<ExprAst> m_args;
4133 };
4134
4135 //----------------------------------------------------------
4136
4137 /** @brief Class representing an 'set' tag in a template */
4138 class TemplateNodeSet : public TemplateNodeCreator<TemplateNodeSet>
4139 {
4140     struct Mapping
4141     {
4142       Mapping(const QCString &n,ExprAst *e) : name(n), value(e) {}
4143      ~Mapping() { delete value; }
4144       QCString name;
4145       ExprAst *value;
4146     };
4147   public:
4148     TemplateNodeSet(TemplateParser *parser,TemplateNode *parent,int line,const QCString &data)
4149       : TemplateNodeCreator<TemplateNodeSet>(parser,parent,line), m_mapping(0)
4150     {
4151       TRACE(("{TemplateNodeSet(%s)\n",data.data()));
4152       ExpressionParser expParser(parser,line);
4153       // data format: name=expression
4154       int j=data.find('=');
4155       ExprAst *expr = 0;
4156       if (j>0 && (expr = expParser.parse(data.mid(j+1))))
4157       {
4158         m_mapping = new Mapping(data.left(j),expr);
4159       }
4160       TRACE(("}TemplateNodeSet(%s)\n",data.data()));
4161     }
4162     ~TemplateNodeSet()
4163     {
4164       delete m_mapping;
4165     }
4166     void render(FTextStream &, TemplateContext *c)
4167     {
4168       TemplateContextImpl *ci = dynamic_cast<TemplateContextImpl*>(c);
4169       if (ci==0) return; // should not happen
4170       ci->setLocation(m_templateName,m_line);
4171       if (m_mapping)
4172       {
4173         TemplateVariant value = m_mapping->value->resolve(c);
4174         ci->set(m_mapping->name,value);
4175       }
4176     }
4177   private:
4178     Mapping *m_mapping;
4179 };
4180
4181 //----------------------------------------------------------
4182
4183 /** @brief Class representing an 'spaceless' tag in a template */
4184 class TemplateNodeSpaceless : public TemplateNodeCreator<TemplateNodeSpaceless>
4185 {
4186   public:
4187     TemplateNodeSpaceless(TemplateParser *parser,TemplateNode *parent,int line,const QCString &)
4188       : TemplateNodeCreator<TemplateNodeSpaceless>(parser,parent,line)
4189     {
4190       TRACE(("{TemplateNodeSpaceless()\n"));
4191       QStrList stopAt;
4192       stopAt.append("endspaceless");
4193       parser->parse(this,line,stopAt,m_nodes);
4194       parser->removeNextToken(); // skip over endwith
4195       TRACE(("}TemplateNodeSpaceless()\n"));
4196     }
4197     void render(FTextStream &ts, TemplateContext *c)
4198     {
4199       TemplateContextImpl *ci = dynamic_cast<TemplateContextImpl*>(c);
4200       if (ci==0) return; // should not happen
4201       ci->setLocation(m_templateName,m_line);
4202       bool wasSpaceless = ci->spacelessEnabled();
4203       ci->enableSpaceless(TRUE);
4204       m_nodes.render(ts,c);
4205       ci->enableSpaceless(wasSpaceless);
4206     }
4207   private:
4208     TemplateNodeList m_nodes;
4209 };
4210
4211 //----------------------------------------------------------
4212
4213 /** @brief Class representing an 'markers' tag in a template */
4214 class TemplateNodeMarkers : public TemplateNodeCreator<TemplateNodeMarkers>
4215 {
4216   public:
4217     TemplateNodeMarkers(TemplateParser *parser,TemplateNode *parent,int line,const QCString &data)
4218       : TemplateNodeCreator<TemplateNodeMarkers>(parser,parent,line), m_listExpr(0), m_patternExpr(0)
4219     {
4220       TRACE(("{TemplateNodeMarkers(%s)\n",data.data()));
4221       int i = data.find(" in ");
4222       int w = data.find(" with ");
4223       if (i==-1 || w==-1 || w<i)
4224       {
4225         parser->warn(m_templateName,line,"markers tag as wrong format. Expected: markers <var> in <list> with <string_with_markers>");
4226       }
4227       else
4228       {
4229         ExpressionParser expParser(parser,line);
4230         m_var = data.left(i);
4231         m_listExpr = expParser.parse(data.mid(i+4,w-i-4));
4232         m_patternExpr = expParser.parse(data.right(data.length()-w-6));
4233       }
4234       QStrList stopAt;
4235       stopAt.append("endmarkers");
4236       parser->parse(this,line,stopAt,m_nodes);
4237       parser->removeNextToken(); // skip over endmarkers
4238       TRACE(("}TemplateNodeMarkers(%s)\n",data.data()));
4239     }
4240    ~TemplateNodeMarkers()
4241     {
4242       delete m_listExpr;
4243       delete m_patternExpr;
4244     }
4245     void render(FTextStream &ts, TemplateContext *c)
4246     {
4247       TemplateContextImpl *ci = dynamic_cast<TemplateContextImpl*>(c);
4248       if (ci==0) return; // should not happen
4249       ci->setLocation(m_templateName,m_line);
4250       if (!m_var.isEmpty() && m_listExpr && m_patternExpr)
4251       {
4252         TemplateVariant v = m_listExpr->resolve(c);
4253         const TemplateListIntf *list = v.toList();
4254         TemplateVariant patternStr = m_patternExpr->resolve(c);
4255         if (list)
4256         {
4257           if (patternStr.type()==TemplateVariant::String)
4258           {
4259             TemplateListIntf::ConstIterator *it = list->createIterator();
4260             c->push();
4261             QCString str = patternStr.toString();
4262             QRegExp marker("@[0-9]+"); // pattern for a marker, i.e. @0, @1 ... @12, etc
4263             int index=0,newIndex,matchLen;
4264             while ((newIndex=marker.match(str,index,&matchLen))!=-1)
4265             {
4266               if (ci->needsRecoding())
4267               {
4268                 ts << ci->recode(str.mid(index,newIndex-index)); // write text before marker
4269               }
4270               else
4271               {
4272                 ts << str.mid(index,newIndex-index); // write text before marker
4273               }
4274               bool ok;
4275               uint entryIndex = str.mid(newIndex+1,matchLen-1).toUInt(&ok); // get marker id
4276               TemplateVariant var;
4277               uint i=0;
4278               // search for list element at position id
4279               for (it->toFirst(); (it->current(var)) && i<entryIndex; it->toNext(),i++) {}
4280               if (ok && i==entryIndex) // found element
4281               {
4282                 TemplateAutoRef<TemplateStruct> s(TemplateStruct::alloc());
4283                 s->set("id",(int)i);
4284                 c->set("markers",s.get());
4285                 c->set(m_var,var); // define local variable to hold element of list type
4286                 bool wasSpaceless = ci->spacelessEnabled();
4287                 ci->enableSpaceless(TRUE);
4288                 m_nodes.render(ts,c);
4289                 ci->enableSpaceless(wasSpaceless);
4290               }
4291               else if (!ok)
4292               {
4293                 ci->warn(m_templateName,m_line,"markers pattern string has invalid markers '%s'",str.data());
4294               }
4295               else if (i<entryIndex)
4296               {
4297                 ci->warn(m_templateName,m_line,"markers list does not an element for marker position %d",i);
4298               }
4299               index=newIndex+matchLen; // set index just after marker
4300             }
4301             if (ci->needsRecoding())
4302             {
4303               ts << ci->recode(str.right(str.length()-index)); // write text after last marker
4304             }
4305             else
4306             {
4307               ts << str.right(str.length()-index); // write text after last marker
4308             }
4309             c->pop();
4310             delete it;
4311           }
4312           else
4313           {
4314             ci->warn(m_templateName,m_line,"markers requires a parameter of string type after 'with'!");
4315           }
4316         }
4317         else
4318         {
4319           ci->warn(m_templateName,m_line,"markers requires a parameter of list type after 'in'!");
4320         }
4321       }
4322     }
4323   private:
4324     TemplateNodeList m_nodes;
4325     QCString m_var;
4326     ExprAst *m_listExpr;
4327     ExprAst *m_patternExpr;
4328 };
4329
4330 //----------------------------------------------------------
4331
4332 /** @brief Class representing an 'tabbing' tag in a template */
4333 class TemplateNodeTabbing : public TemplateNodeCreator<TemplateNodeTabbing>
4334 {
4335   public:
4336     TemplateNodeTabbing(TemplateParser *parser,TemplateNode *parent,int line,const QCString &)
4337       : TemplateNodeCreator<TemplateNodeTabbing>(parser,parent,line)
4338     {
4339       TRACE(("{TemplateNodeTabbing()\n"));
4340       QStrList stopAt;
4341       stopAt.append("endtabbing");
4342       parser->parse(this,line,stopAt,m_nodes);
4343       parser->removeNextToken(); // skip over endtabbing
4344       TRACE(("}TemplateNodeTabbing()\n"));
4345     }
4346     void render(FTextStream &ts, TemplateContext *c)
4347     {
4348       TemplateContextImpl *ci = dynamic_cast<TemplateContextImpl*>(c);
4349       if (ci==0) return; // should not happen
4350       ci->setLocation(m_templateName,m_line);
4351       bool wasTabbing = ci->tabbingEnabled();
4352       ci->enableTabbing(TRUE);
4353       m_nodes.render(ts,c);
4354       ci->enableTabbing(wasTabbing);
4355     }
4356   private:
4357     TemplateNodeList m_nodes;
4358 };
4359
4360 //----------------------------------------------------------
4361
4362 /** @brief Class representing an 'markers' tag in a template */
4363 class TemplateNodeResource : public TemplateNodeCreator<TemplateNodeResource>
4364 {
4365   public:
4366     TemplateNodeResource(TemplateParser *parser,TemplateNode *parent,int line,const QCString &data)
4367       : TemplateNodeCreator<TemplateNodeResource>(parser,parent,line)
4368     {
4369       TRACE(("{TemplateNodeResource(%s)\n",data.data()));
4370       ExpressionParser ep(parser,line);
4371       int i;
4372       if (data.isEmpty())
4373       {
4374         parser->warn(m_templateName,line,"resource tag is missing resource file argument");
4375         m_resExpr=0;
4376         m_asExpr=0;
4377       }
4378       else if ((i=data.find(" as "))!=-1) // resource a as b
4379       {
4380         m_resExpr = ep.parse(data.left(i));  // part before as
4381         m_asExpr  = ep.parse(data.mid(i+4)); // part after as
4382       }
4383       else // resource a
4384       {
4385         m_resExpr = ep.parse(data);
4386         m_asExpr  = 0;
4387       }
4388       TRACE(("}TemplateNodeResource(%s)\n",data.data()));
4389     }
4390     ~TemplateNodeResource()
4391     {
4392       delete m_resExpr;
4393       delete m_asExpr;
4394     }
4395     void render(FTextStream &, TemplateContext *c)
4396     {
4397       TemplateContextImpl *ci = dynamic_cast<TemplateContextImpl*>(c);
4398       if (ci==0) return; // should not happen
4399       ci->setLocation(m_templateName,m_line);
4400       if (m_resExpr)
4401       {
4402         QCString resourceFile = m_resExpr->resolve(c).toString();
4403         if (resourceFile.isEmpty())
4404         {
4405           ci->warn(m_templateName,m_line,"invalid parameter for resource command\n");
4406         }
4407         else
4408         {
4409           QCString outputDirectory = ci->outputDirectory();
4410           if (m_asExpr)
4411           {
4412             QCString targetFile = m_asExpr->resolve(c).toString();
4413             mkpath(ci,targetFile);
4414             if (targetFile.isEmpty())
4415             { ci->warn(m_templateName,m_line,"invalid parameter at right side of 'as' for resource command\n");
4416             }
4417             else
4418             {
4419               ResourceMgr::instance().copyResourceAs(resourceFile,outputDirectory,targetFile);
4420             }
4421           }
4422           else
4423           {
4424             ResourceMgr::instance().copyResource(resourceFile,outputDirectory);
4425           }
4426         }
4427       }
4428     }
4429   private:
4430     ExprAst *m_resExpr;
4431     ExprAst *m_asExpr;
4432 };
4433
4434 //----------------------------------------------------------
4435
4436 /** @brief Class representing the 'encoding' tag in a template */
4437 class TemplateNodeEncoding : public TemplateNodeCreator<TemplateNodeEncoding>
4438 {
4439   public:
4440     TemplateNodeEncoding(TemplateParser *parser,TemplateNode *parent,int line,const QCString &data)
4441       : TemplateNodeCreator<TemplateNodeEncoding>(parser,parent,line)
4442     {
4443       TRACE(("{TemplateNodeEncoding(%s)\n",data.data()));
4444       ExpressionParser ep(parser,line);
4445       if (data.isEmpty())
4446       {
4447         parser->warn(m_templateName,line,"encoding tag is missing encoding argument");
4448         m_encExpr = 0;
4449       }
4450       else
4451       {
4452         m_encExpr = ep.parse(data);
4453       }
4454       QStrList stopAt;
4455       stopAt.append("endencoding");
4456       parser->parse(this,line,stopAt,m_nodes);
4457       parser->removeNextToken(); // skip over endencoding
4458       TRACE(("}TemplateNodeEncoding(%s)\n",data.data()));
4459     }
4460    ~TemplateNodeEncoding()
4461     {
4462       delete m_encExpr;
4463     }
4464     void render(FTextStream &ts, TemplateContext *c)
4465     {
4466       TemplateContextImpl *ci = dynamic_cast<TemplateContextImpl*>(c);
4467       if (ci==0) return; // should not happen
4468       ci->setLocation(m_templateName,m_line);
4469       QCString encStr;
4470       if (m_encExpr)
4471       {
4472         encStr = m_encExpr->resolve(c).toString();
4473       }
4474       QCString oldEncStr = ci->encoding();
4475       if (!encStr.isEmpty())
4476       {
4477         ci->setEncoding(m_templateName,m_line,encStr);
4478       }
4479       m_nodes.render(ts,c);
4480       ci->setEncoding(m_templateName,m_line,oldEncStr);
4481     }
4482   private:
4483     ExprAst *m_encExpr;
4484     TemplateNodeList m_nodes;
4485 };
4486
4487 //----------------------------------------------------------
4488
4489 /** @brief Factory class for creating tag AST nodes found in a template */
4490 class TemplateNodeFactory
4491 {
4492   public:
4493     typedef TemplateNode *(*CreateFunc)(TemplateParser *parser,
4494                                         TemplateNode *parent,
4495                                         int line,
4496                                         const QCString &data);
4497
4498     static TemplateNodeFactory *instance()
4499     {
4500       static TemplateNodeFactory *instance = 0;
4501       if (instance==0) instance = new TemplateNodeFactory;
4502       return instance;
4503     }
4504
4505     TemplateNode *create(const QCString &name,
4506                          TemplateParser *parser,
4507                          TemplateNode *parent,
4508                          int line,
4509                          const QCString &data)
4510     {
4511       if (m_registry.find(name)==0) return 0;
4512       return ((CreateFunc)m_registry[name])(parser,parent,line,data);
4513     }
4514
4515     void registerTemplateNode(const QCString &name,CreateFunc func)
4516     {
4517       m_registry.insert(name,(void*)func);
4518     }
4519
4520     /** @brief Helper class for registering a template AST node */
4521     template<class T> class AutoRegister
4522     {
4523       public:
4524         AutoRegister<T>(const QCString &key)
4525         {
4526           TemplateNodeFactory::instance()->registerTemplateNode(key,T::createInstance);
4527         }
4528     };
4529
4530   private:
4531     QDict<void> m_registry;
4532 };
4533
4534 // register a handler for each start tag we support
4535 static TemplateNodeFactory::AutoRegister<TemplateNodeIf>            autoRefIf("if");
4536 static TemplateNodeFactory::AutoRegister<TemplateNodeFor>           autoRefFor("for");
4537 static TemplateNodeFactory::AutoRegister<TemplateNodeMsg>           autoRefMsg("msg");
4538 static TemplateNodeFactory::AutoRegister<TemplateNodeSet>           autoRefSet("set");
4539 static TemplateNodeFactory::AutoRegister<TemplateNodeTree>          autoRefTree("recursetree");
4540 static TemplateNodeFactory::AutoRegister<TemplateNodeWith>          autoRefWith("with");
4541 static TemplateNodeFactory::AutoRegister<TemplateNodeBlock>         autoRefBlock("block");
4542 static TemplateNodeFactory::AutoRegister<TemplateNodeCycle>         autoRefCycle("cycle");
4543 static TemplateNodeFactory::AutoRegister<TemplateNodeRange>         autoRefRange("range");
4544 static TemplateNodeFactory::AutoRegister<TemplateNodeExtend>        autoRefExtend("extend");
4545 static TemplateNodeFactory::AutoRegister<TemplateNodeCreate>        autoRefCreate("create");
4546 static TemplateNodeFactory::AutoRegister<TemplateNodeRepeat>        autoRefRepeat("repeat");
4547 static TemplateNodeFactory::AutoRegister<TemplateNodeInclude>       autoRefInclude("include");
4548 static TemplateNodeFactory::AutoRegister<TemplateNodeMarkers>       autoRefMarkers("markers");
4549 static TemplateNodeFactory::AutoRegister<TemplateNodeTabbing>       autoRefTabbing("tabbing");
4550 static TemplateNodeFactory::AutoRegister<TemplateNodeResource>      autoRefResource("resource");
4551 static TemplateNodeFactory::AutoRegister<TemplateNodeEncoding>      autoRefEncoding("encoding");
4552 static TemplateNodeFactory::AutoRegister<TemplateNodeSpaceless>     autoRefSpaceless("spaceless");
4553 static TemplateNodeFactory::AutoRegister<TemplateNodeIndexEntry>    autoRefIndexEntry("indexentry");
4554 static TemplateNodeFactory::AutoRegister<TemplateNodeOpenSubIndex>  autoRefOpenSubIndex("opensubindex");
4555 static TemplateNodeFactory::AutoRegister<TemplateNodeCloseSubIndex> autoRefCloseSubIndex("closesubindex");
4556
4557 //----------------------------------------------------------
4558
4559 TemplateBlockContext::TemplateBlockContext() : m_blocks(257)
4560 {
4561   m_blocks.setAutoDelete(TRUE);
4562 }
4563
4564 TemplateNodeBlock *TemplateBlockContext::get(const QCString &name) const
4565 {
4566   QList<TemplateNodeBlock> *list = m_blocks.find(name);
4567   if (list==0 || list->count()==0)
4568   {
4569     return 0;
4570   }
4571   else
4572   {
4573     return list->getLast();
4574   }
4575 }
4576
4577 TemplateNodeBlock *TemplateBlockContext::pop(const QCString &name) const
4578 {
4579   QList<TemplateNodeBlock> *list = m_blocks.find(name);
4580   if (list==0 || list->count()==0)
4581   {
4582     return 0;
4583   }
4584   else
4585   {
4586     return list->take(list->count()-1);
4587   }
4588 }
4589
4590 void TemplateBlockContext::add(TemplateNodeBlock *block)
4591 {
4592   QList<TemplateNodeBlock> *list = m_blocks.find(block->name());
4593   if (list==0)
4594   {
4595     list = new QList<TemplateNodeBlock>;
4596     m_blocks.insert(block->name(),list);
4597   }
4598   list->prepend(block);
4599 }
4600
4601 void TemplateBlockContext::add(TemplateBlockContext *ctx)
4602 {
4603   QDictIterator< QList<TemplateNodeBlock> > di(ctx->m_blocks);
4604   QList<TemplateNodeBlock> *list;
4605   for (di.toFirst();(list=di.current());++di)
4606   {
4607     QListIterator<TemplateNodeBlock> li(*list);
4608     TemplateNodeBlock *nb;
4609     for (li.toFirst();(nb=li.current());++li)
4610     {
4611       add(nb);
4612     }
4613   }
4614 }
4615
4616 void TemplateBlockContext::clear()
4617 {
4618   m_blocks.clear();
4619 }
4620
4621 void TemplateBlockContext::push(TemplateNodeBlock *block)
4622 {
4623   QList<TemplateNodeBlock> *list = m_blocks.find(block->name());
4624   if (list==0)
4625   {
4626     list = new QList<TemplateNodeBlock>;
4627     m_blocks.insert(block->name(),list);
4628   }
4629   list->append(block);
4630 }
4631
4632
4633 //----------------------------------------------------------
4634
4635 /** @brief Lexer class for turning a template into a list of tokens */
4636 class TemplateLexer
4637 {
4638   public:
4639     TemplateLexer(const TemplateEngine *engine,const QCString &fileName,const QCString &data);
4640     void tokenize(QList<TemplateToken> &tokens);
4641     void setOpenCloseCharacters(char openChar,char closeChar)
4642     { m_openChar=openChar; m_closeChar=closeChar; }
4643   private:
4644     void addToken(QList<TemplateToken> &tokens,
4645                   const char *data,int line,int startPos,int endPos,
4646                   TemplateToken::Type type);
4647     void reset();
4648     const TemplateEngine *m_engine;
4649     QCString m_fileName;
4650     QCString m_data;
4651     char m_openChar;
4652     char m_closeChar;
4653 };
4654
4655 TemplateLexer::TemplateLexer(const TemplateEngine *engine,const QCString &fileName,const QCString &data) :
4656   m_engine(engine), m_fileName(fileName), m_data(data)
4657 {
4658   m_openChar='{';
4659   m_closeChar='}';
4660 }
4661
4662 void TemplateLexer::tokenize(QList<TemplateToken> &tokens)
4663 {
4664   enum LexerStates
4665   {
4666     StateText,
4667     StateBeginTemplate,
4668     StateTag,
4669     StateEndTag,
4670     StateComment,
4671     StateEndComment,
4672     StateMaybeVar,
4673     StateVariable,
4674     StateEndVariable
4675   };
4676
4677   const char *p=m_data.data();
4678   if (p==0) return;
4679   int  state=StateText;
4680   int  pos=0;
4681   int  lastTokenPos=0;
4682   int  startLinePos=0;
4683   bool emptyOutputLine=TRUE;
4684   int  line=1;
4685   char c;
4686   int  markStartPos=-1;
4687   for (;(c=*p);p++,pos++)
4688   {
4689     switch (state)
4690     {
4691       case StateText:
4692         if (c==m_openChar) // {{ or {% or {# or something else
4693         {
4694           state=StateBeginTemplate;
4695         }
4696         else if (c!=' ' && c!='\t' && c!='\n') // non-whitepace text
4697         {
4698           emptyOutputLine=FALSE;
4699         }
4700         break;
4701       case StateBeginTemplate:
4702         switch (c)
4703         {
4704           case '%': // {%
4705             state=StateTag;
4706             markStartPos=pos-1;
4707             break;
4708           case '#': // {#
4709             state=StateComment;
4710             markStartPos=pos-1;
4711             break;
4712           case '{': // {{
4713             if (m_openChar=='{')
4714             {
4715               state=StateMaybeVar;
4716             }
4717             else
4718             {
4719               state=StateVariable;
4720             }
4721             markStartPos=pos-1;
4722             break;
4723           default:
4724             state=StateText;
4725             emptyOutputLine=FALSE;
4726             break;
4727         }
4728         break;
4729       case StateTag:
4730         if (c=='\n')
4731         {
4732           warn(m_fileName,line,"unexpected new line inside %c%%...%%%c block",m_openChar,m_closeChar);
4733           m_engine->printIncludeContext(m_fileName,line);
4734         }
4735         else if (c=='%') // %} or something else
4736         {
4737           state=StateEndTag;
4738         }
4739         break;
4740       case StateEndTag:
4741         if (c==m_closeChar) // %}
4742         {
4743           // found tag!
4744           state=StateText;
4745           addToken(tokens,m_data.data(),line,lastTokenPos,
4746                    emptyOutputLine ? startLinePos : markStartPos,
4747                    TemplateToken::Text);
4748           addToken(tokens,m_data.data(),line,markStartPos+2,
4749                    pos-1,TemplateToken::Block);
4750           lastTokenPos = pos+1;
4751         }
4752         else // something else
4753         {
4754           if (c=='\n')
4755           {
4756             warn(m_fileName,line,"unexpected new line inside %c%%...%%%c block",m_openChar,m_closeChar);
4757             m_engine->printIncludeContext(m_fileName,line);
4758           }
4759           state=StateTag;
4760         }
4761         break;
4762       case StateComment:
4763         if (c=='\n')
4764         {
4765           warn(m_fileName,line,"unexpected new line inside %c#...#%c block",m_openChar,m_closeChar);
4766           m_engine->printIncludeContext(m_fileName,line);
4767         }
4768         else if (c=='#') // #} or something else
4769         {
4770           state=StateEndComment;
4771         }
4772         break;
4773       case StateEndComment:
4774         if (c==m_closeChar) // #}
4775         {
4776           // found comment tag!
4777           state=StateText;
4778           addToken(tokens,m_data.data(),line,lastTokenPos,
4779                    emptyOutputLine ? startLinePos : markStartPos,
4780                    TemplateToken::Text);
4781           lastTokenPos = pos+1;
4782         }
4783         else // something else
4784         {
4785           if (c=='\n')
4786           {
4787             warn(m_fileName,line,"unexpected new line inside %c#...#%c block",m_openChar,m_closeChar);
4788             m_engine->printIncludeContext(m_fileName,line);
4789           }
4790           state=StateComment;
4791         }
4792         break;
4793       case StateMaybeVar:
4794         switch (c)
4795         {
4796           case '#': // {{#
4797             state=StateComment;
4798             markStartPos=pos-1;
4799             break;
4800           case '%': // {{%
4801             state=StateTag;
4802             markStartPos=pos-1;
4803             break;
4804           default:  // {{
4805             state=StateVariable;
4806             break;
4807         }
4808         break;
4809       case StateVariable:
4810         emptyOutputLine=FALSE; // assume a variable expands to content
4811         if (c=='\n')
4812         {
4813           warn(m_fileName,line,"unexpected new line inside %c{...}%c block",m_openChar,m_closeChar);
4814           m_engine->printIncludeContext(m_fileName,line);
4815         }
4816         else if (c=='}') // }} or something else
4817         {
4818           state=StateEndVariable;
4819         }
4820         break;
4821       case StateEndVariable:
4822         if (c==m_closeChar) // }}
4823         {
4824           // found variable tag!
4825           state=StateText;
4826           addToken(tokens,m_data.data(),line,lastTokenPos,
4827                    emptyOutputLine ? startLinePos : markStartPos,
4828                    TemplateToken::Text);
4829           addToken(tokens,m_data.data(),line,markStartPos+2,
4830                    pos-1,TemplateToken::Variable);
4831           lastTokenPos = pos+1;
4832         }
4833         else // something else
4834         {
4835           if (c=='\n')
4836           {
4837             warn(m_fileName,line,"unexpected new line inside %c{...}%c block",m_openChar,m_closeChar);
4838             m_engine->printIncludeContext(m_fileName,line);
4839           }
4840           state=StateVariable;
4841         }
4842         break;
4843     }
4844     if (c=='\n') // new line
4845     {
4846       state=StateText;
4847       startLinePos=pos+1;
4848       // if the current line only contain commands and whitespace,
4849       // then skip it in the output by moving lastTokenPos
4850       if (markStartPos!=-1 && emptyOutputLine) lastTokenPos = startLinePos;
4851       // reset markers
4852       markStartPos=-1;
4853       line++;
4854       emptyOutputLine=TRUE;
4855     }
4856   }
4857   if (lastTokenPos<pos)
4858   {
4859     addToken(tokens,m_data.data(),line,
4860              lastTokenPos,pos,
4861              TemplateToken::Text);
4862   }
4863 }
4864
4865 void TemplateLexer::addToken(QList<TemplateToken> &tokens,
4866                              const char *data,int line,
4867                              int startPos,int endPos,
4868                              TemplateToken::Type type)
4869 {
4870   if (startPos<endPos)
4871   {
4872     int len = endPos-startPos+1;
4873     QCString text(len);
4874     qstrncpy(text.rawData(),data+startPos,len);
4875     if (type!=TemplateToken::Text) text = text.stripWhiteSpace();
4876     tokens.append(new TemplateToken(type,text,line));
4877   }
4878 }
4879
4880 //----------------------------------------------------------
4881
4882 TemplateParser::TemplateParser(const TemplateEngine *engine,
4883                                const QCString &templateName,
4884                                QList<TemplateToken> &tokens) :
4885    m_engine(engine), m_templateName(templateName), m_tokens(tokens)
4886 {
4887 }
4888
4889 void TemplateParser::parse(
4890                      TemplateNode *parent,int line,const QStrList &stopAt,
4891                      QList<TemplateNode> &nodes)
4892 {
4893   TRACE(("{TemplateParser::parse\n"));
4894   // process the tokens. Build node list
4895   while (hasNextToken())
4896   {
4897     TemplateToken *tok = takeNextToken();
4898     //printf("%p:Token type=%d data='%s' line=%d\n",
4899     //       parent,tok->type,tok->data.data(),tok->line);
4900     switch(tok->type)
4901     {
4902       case TemplateToken::Text:
4903         nodes.append(new TemplateNodeText(this,parent,tok->line,tok->data));
4904         break;
4905       case TemplateToken::Variable: // {{ var }}
4906         nodes.append(new TemplateNodeVariable(this,parent,tok->line,tok->data));
4907         break;
4908       case TemplateToken::Block:    // {% tag %}
4909         {
4910           QCString command = tok->data;
4911           int sep = command.find(' ');
4912           if (sep!=-1)
4913           {
4914             command=command.left(sep);
4915           }
4916           if (stopAt.contains(command))
4917           {
4918             prependToken(tok);
4919             TRACE(("}TemplateParser::parse: stop\n"));
4920             return;
4921           }
4922           QCString arg;
4923           if (sep!=-1)
4924           {
4925             arg = tok->data.mid(sep+1);
4926           }
4927           TemplateNode *node = TemplateNodeFactory::instance()->
4928                                create(command,this,parent,tok->line,arg);
4929           if (node)
4930           {
4931             nodes.append(node);
4932           }
4933           else if (command=="empty"          || command=="else"         ||
4934                    command=="endif"          || command=="endfor"       ||
4935                    command=="endblock"       || command=="endwith"      ||
4936                    command=="endrecursetree" || command=="endspaceless" ||
4937                    command=="endmarkers"     || command=="endmsg"       ||
4938                    command=="endrepeat"      || command=="elif"         ||
4939                    command=="endrange"       || command=="endtabbing"   ||
4940                    command=="endencoding")
4941           {
4942             warn(m_templateName,tok->line,"Found tag '%s' without matching start tag",command.data());
4943           }
4944           else
4945           {
4946             warn(m_templateName,tok->line,"Unknown tag '%s'",command.data());
4947           }
4948         }
4949         break;
4950     }
4951     delete tok;
4952   }
4953   if (!stopAt.isEmpty())
4954   {
4955     QStrListIterator it(stopAt);
4956     const char *s;
4957     QCString options;
4958     for (it.toFirst();(s=it.current());++it)
4959     {
4960       if (!options.isEmpty()) options+=", ";
4961       options+=s;
4962     }
4963     warn(m_templateName,line,"Unclosed tag in template, expected one of: %s",
4964         options.data());
4965   }
4966   TRACE(("}TemplateParser::parse: last token\n"));
4967 }
4968
4969 bool TemplateParser::hasNextToken() const
4970 {
4971   return !m_tokens.isEmpty();
4972 }
4973
4974 TemplateToken *TemplateParser::takeNextToken()
4975 {
4976   return m_tokens.take(0);
4977 }
4978
4979 const TemplateToken *TemplateParser::currentToken() const
4980 {
4981   return m_tokens.getFirst();
4982 };
4983
4984 void TemplateParser::removeNextToken()
4985 {
4986   m_tokens.removeFirst();
4987 }
4988
4989 void TemplateParser::prependToken(const TemplateToken *token)
4990 {
4991   m_tokens.prepend(token);
4992 }
4993
4994 void TemplateParser::warn(const char *fileName,int line,const char *fmt,...) const
4995 {
4996   va_list args;
4997   va_start(args,fmt);
4998   va_warn(fileName,line,fmt,args);
4999   va_end(args);
5000   m_engine->printIncludeContext(fileName,line);
5001 }
5002
5003
5004
5005 //----------------------------------------------------------
5006
5007
5008 TemplateImpl::TemplateImpl(TemplateEngine *engine,const QCString &name,const QCString &data,
5009     const QCString &extension)
5010   : TemplateNode(0)
5011 {
5012   m_name = name;
5013   m_engine = engine;
5014   TemplateLexer lexer(engine,name,data);
5015   if (extension=="tex")
5016   {
5017     lexer.setOpenCloseCharacters('<','>');
5018   }
5019   QList<TemplateToken> tokens;
5020   tokens.setAutoDelete(TRUE);
5021   lexer.tokenize(tokens);
5022   TemplateParser parser(engine,name,tokens);
5023   parser.parse(this,1,QStrList(),m_nodes);
5024 }
5025
5026 TemplateImpl::~TemplateImpl()
5027 {
5028   //printf("deleting template %s\n",m_name.data());
5029 }
5030
5031 void TemplateImpl::render(FTextStream &ts, TemplateContext *c)
5032 {
5033   TemplateContextImpl *ci = dynamic_cast<TemplateContextImpl*>(c);
5034   if (ci==0) return; // should not happen
5035   if (!m_nodes.isEmpty())
5036   {
5037     TemplateNodeExtend *ne = dynamic_cast<TemplateNodeExtend*>(m_nodes.getFirst());
5038     if (ne==0) // normal template, add blocks to block context
5039     {
5040       TemplateBlockContext *bc = ci->blockContext();
5041       QListIterator<TemplateNode> li(m_nodes);
5042       TemplateNode *n;
5043       for (li.toFirst();(n=li.current());++li)
5044       {
5045         TemplateNodeBlock *nb = dynamic_cast<TemplateNodeBlock*>(n);
5046         if (nb)
5047         {
5048           bc->add(nb);
5049         }
5050       }
5051     }
5052     m_nodes.render(ts,c);
5053   }
5054 }
5055
5056 //----------------------------------------------------------
5057
5058 /** @brief Private data of the template engine */
5059 class TemplateEngine::Private
5060 {
5061     class IncludeEntry
5062     {
5063       public:
5064         enum Type { Template, Block };
5065         IncludeEntry(Type type,const QCString &fileName,const QCString &blockName,int line)
5066           : m_type(type), m_fileName(fileName), m_blockName(blockName), m_line(line) {}
5067         Type type() const { return m_type; }
5068         QCString fileName() const { return m_fileName; }
5069         QCString blockName() const { return m_blockName; }
5070         int line() const { return m_line; }
5071
5072       private:
5073         Type m_type;
5074         QCString m_fileName;
5075         QCString m_blockName;
5076         int m_line;
5077     };
5078   public:
5079     Private(TemplateEngine *engine) : m_templateCache(17) /*, m_indent(0)*/, m_engine(engine)
5080     {
5081       m_templateCache.setAutoDelete(TRUE);
5082       m_includeStack.setAutoDelete(TRUE);
5083     }
5084     Template *loadByName(const QCString &fileName,int line)
5085     {
5086       //for (int i=0;i<m_indent;i++) printf("  ");
5087       //m_indent++;
5088       //printf("loadByName(%s,%d) {\n",fileName.data(),line);
5089       m_includeStack.append(new IncludeEntry(IncludeEntry::Template,fileName,QCString(),line));
5090       Template *templ = m_templateCache.find(fileName);
5091       if (templ==0) // first time template is referenced
5092       {
5093         QCString filePath = m_templateDirName+"/"+fileName;
5094         QFile f(filePath);
5095         if (f.open(IO_ReadOnly))
5096         {
5097            QFileInfo fi(filePath);
5098            int size=fi.size();
5099            QCString data(size+1);
5100            if (f.readBlock(data.rawData(),size)==size)
5101            {
5102              templ = new TemplateImpl(m_engine,filePath,data,m_extension);
5103              m_templateCache.insert(fileName,templ);
5104              return templ;
5105            }
5106         }
5107         // fallback to default built-in template
5108         const QCString data = ResourceMgr::instance().getAsString(fileName);
5109         if (!data.isEmpty())
5110         {
5111           templ = new TemplateImpl(m_engine,fileName,data,m_extension);
5112           m_templateCache.insert(fileName,templ);
5113         }
5114         else
5115         {
5116           err("Could not open template file %s\n",fileName.data());
5117         }
5118       }
5119       return templ;
5120     }
5121     void unload(Template * /*t*/)
5122     {
5123       //(void)t;
5124       //m_indent--;
5125       //for (int i=0;i<m_indent;i++) printf("  ");
5126       //printf("}\n");
5127       m_includeStack.removeLast();
5128     }
5129
5130     void enterBlock(const QCString &fileName,const QCString &blockName,int line)
5131     {
5132       //for (int i=0;i<m_indent;i++) printf("  ");
5133       //m_indent++;
5134       //printf("enterBlock(%s,%s,%d) {\n",fileName.data(),blockName.data(),line);
5135       m_includeStack.append(new IncludeEntry(IncludeEntry::Block,fileName,blockName,line));
5136     }
5137
5138     void leaveBlock()
5139     {
5140       //m_indent--;
5141       //for (int i=0;i<m_indent;i++) printf("  ");
5142       //printf("}\n");
5143       m_includeStack.removeLast();
5144     }
5145
5146     void printIncludeContext(const char *fileName,int line) const
5147     {
5148       QListIterator<IncludeEntry> li(m_includeStack);
5149       li.toLast();
5150       IncludeEntry *ie=li.current();
5151       while ((ie=li.current()))
5152       {
5153         --li;
5154         IncludeEntry *next=li.current();
5155         if (ie->type()==IncludeEntry::Template)
5156         {
5157           if (next)
5158           {
5159             warn(fileName,line,"  inside template '%s' included from template '%s' at line %d",ie->fileName().data(),next->fileName().data(),ie->line());
5160           }
5161         }
5162         else // ie->type()==IncludeEntry::Block
5163         {
5164           warn(fileName,line,"  included by block '%s' inside template '%s' at line %d",ie->blockName().data(),
5165               ie->fileName().data(),ie->line());
5166         }
5167       }
5168     }
5169
5170     void setOutputExtension(const char *extension)
5171     {
5172       m_extension = extension;
5173     }
5174
5175     QCString outputExtension() const
5176     {
5177       return m_extension;
5178     }
5179
5180     void setTemplateDir(const char *dirName)
5181     {
5182       m_templateDirName = dirName;
5183     }
5184
5185   private:
5186     QDict<Template> m_templateCache;
5187     //mutable int m_indent;
5188     TemplateEngine *m_engine;
5189     QList<IncludeEntry> m_includeStack;
5190     QCString m_extension;
5191     QCString m_templateDirName;
5192 };
5193
5194 TemplateEngine::TemplateEngine()
5195 {
5196   p = new Private(this);
5197 }
5198
5199 TemplateEngine::~TemplateEngine()
5200 {
5201   delete p;
5202 }
5203
5204 TemplateContext *TemplateEngine::createContext() const
5205 {
5206   return new TemplateContextImpl(this);
5207 }
5208
5209 void TemplateEngine::destroyContext(TemplateContext *ctx)
5210 {
5211   delete ctx;
5212 }
5213
5214 Template *TemplateEngine::loadByName(const QCString &fileName,int line)
5215 {
5216   return p->loadByName(fileName,line);
5217 }
5218
5219 void TemplateEngine::unload(Template *t)
5220 {
5221   p->unload(t);
5222 }
5223
5224 void TemplateEngine::enterBlock(const QCString &fileName,const QCString &blockName,int line)
5225 {
5226   p->enterBlock(fileName,blockName,line);
5227 }
5228
5229 void TemplateEngine::leaveBlock()
5230 {
5231   p->leaveBlock();
5232 }
5233
5234 void TemplateEngine::printIncludeContext(const char *fileName,int line) const
5235 {
5236   p->printIncludeContext(fileName,line);
5237 }
5238
5239 void TemplateEngine::setOutputExtension(const char *extension)
5240 {
5241   p->setOutputExtension(extension);
5242 }
5243
5244 QCString TemplateEngine::outputExtension() const
5245 {
5246   return p->outputExtension();
5247 }
5248
5249 void TemplateEngine::setTemplateDir(const char *dirName)
5250 {
5251   p->setTemplateDir(dirName);
5252 }
5253
5254