Fix for UBSan build
[platform/upstream/doxygen.git] / src / searchindex.cpp
1 /******************************************************************************
2  *
3  * $Id: searchindex.cpp,v 1.7 2001/03/19 19:27:41 root Exp $
4  *
5  * Copyright (C) 1997-2012 by Dimitri van Heesch.
6  *
7  * Permission to use, copy, modify, and distribute this software and its
8  * documentation under the terms of the GNU General Public License is hereby 
9  * granted. No representations are made about the suitability of this software 
10  * for any purpose. It is provided "as is" without express or implied warranty.
11  * See the GNU General Public License for more details.
12  *
13  * Documents produced by Doxygen are derivative works derived from the
14  * input used in their production; they are not affected by this license.
15  *
16  */
17
18 #include <ctype.h>
19 #include <assert.h>
20
21 #include <qfile.h>
22 #include <qregexp.h>
23
24 #include "qtbc.h"
25 #include "searchindex.h"
26 #include "config.h"
27 #include "util.h"
28 #include "doxygen.h"
29 #include "language.h"
30 #include "pagedef.h"
31 #include "growbuf.h"
32 #include "message.h"
33
34
35 // file format: (all multi-byte values are stored in big endian format)
36 //   4 byte header
37 //   256*256*4 byte index (4 bytes)
38 //   for each index entry: a zero terminated list of words 
39 //   for each word: a \0 terminated string + 4 byte offset to the stats info
40 //   padding bytes to align at 4 byte boundary
41 //   for each word: the number of urls (4 bytes) 
42 //               + for each url containing the word 8 bytes statistics
43 //                 (4 bytes index to url string + 4 bytes frequency counter)
44 //   for each url: a \0 terminated string
45
46 const int numIndexEntries = 256*256;
47
48 //--------------------------------------------------------------------
49
50 IndexWord::IndexWord(const char *word) : m_word(word), m_urls(17)
51 {
52   m_urls.setAutoDelete(TRUE);
53   //printf("IndexWord::IndexWord(%s)\n",word);
54 }
55
56 void IndexWord::addUrlIndex(int idx,bool hiPriority)
57 {
58   //printf("IndexWord::addUrlIndex(%d,%d)\n",idx,hiPriority);
59   URLInfo *ui = m_urls.find(idx);
60   if (ui==0)
61   {
62     //printf("URLInfo::URLInfo(%d)\n",idx);
63     ui=new URLInfo(idx,0);
64     m_urls.insert(idx,ui);
65   }
66   ui->freq+=2;
67   if (hiPriority) ui->freq|=1; // mark as high priority document
68 }
69
70 //--------------------------------------------------------------------
71
72 SearchIndex::SearchIndex() : SearchIndexIntf(Internal), 
73       m_words(328829), m_index(numIndexEntries), m_urlIndex(-1)
74 {
75   int i;
76   m_words.setAutoDelete(TRUE);
77   m_urls.setAutoDelete(TRUE);
78   m_index.setAutoDelete(TRUE);
79   for (i=0;i<numIndexEntries;i++) m_index.insert(i,new QList<IndexWord>);
80 }
81
82 void SearchIndex::setCurrentDoc(Definition *ctx,const char *anchor,bool isSourceFile)
83 {
84   if (ctx==0) return;
85   assert(!isSourceFile || ctx->definitionType()==Definition::TypeFile);
86   //printf("SearchIndex::setCurrentDoc(%s,%s,%s)\n",name,baseName,anchor);
87   QCString url=isSourceFile ? ((FileDef*)ctx)->getSourceFileBase() : ctx->getOutputFileBase();
88   url+=Config_getString("HTML_FILE_EXTENSION");
89   if (anchor) url+=QCString("#")+anchor;  
90   m_urlIndex++;
91   QCString name=ctx->qualifiedName();
92   if (ctx->definitionType()==Definition::TypeMember)
93   {
94     MemberDef *md = (MemberDef *)ctx;
95     name.prepend((md->getLanguage()==SrcLangExt_Fortran  ? 
96                  theTranslator->trSubprogram(TRUE,TRUE) :
97                  theTranslator->trMember(TRUE,TRUE))+" ");
98   }
99   else // compound type
100   {
101     SrcLangExt lang = ctx->getLanguage();
102     QCString sep = getLanguageSpecificSeparator(lang);
103     if (sep!="::")
104     {
105       name = substitute(name,"::",sep);
106     }
107     switch (ctx->definitionType())
108     {
109       case Definition::TypePage:
110         {
111           PageDef *pd = (PageDef *)ctx;
112           if (!pd->title().isEmpty())
113           {
114             name = theTranslator->trPage(TRUE,TRUE)+" "+pd->title();
115           }
116           else
117           {
118             name = theTranslator->trPage(TRUE,TRUE)+" "+pd->name();
119           }
120         }
121         break;
122       case Definition::TypeClass:
123         {
124           ClassDef *cd = (ClassDef *)ctx;
125           name.prepend(cd->compoundTypeString()+" ");
126         }
127         break;
128       case Definition::TypeNamespace:
129         {
130           if (lang==SrcLangExt_Java || lang==SrcLangExt_CSharp)
131           {
132             name = theTranslator->trPackage(name);
133           }
134           else if (lang==SrcLangExt_Fortran)
135           {
136             name.prepend(theTranslator->trModule(TRUE,TRUE)+" ");
137           }
138           else
139           {
140             name.prepend(theTranslator->trNamespace(TRUE,TRUE)+" ");
141           }
142         }
143         break;
144       case Definition::TypeGroup:
145         {
146           GroupDef *gd = (GroupDef *)ctx;
147           if (gd->groupTitle())
148           {
149             name = theTranslator->trGroup(TRUE,TRUE)+" "+gd->groupTitle();
150           }
151           else
152           {
153             name.prepend(theTranslator->trGroup(TRUE,TRUE)+" ");
154           }
155         }
156         break;
157       default:
158         break;
159     }
160   }
161
162   m_urls.insert(m_urlIndex,new URL(name,url));
163 }
164
165 static int charsToIndex(const char *word)
166 {
167   if (word==0) return -1;
168
169   // Fast string hashing algorithm
170   //register ushort h=0;
171   //const char *k = word;
172   //ushort mask=0xfc00;
173   //while ( *k ) 
174   //{
175   //  h = (h&mask)^(h<<6)^(*k++);
176   //}
177   //return h;
178
179   // Simple hashing that allows for substring searching
180   uint c1=word[0];
181   if (c1==0) return -1;
182   uint c2=word[1];
183   if (c2==0) return -1;
184   return c1*256+c2;
185 }
186
187 void SearchIndex::addWord(const char *word,bool hiPriority,bool recurse)
188 {
189   static QRegExp nextPart("[_a-z:][A-Z]");
190   //printf("SearchIndex::addWord(%s,%d)\n",word,hiPriority);
191   if (word==0 || word[0]=='\0') return;
192   QCString wStr = QCString(word).lower();
193   IndexWord *w = m_words[wStr];
194   if (w==0)
195   {
196     int idx=charsToIndex(wStr);
197     if (idx<0) return;
198     w = new IndexWord(wStr);
199     //fprintf(stderr,"addWord(%s) at index %d\n",word,idx);
200     m_index[idx]->append(w);
201     m_words.insert(wStr,w);
202   }
203   w->addUrlIndex(m_urlIndex,hiPriority);
204   int i;
205   bool found=FALSE;
206   if (!recurse) // the first time we check if we can strip the prefix
207   {
208     i=getPrefixIndex(word);
209     if (i>0)
210     {
211       addWord(word+i,hiPriority,TRUE);
212       found=TRUE;
213     }
214   }
215   if (!found) // no prefix stripped
216   {
217     if ((i=nextPart.match(word))>=1)
218     {
219       addWord(word+i+1,hiPriority,TRUE);
220     }
221   }
222 }
223
224 void SearchIndex::addWord(const char *word,bool hiPriority)
225 {
226   addWord(word,hiPriority,FALSE);
227 }
228
229 static void writeInt(QFile &f,int index)
230 {
231   f.putch(((uint)index)>>24);
232   f.putch((((uint)index)>>16)&0xff);
233   f.putch((((uint)index)>>8)&0xff);
234   f.putch(((uint)index)&0xff);
235 }
236
237 static void writeString(QFile &f,const char *s)
238 {
239   const char *p = s;
240   while (*p) f.putch(*p++);
241   f.putch(0);
242 }
243
244 void SearchIndex::write(const char *fileName)
245 {
246   int i;
247   int size=4; // for the header
248   size+=4*numIndexEntries; // for the index
249   int wordsOffset = size;
250   // first pass: compute the size of the wordlist
251   for (i=0;i<numIndexEntries;i++)
252   {
253     QList<IndexWord> *wlist = m_index[i];
254     if (!wlist->isEmpty())
255     {
256       QListIterator<IndexWord> iwi(*wlist);
257       IndexWord *iw;
258       for (iwi.toFirst();(iw=iwi.current());++iwi)
259       {
260         int ws = iw->word().length()+1; 
261         size+=ws+4; // word + url info list offset
262       }
263       size+=1; // zero list terminator
264     }
265   }
266
267   // second pass: compute the offsets in the index
268   int indexOffsets[numIndexEntries];
269   int offset=wordsOffset;
270   for (i=0;i<numIndexEntries;i++)
271   {
272     QList<IndexWord> *wlist = m_index[i];
273     if (!wlist->isEmpty())
274     {
275       indexOffsets[i]=offset;
276       QListIterator<IndexWord> iwi(*wlist);
277       IndexWord *iw;
278       for (iwi.toFirst();(iw=iwi.current());++iwi)
279       {
280         offset+= iw->word().length()+1; 
281         offset+=4; // word + offset to url info array 
282       }
283       offset+=1; // zero list terminator
284     }
285     else
286     {
287       indexOffsets[i]=0;
288     }
289   }
290   int padding = size;
291   size = (size+3)&~3; // round up to 4 byte boundary
292   padding = size - padding;
293
294   //int statsOffset = size;
295   QDictIterator<IndexWord> wdi(m_words);
296   //IndexWord *iw;
297   int *wordStatOffsets = new int[m_words.count()];
298   
299   int count=0;
300
301   // third pass: compute offset to stats info for each word
302   for (i=0;i<numIndexEntries;i++)
303   {
304     QList<IndexWord> *wlist = m_index[i];
305     if (!wlist->isEmpty())
306     {
307       QListIterator<IndexWord> iwi(*wlist);
308       IndexWord *iw;
309       for (iwi.toFirst();(iw=iwi.current());++iwi)
310       {
311         //printf("wordStatOffsets[%d]=%d\n",count,size);
312         wordStatOffsets[count++] = size;
313         size+=4+iw->urls().count()*8; // count + (url_index,freq) per url
314       }
315     }
316   }
317   int *urlOffsets = new int[m_urls.count()];
318   //int urlsOffset = size;
319   QIntDictIterator<URL> udi(m_urls);
320   URL *url;
321   for (udi.toFirst();(url=udi.current());++udi)
322   {
323     urlOffsets[udi.currentKey()]=size;
324     size+=url->name.length()+1+
325           url->url.length()+1;
326   }
327   //printf("Total size %x bytes (word=%x stats=%x urls=%x)\n",size,wordsOffset,statsOffset,urlsOffset);
328   QFile f(fileName);
329   if (f.open(IO_WriteOnly))
330   {
331     // write header
332     f.putch('D'); f.putch('O'); f.putch('X'); f.putch('S');
333     // write index
334     for (i=0;i<numIndexEntries;i++)
335     {
336       writeInt(f,indexOffsets[i]);
337     }
338     // write word lists
339     count=0;
340     for (i=0;i<numIndexEntries;i++)
341     {
342       QList<IndexWord> *wlist = m_index[i];
343       if (!wlist->isEmpty())
344       {
345         QListIterator<IndexWord> iwi(*wlist);
346         IndexWord *iw;
347         for (iwi.toFirst();(iw=iwi.current());++iwi)
348         {
349           writeString(f,iw->word());
350           writeInt(f,wordStatOffsets[count++]);
351         }
352         f.putch(0);
353       }
354     }
355     // write extra padding bytes
356     for (i=0;i<padding;i++) f.putch(0);
357     // write word statistics
358     for (i=0;i<numIndexEntries;i++)
359     {
360       QList<IndexWord> *wlist = m_index[i];
361       if (!wlist->isEmpty())
362       {
363         QListIterator<IndexWord> iwi(*wlist);
364         IndexWord *iw;
365         for (iwi.toFirst();(iw=iwi.current());++iwi)
366         {
367           int numUrls = iw->urls().count();
368           writeInt(f,numUrls);
369           QIntDictIterator<URLInfo> uli(iw->urls());
370           URLInfo *ui;
371           for (uli.toFirst();(ui=uli.current());++uli)
372           {
373             writeInt(f,urlOffsets[ui->urlIdx]);
374             writeInt(f,ui->freq);
375           }
376         }
377       }
378     }
379     // write urls
380     QIntDictIterator<URL> udi(m_urls);
381     URL *url;
382     for (udi.toFirst();(url=udi.current());++udi)
383     {
384       writeString(f,url->name);
385       writeString(f,url->url);
386     }
387   }
388
389   delete[] urlOffsets;
390   delete[] wordStatOffsets;
391 }
392
393
394 //---------------------------------------------------------------------------
395 // the following part is for writing an external search index
396
397 struct SearchDocEntry
398 {
399   QCString type;
400   QCString name;
401   QCString tag;
402   QCString url;
403   GrowBuf  importantText;
404   GrowBuf  normalText;
405 };
406
407 struct SearchIndexExternal::Private
408 {
409   Private() : docEntries(257) {}
410   //QFile f;
411   //bool openOk;
412   //FTextStream t;
413   //bool insideDoc;
414   SDict<SearchDocEntry> docEntries;
415   SearchDocEntry *current;
416 };
417
418 SearchIndexExternal::SearchIndexExternal() : SearchIndexIntf(External)
419 {
420   p = new SearchIndexExternal::Private;
421   p->docEntries.setAutoDelete(TRUE);
422   p->current=0;
423   //p->f.setName(fileName);
424   //p->openOk = p->f.open(IO_WriteOnly);
425   //if (p->openOk) 
426   //{
427   //  p->t.setDevice(&p->f);
428   //  p->t << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" << endl;
429   //  p->t << "<add>" << endl;
430   //  p->insideDoc=FALSE;
431   //}
432 }
433
434 SearchIndexExternal::~SearchIndexExternal()
435 {
436   //if (p->openOk)
437   //{
438   //  if (p->insideDoc)
439   //  {
440   //    p->t << "  </doc>" << endl;
441   //  }
442   //  p->t << "</add>" << endl;
443   //  p->f.close();
444   //  p->openOk=FALSE;
445   //}
446   delete p;
447 }
448
449 static QCString definitionToName(Definition *ctx)
450 {
451   if (ctx->definitionType()==Definition::TypeMember)
452   {
453     MemberDef *md = (MemberDef*)ctx;
454     if (md->isFunction())
455       return "function";
456     else if (md->isSlot())
457       return "slot";
458     else if (md->isSignal())
459       return "signal";
460     else if (md->isVariable())
461       return "variable";
462     else if (md->isTypedef())
463       return "typedef";
464     else if (md->isEnumerate())
465       return "enum";
466     else if (md->isEnumValue())
467       return "enumvalue";
468     else if (md->isProperty())
469       return "property";
470     else if (md->isEvent())
471       return "event";
472     else if (md->isRelated() || md->isForeign())
473       return "related";
474     else if (md->isFriend())
475       return "friend";
476     else if (md->isDefine())
477       return "define";
478   }
479   else if (ctx)
480   {
481     switch(ctx->definitionType())
482     {
483       case Definition::TypeClass: 
484         return ((ClassDef*)ctx)->compoundTypeString();
485       case Definition::TypeFile:
486         return "file";
487       case Definition::TypeNamespace:
488         return "namespace";
489       case Definition::TypeGroup:
490         return "group";
491       case Definition::TypePackage:
492         return "package";
493       case Definition::TypePage:
494         return "page";
495       case Definition::TypeDir:
496         return "dir";
497       default:
498         break;
499     }
500   }
501   return "unknown";
502 }
503
504 void SearchIndexExternal::setCurrentDoc(Definition *ctx,const char *anchor,bool isSourceFile)
505 {
506   //if (p->openOk)
507   //{
508     SearchDocEntry *e = new SearchDocEntry;
509     e->type = definitionToName(ctx);
510     e->name = ctx->qualifiedName();
511     e->tag  = stripPath(Config_getString("GENERATE_TAGFILE"));
512     QCString baseName = isSourceFile ? ((FileDef*)ctx)->getSourceFileBase() : ctx->getOutputFileBase();
513     e->url  = baseName + Doxygen::htmlFileExtension;
514     if (anchor) e->url+=QCString("#")+anchor;
515     p->current = e;
516     p->docEntries.append(e->url,e);
517     //if (p->insideDoc)
518     //{
519     //  p->t << "  </doc>" << endl;
520     //}
521     //p->t << "  <doc>" << endl;
522     //QCString baseName = isSourceFile ? ((FileDef*)ctx)->getSourceFileBase() : ctx->getOutputFileBase();
523     //p->t << "    <field name=\"type\">" << definitionToName(ctx) << "</field>" << endl;
524     //p->t << "    <field name=\"name\">" << convertToXML(ctx->qualifiedName()) << "</field>" << endl;
525     //p->t << "    <field name=\"tag\">"  << convertToXML(stripPath(Config_getString("GENERATE_TAGFILE"))) << "</field>" << endl;
526     //p->t << "    <field name=\"url\">" << baseName << Doxygen::htmlFileExtension;
527     //if (anchor) p->t << "#" << anchor;
528     //p->t << "</field>" << endl;
529     //p->insideDoc=TRUE;
530   //}
531 }
532
533 void SearchIndexExternal::addWord(const char *word,bool hiPriority)
534 {
535   if (word==0 || !isId(*word) || p->current==0) return;
536   GrowBuf *pText = hiPriority ? &p->current->importantText : &p->current->normalText;
537   if (pText->getPos()>0) pText->addChar(' ');
538   pText->addStr(word);
539   //if (p->openOk)
540   //{
541   //  p->t << "    <field name=\"text";
542   //  if (hiPriority) p->t << "\" boost=\"yes";
543   //  p->t << "\">";
544   //  p->t << convertToXML(word);
545   //  p->t << "</field>" << endl;
546   //}
547 }
548
549 void SearchIndexExternal::write(const char *fileName)
550 {
551   QFile f(fileName);
552   if (f.open(IO_WriteOnly))
553   {
554     FTextStream t(&f);
555     t << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" << endl;
556     t << "<add>" << endl;
557     SDict<SearchDocEntry>::Iterator it(p->docEntries);
558     SearchDocEntry *doc;
559     for (it.toFirst();(doc=it.current());++it)
560     {
561       doc->normalText.addChar(0);    // make sure buffer ends with a 0 terminator
562       doc->importantText.addChar(0); // make sure buffer ends with a 0 terminator
563       t << "  <doc>" << endl;
564       t << "    <field name=\"type\">" << doc->type << "</field>" << endl;
565       t << "    <field name=\"name\">" << convertToXML(doc->name) << "</field>" << endl;
566       t << "    <field name=\"tag\">"  << convertToXML(doc->tag)  << "</field>" << endl;
567       t << "    <field name=\"url\">"  << doc->url  << "</field>" << endl;
568       t << "    <field name=\"keywords\">" << convertToXML(doc->importantText.get())  << "</field>" << endl;
569       t << "    <field name=\"text\">"     << convertToXML(doc->normalText.get())     << "</field>" << endl;
570       t << "  </doc>" << endl;
571     }
572     t << "</add>" << endl;
573   }
574   else
575   {
576     err("Failed to open file %s for writing!\n",fileName);
577   }
578 }
579
580 //---------------------------------------------------------------------------
581 // the following part is for the javascript based search engine
582
583 #include "memberdef.h"
584 #include "namespacedef.h"
585 #include "pagedef.h"
586 #include "classdef.h"
587 #include "filedef.h"
588 #include "language.h"
589 #include "doxygen.h"
590 #include "message.h"
591
592 static const char search_script[]=
593 #include "search_js.h"
594 ;
595
596 #define MEMBER_INDEX_ENTRIES   256
597
598 #define SEARCH_INDEX_ALL         0
599 #define SEARCH_INDEX_CLASSES     1
600 #define SEARCH_INDEX_NAMESPACES  2
601 #define SEARCH_INDEX_FILES       3
602 #define SEARCH_INDEX_FUNCTIONS   4
603 #define SEARCH_INDEX_VARIABLES   5
604 #define SEARCH_INDEX_TYPEDEFS    6
605 #define SEARCH_INDEX_ENUMS       7
606 #define SEARCH_INDEX_ENUMVALUES  8
607 #define SEARCH_INDEX_PROPERTIES  9
608 #define SEARCH_INDEX_EVENTS     10
609 #define SEARCH_INDEX_RELATED    11
610 #define SEARCH_INDEX_DEFINES    12
611 #define SEARCH_INDEX_GROUPS     13
612 #define SEARCH_INDEX_PAGES      14
613 #define NUM_SEARCH_INDICES      15
614
615 class SearchIndexList : public SDict< QList<Definition> >
616 {
617   public:
618     SearchIndexList(int size=17) : SDict< QList<Definition> >(size,FALSE) 
619     {
620       setAutoDelete(TRUE);
621     }
622    ~SearchIndexList() {}
623     void append(Definition *d)
624     {
625       QList<Definition> *l = find(d->name());
626       if (l==0)
627       {
628         l=new QList<Definition>;
629         SDict< QList<Definition> >::append(d->name(),l);
630       }
631       l->append(d);
632     }
633     int compareItems(GCI item1, GCI item2)
634     {
635       QList<Definition> *md1=(QList<Definition> *)item1;
636       QList<Definition> *md2=(QList<Definition> *)item2;
637       QCString n1 = md1->first()->localName();
638       QCString n2 = md2->first()->localName();
639       return stricmp(n1.data(),n2.data());
640     }
641 };
642
643 static void addMemberToSearchIndex(
644          SearchIndexList symbols[NUM_SEARCH_INDICES][MEMBER_INDEX_ENTRIES],
645          int symbolCount[NUM_SEARCH_INDICES],
646          MemberDef *md)
647 {
648   static bool hideFriendCompounds = Config_getBool("HIDE_FRIEND_COMPOUNDS");
649   bool isLinkable = md->isLinkable();
650   ClassDef *cd=0;
651   NamespaceDef *nd=0;
652   FileDef *fd=0;
653   GroupDef *gd=0;
654   if (isLinkable && 
655       (
656        ((cd=md->getClassDef()) && cd->isLinkable() && cd->templateMaster()==0) ||
657        ((gd=md->getGroupDef()) && gd->isLinkable())
658       )
659      )
660   {
661     QCString n = md->name();
662     uchar charCode = (uchar)n.at(0);
663     uint letter = charCode<128 ? tolower(charCode) : charCode;
664     if (!n.isEmpty()) 
665     {
666       bool isFriendToHide = hideFriendCompounds &&
667         (QCString(md->typeString())=="friend class" || 
668          QCString(md->typeString())=="friend struct" ||
669          QCString(md->typeString())=="friend union");
670       if (!(md->isFriend() && isFriendToHide))
671       {
672         symbols[SEARCH_INDEX_ALL][letter].append(md);
673         symbolCount[SEARCH_INDEX_ALL]++;
674       }
675       if (md->isFunction() || md->isSlot() || md->isSignal())
676       {
677         symbols[SEARCH_INDEX_FUNCTIONS][letter].append(md);
678         symbolCount[SEARCH_INDEX_FUNCTIONS]++;
679       } 
680       else if (md->isVariable())
681       {
682         symbols[SEARCH_INDEX_VARIABLES][letter].append(md);
683         symbolCount[SEARCH_INDEX_VARIABLES]++;
684       }
685       else if (md->isTypedef())
686       {
687         symbols[SEARCH_INDEX_TYPEDEFS][letter].append(md);
688         symbolCount[SEARCH_INDEX_TYPEDEFS]++;
689       }
690       else if (md->isEnumerate())
691       {
692         symbols[SEARCH_INDEX_ENUMS][letter].append(md);
693         symbolCount[SEARCH_INDEX_ENUMS]++;
694       }
695       else if (md->isEnumValue())
696       {
697         symbols[SEARCH_INDEX_ENUMVALUES][letter].append(md);
698         symbolCount[SEARCH_INDEX_ENUMVALUES]++;
699       }
700       else if (md->isProperty())
701       {
702         symbols[SEARCH_INDEX_PROPERTIES][letter].append(md);
703         symbolCount[SEARCH_INDEX_PROPERTIES]++;
704       }
705       else if (md->isEvent())
706       {
707         symbols[SEARCH_INDEX_EVENTS][letter].append(md);
708         symbolCount[SEARCH_INDEX_EVENTS]++;
709       }
710       else if (md->isRelated() || md->isForeign() ||
711                (md->isFriend() && !isFriendToHide))
712       {
713         symbols[SEARCH_INDEX_RELATED][letter].append(md);
714         symbolCount[SEARCH_INDEX_RELATED]++;
715       }
716     }
717   }
718   else if (isLinkable && 
719       (((nd=md->getNamespaceDef()) && nd->isLinkable()) || 
720        ((fd=md->getFileDef())      && fd->isLinkable())
721       )
722      )
723   {
724     QCString n = md->name();
725     uchar charCode = (uchar)n.at(0);
726     uint letter = charCode<128 ? tolower(charCode) : charCode;
727     if (!n.isEmpty()) 
728     {
729       symbols[SEARCH_INDEX_ALL][letter].append(md);
730       symbolCount[SEARCH_INDEX_ALL]++;
731
732       if (md->isFunction()) 
733       {
734         symbols[SEARCH_INDEX_FUNCTIONS][letter].append(md);
735         symbolCount[SEARCH_INDEX_FUNCTIONS]++;
736       }
737       else if (md->isVariable()) 
738       {
739         symbols[SEARCH_INDEX_VARIABLES][letter].append(md);
740         symbolCount[SEARCH_INDEX_VARIABLES]++;
741       }
742       else if (md->isTypedef())
743       {
744         symbols[SEARCH_INDEX_TYPEDEFS][letter].append(md);
745         symbolCount[SEARCH_INDEX_TYPEDEFS]++;
746       }
747       else if (md->isEnumerate())
748       {
749         symbols[SEARCH_INDEX_ENUMS][letter].append(md);
750         symbolCount[SEARCH_INDEX_ENUMS]++;
751       }
752       else if (md->isEnumValue())
753       {
754         symbols[SEARCH_INDEX_ENUMVALUES][letter].append(md);
755         symbolCount[SEARCH_INDEX_ENUMVALUES]++;
756       }
757       else if (md->isDefine())
758       {
759         symbols[SEARCH_INDEX_DEFINES][letter].append(md);
760         symbolCount[SEARCH_INDEX_DEFINES]++;
761       }
762     }
763   }
764 }
765
766 static QCString searchId(const QCString &s)
767 {
768   int c;
769   uint i;
770   QCString result;
771   for (i=0;i<s.length();i++)
772   {
773     c=s.at(i);
774     if ((c>='0' && c<='9') || (c>='A' && c<='Z') || (c>='a' && c<='z'))
775     {
776       result+=(char)tolower(c);
777     }
778     else
779     {
780       char val[4];
781       sprintf(val,"_%02x",(uchar)c);
782       result+=val;
783     }
784   }
785   return result;
786 }
787
788 static int g_searchIndexCount[NUM_SEARCH_INDICES];
789 static SearchIndexList g_searchIndexSymbols[NUM_SEARCH_INDICES][MEMBER_INDEX_ENTRIES];
790 static const char *g_searchIndexName[NUM_SEARCH_INDICES] = 
791
792     "all",
793     "classes",
794     "namespaces",
795     "files",
796     "functions",
797     "variables",
798     "typedefs", 
799     "enums", 
800     "enumvalues",
801     "properties", 
802     "events", 
803     "related",
804     "defines",
805     "groups",
806     "pages"
807 };
808
809
810 class SearchIndexCategoryMapping
811 {
812   public:
813     SearchIndexCategoryMapping()
814     {
815       categoryLabel[SEARCH_INDEX_ALL]        = theTranslator->trAll();
816       categoryLabel[SEARCH_INDEX_CLASSES]    = theTranslator->trClasses();
817       categoryLabel[SEARCH_INDEX_NAMESPACES] = theTranslator->trNamespace(TRUE,FALSE);
818       categoryLabel[SEARCH_INDEX_FILES]      = theTranslator->trFile(TRUE,FALSE);
819       categoryLabel[SEARCH_INDEX_FUNCTIONS]  = theTranslator->trFunctions();
820       categoryLabel[SEARCH_INDEX_VARIABLES]  = theTranslator->trVariables();
821       categoryLabel[SEARCH_INDEX_TYPEDEFS]   = theTranslator->trTypedefs();
822       categoryLabel[SEARCH_INDEX_ENUMS]      = theTranslator->trEnumerations();
823       categoryLabel[SEARCH_INDEX_ENUMVALUES] = theTranslator->trEnumerationValues();
824       categoryLabel[SEARCH_INDEX_PROPERTIES] = theTranslator->trProperties();
825       categoryLabel[SEARCH_INDEX_EVENTS]     = theTranslator->trEvents();
826       categoryLabel[SEARCH_INDEX_RELATED]    = theTranslator->trFriends();
827       categoryLabel[SEARCH_INDEX_DEFINES]    = theTranslator->trDefines();
828       categoryLabel[SEARCH_INDEX_GROUPS]     = theTranslator->trGroup(TRUE,FALSE);
829       categoryLabel[SEARCH_INDEX_PAGES]      = theTranslator->trPage(TRUE,FALSE);
830     }
831     QCString categoryLabel[NUM_SEARCH_INDICES];
832 };
833
834 void writeJavascriptSearchIndex()
835 {
836   if (!Config_getBool("GENERATE_HTML")) return;
837
838   // index classes
839   ClassSDict::Iterator cli(*Doxygen::classSDict);
840   ClassDef *cd;
841   for (;(cd=cli.current());++cli)
842   {
843     uchar charCode = (uchar)cd->localName().at(0);
844     uint letter = charCode<128 ? tolower(charCode) : charCode;
845     if (cd->isLinkable() && isId(letter))
846     {
847       g_searchIndexSymbols[SEARCH_INDEX_ALL][letter].append(cd);
848       g_searchIndexSymbols[SEARCH_INDEX_CLASSES][letter].append(cd);
849       g_searchIndexCount[SEARCH_INDEX_ALL]++;
850       g_searchIndexCount[SEARCH_INDEX_CLASSES]++;
851     }
852   }
853
854   // index namespaces
855   NamespaceSDict::Iterator nli(*Doxygen::namespaceSDict);
856   NamespaceDef *nd;
857   for (;(nd=nli.current());++nli)
858   {
859     uchar charCode = (uchar)nd->name().at(0);
860     uint letter = charCode<128 ? tolower(charCode) : charCode;
861     if (nd->isLinkable() && isId(letter))
862     {
863       g_searchIndexSymbols[SEARCH_INDEX_ALL][letter].append(nd);
864       g_searchIndexSymbols[SEARCH_INDEX_NAMESPACES][letter].append(nd);
865       g_searchIndexCount[SEARCH_INDEX_ALL]++;
866       g_searchIndexCount[SEARCH_INDEX_NAMESPACES]++;
867     }
868   }
869
870   // index files
871   FileNameListIterator fnli(*Doxygen::inputNameList);
872   FileName *fn;
873   for (;(fn=fnli.current());++fnli)
874   {
875     FileNameIterator fni(*fn);
876     FileDef *fd;
877     for (;(fd=fni.current());++fni)
878     {
879       uchar charCode = (uchar)fd->name().at(0);
880       uint letter = charCode<128 ? tolower(charCode) : charCode;
881       if (fd->isLinkable() && isId(letter))
882       {
883         g_searchIndexSymbols[SEARCH_INDEX_ALL][letter].append(fd);
884         g_searchIndexSymbols[SEARCH_INDEX_FILES][letter].append(fd);
885         g_searchIndexCount[SEARCH_INDEX_ALL]++;
886         g_searchIndexCount[SEARCH_INDEX_FILES]++;
887       }
888     }
889   }
890
891   // index class members
892   {
893     MemberNameSDict::Iterator mnli(*Doxygen::memberNameSDict);
894     MemberName *mn;
895     // for each member name
896     for (mnli.toFirst();(mn=mnli.current());++mnli)
897     {
898       MemberDef *md;
899       MemberNameIterator mni(*mn);
900       // for each member definition
901       for (mni.toFirst();(md=mni.current());++mni)
902       {
903         addMemberToSearchIndex(g_searchIndexSymbols,g_searchIndexCount,md);
904       }
905     }
906   }
907
908   // index file/namespace members
909   {
910     MemberNameSDict::Iterator fnli(*Doxygen::functionNameSDict);
911     MemberName *mn;
912     // for each member name
913     for (fnli.toFirst();(mn=fnli.current());++fnli)
914     {
915       MemberDef *md;
916       MemberNameIterator mni(*mn);
917       // for each member definition
918       for (mni.toFirst();(md=mni.current());++mni)
919       {
920         addMemberToSearchIndex(g_searchIndexSymbols,g_searchIndexCount,md);
921       }
922     }
923   }
924
925   // index groups
926   GroupSDict::Iterator gli(*Doxygen::groupSDict);
927   GroupDef *gd;
928   for (gli.toFirst();(gd=gli.current());++gli)
929   {
930     if (gd->isLinkable())
931     {
932       QCString title = gd->groupTitle();
933       if (!title.isEmpty()) // TODO: able searching for all word in the title
934       {
935         uchar charCode = title.at(0);
936         uint letter = charCode<128 ? tolower(charCode) : charCode;
937         if (isId(letter))
938         {
939           g_searchIndexSymbols[SEARCH_INDEX_ALL][letter].append(gd);
940           g_searchIndexSymbols[SEARCH_INDEX_GROUPS][letter].append(gd);
941           g_searchIndexCount[SEARCH_INDEX_ALL]++;
942           g_searchIndexCount[SEARCH_INDEX_GROUPS]++;
943         }
944       }
945     }
946   }
947
948   // index pages
949   PageSDict::Iterator pdi(*Doxygen::pageSDict);
950   PageDef *pd=0;
951   for (pdi.toFirst();(pd=pdi.current());++pdi)
952   {
953     if (pd->isLinkable())
954     {
955       QCString title = pd->title();
956       if (!title.isEmpty())
957       {
958         uchar charCode = title.at(0);
959         uint letter = charCode<128 ? tolower(charCode) : charCode;
960         if (isId(letter))
961         {
962           g_searchIndexSymbols[SEARCH_INDEX_ALL][letter].append(pd);
963           g_searchIndexSymbols[SEARCH_INDEX_PAGES][letter].append(pd);
964           g_searchIndexCount[SEARCH_INDEX_ALL]++;
965           g_searchIndexCount[SEARCH_INDEX_PAGES]++;
966         }
967       }
968     }
969   }
970   if (Doxygen::mainPage)
971   {
972     QCString title = Doxygen::mainPage->title();
973     if (!title.isEmpty())
974     {
975       uchar charCode = title.at(0);
976       uint letter = charCode<128 ? tolower(charCode) : charCode;
977       if (isId(letter))
978       {
979         g_searchIndexSymbols[SEARCH_INDEX_ALL][letter].append(Doxygen::mainPage);
980         g_searchIndexSymbols[SEARCH_INDEX_PAGES][letter].append(Doxygen::mainPage);
981         g_searchIndexCount[SEARCH_INDEX_ALL]++;
982         g_searchIndexCount[SEARCH_INDEX_PAGES]++;
983       }
984     }
985   }
986   
987   // sort all lists
988   int i,p;
989   for (i=0;i<NUM_SEARCH_INDICES;i++)
990   {
991     for (p=0;p<MEMBER_INDEX_ENTRIES;p++)
992     {
993       if (g_searchIndexSymbols[i][p].count()>0)
994       {
995         g_searchIndexSymbols[i][p].sort();
996       }
997     }
998   }
999
1000   // write index files
1001   QCString searchDirName = Config_getString("HTML_OUTPUT")+"/search";
1002
1003   for (i=0;i<NUM_SEARCH_INDICES;i++)
1004   {
1005     for (p=0;p<MEMBER_INDEX_ENTRIES;p++)
1006     {
1007       if (g_searchIndexSymbols[i][p].count()>0)
1008       {
1009         QCString baseName;
1010         baseName.sprintf("%s_%02x",g_searchIndexName[i],p);
1011
1012         QCString fileName = searchDirName + "/"+baseName+".html";
1013         QCString dataFileName = searchDirName + "/"+baseName+".js";
1014
1015         QFile outFile(fileName);
1016         QFile dataOutFile(dataFileName);
1017         if (outFile.open(IO_WriteOnly) && dataOutFile.open(IO_WriteOnly))
1018         {
1019           {
1020             FTextStream t(&outFile);
1021
1022             t << "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\""
1023               " \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">" << endl;
1024             t << "<html><head><title></title>" << endl;
1025             t << "<meta http-equiv=\"Content-Type\" content=\"text/xhtml;charset=UTF-8\"/>" << endl;
1026             t << "<link rel=\"stylesheet\" type=\"text/css\" href=\"search.css\"/>" << endl;
1027             t << "<script type=\"text/javascript\" src=\"" << baseName << ".js\"></script>" << endl;
1028             t << "<script type=\"text/javascript\" src=\"search.js\"></script>" << endl;
1029             t << "</head>" << endl;
1030             t << "<body class=\"SRPage\">" << endl;
1031             t << "<div id=\"SRIndex\">" << endl;
1032             t << "<div class=\"SRStatus\" id=\"Loading\">" << theTranslator->trLoading() << "</div>" << endl;
1033             t << "<div id=\"SRResults\"></div>" << endl; // here the results will be inserted
1034             t << "<script type=\"text/javascript\"><!--" << endl;
1035             t << "createResults();" << endl; // this function will insert the results
1036             t << "--></script>" << endl;
1037             t << "<div class=\"SRStatus\" id=\"Searching\">" 
1038               << theTranslator->trSearching() << "</div>" << endl;
1039             t << "<div class=\"SRStatus\" id=\"NoMatches\">"
1040               << theTranslator->trNoMatches() << "</div>" << endl;
1041
1042             t << "<script type=\"text/javascript\"><!--" << endl;
1043             t << "document.getElementById(\"Loading\").style.display=\"none\";" << endl;
1044             t << "document.getElementById(\"NoMatches\").style.display=\"none\";" << endl;
1045             t << "var searchResults = new SearchResults(\"searchResults\");" << endl;
1046             t << "searchResults.Search();" << endl;
1047             t << "--></script>" << endl;
1048             t << "</div>" << endl; // SRIndex
1049             t << "</body>" << endl;
1050             t << "</html>" << endl;
1051           }
1052           FTextStream ti(&dataOutFile);
1053
1054           ti << "var searchData=" << endl;
1055           // format
1056           // searchData[] = array of items
1057           // searchData[x][0] = id
1058           // searchData[x][1] = [ name + child1 + child2 + .. ]
1059           // searchData[x][1][0] = name as shown
1060           // searchData[x][1][y+1] = info for child y
1061           // searchData[x][1][y+1][0] = url
1062           // searchData[x][1][y+1][1] = 1 => target="_parent"
1063           // searchData[x][1][y+1][2] = scope
1064
1065           ti << "[" << endl;
1066           bool firstEntry=TRUE;
1067
1068           SDict<QList<Definition> >::Iterator li(g_searchIndexSymbols[i][p]);
1069           QList<Definition> *dl;
1070           int itemCount=0;
1071           for (li.toFirst();(dl=li.current());++li)
1072           {
1073             Definition *d = dl->first();
1074             QCString id = d->localName();
1075
1076             if (!firstEntry)
1077             {
1078               ti << "," << endl;
1079             }
1080             firstEntry=FALSE;
1081
1082             QCString dispName = d->localName();
1083             if (d->definitionType()==Definition::TypeGroup)
1084             {
1085               dispName = ((GroupDef*)d)->groupTitle();
1086             }
1087             else if (d->definitionType()==Definition::TypePage)
1088             {
1089               dispName = ((PageDef*)d)->title();
1090             }
1091             ti << "  ['" << searchId(dispName) << "',['" 
1092                << convertToXML(dispName) << "',[";
1093
1094             if (dl->count()==1) // item with a unique name
1095             {
1096               MemberDef  *md   = 0;
1097               bool isMemberDef = d->definitionType()==Definition::TypeMember;
1098               if (isMemberDef) md = (MemberDef*)d;
1099               QCString anchor = d->anchor();
1100
1101               ti << "'" << externalRef("../",d->getReference(),TRUE)
1102                  << d->getOutputFileBase() << Doxygen::htmlFileExtension;
1103               if (!anchor.isEmpty())
1104               {
1105                 ti << "#" << anchor;
1106               }
1107               ti << "',";
1108
1109               static bool extLinksInWindow = Config_getBool("EXT_LINKS_IN_WINDOW");
1110               if (!extLinksInWindow || d->getReference().isEmpty())
1111               {
1112                 ti << "1,";
1113               }
1114               else
1115               {
1116                 ti << "0,";
1117               }
1118
1119               if (d->getOuterScope()!=Doxygen::globalScope)
1120               {
1121                 ti << "'" << convertToXML(d->getOuterScope()->name()) << "'";
1122               }
1123               else if (md)
1124               {
1125                 FileDef *fd = md->getBodyDef();
1126                 if (fd==0) fd = md->getFileDef();
1127                 if (fd)
1128                 {
1129                   ti << "'" << convertToXML(fd->localName()) << "'";
1130                 }
1131               }
1132               else
1133               {
1134                 ti << "''";
1135               }
1136               ti << "]]";
1137             }
1138             else // multiple items with the same name
1139             {
1140               QListIterator<Definition> di(*dl);
1141               bool overloadedFunction = FALSE;
1142               Definition *prevScope = 0;
1143               int childCount=0;
1144               for (di.toFirst();(d=di.current());)
1145               {
1146                 ++di;
1147                 Definition *scope     = d->getOuterScope();
1148                 Definition *next      = di.current();
1149                 Definition *nextScope = 0;
1150                 MemberDef  *md        = 0;
1151                 bool isMemberDef = d->definitionType()==Definition::TypeMember;
1152                 if (isMemberDef) md = (MemberDef*)d;
1153                 if (next) nextScope = next->getOuterScope();
1154                 QCString anchor = d->anchor();
1155
1156                 if (childCount>0)
1157                 {
1158                   ti << "],[";
1159                 }
1160                 ti << "'" << externalRef("../",d->getReference(),TRUE)
1161                    << d->getOutputFileBase() << Doxygen::htmlFileExtension;
1162                 if (!anchor.isEmpty())
1163                 {
1164                   ti << "#" << anchor;
1165                 }
1166                 ti << "',";
1167
1168                 static bool extLinksInWindow = Config_getBool("EXT_LINKS_IN_WINDOW");
1169                 if (!extLinksInWindow || d->getReference().isEmpty())
1170                 {
1171                   ti << "1,";
1172                 }
1173                 else
1174                 {
1175                   ti << "0,";
1176                 }
1177                 bool found=FALSE;
1178                 overloadedFunction = ((prevScope!=0 && scope==prevScope) ||
1179                                       (scope && scope==nextScope)
1180                                      ) && md && 
1181                                      (md->isFunction() || md->isSlot());
1182                 QCString prefix;
1183                 if (md) prefix=convertToXML(md->localName());
1184                 if (overloadedFunction) // overloaded member function
1185                 {
1186                   prefix+=convertToXML(md->argsString()); 
1187                   // show argument list to disambiguate overloaded functions
1188                 }
1189                 else if (md) // unique member function
1190                 {
1191                   prefix+="()"; // only to show it is a function
1192                 }
1193                 QCString name;
1194                 if (d->definitionType()==Definition::TypeClass)
1195                 {
1196                   name = convertToXML(((ClassDef*)d)->displayName());
1197                   found = TRUE;
1198                 }
1199                 else if (d->definitionType()==Definition::TypeNamespace)
1200                 {
1201                   name = convertToXML(((NamespaceDef*)d)->displayName());
1202                   found = TRUE;
1203                 }
1204                 else if (scope==0 || scope==Doxygen::globalScope) // in global scope
1205                 {
1206                   if (md)
1207                   {
1208                     FileDef *fd = md->getBodyDef();
1209                     if (fd==0) fd = md->getFileDef();
1210                     if (fd)
1211                     {
1212                       if (!prefix.isEmpty()) prefix+=":&#160;";
1213                       name = prefix + convertToXML(fd->localName());
1214                       found = TRUE;
1215                     }
1216                   }
1217                 }
1218                 else if (md && (md->getClassDef() || md->getNamespaceDef())) 
1219                   // member in class or namespace scope
1220                 {
1221                   SrcLangExt lang = md->getLanguage();
1222                   name = convertToXML(d->getOuterScope()->qualifiedName()) 
1223                        + getLanguageSpecificSeparator(lang) + prefix;
1224                   found = TRUE;
1225                 }
1226                 else if (scope) // some thing else? -> show scope
1227                 {
1228                   name = prefix + convertToXML(scope->name());
1229                   found = TRUE;
1230                 }
1231                 if (!found) // fallback
1232                 {
1233                   name = prefix + "("+theTranslator->trGlobalNamespace()+")";
1234                 }
1235
1236                 ti << "'" << name << "'";
1237
1238                 prevScope = scope;
1239                 childCount++;
1240               }
1241
1242               ti << "]]";
1243             }
1244             ti << "]";
1245             itemCount++;
1246           }
1247           if (!firstEntry)
1248           {
1249             ti << endl;
1250           }
1251
1252           ti << "];" << endl;
1253
1254         }
1255         else
1256         {
1257           err("Failed to open file '%s' for writing...\n",fileName.data());
1258         }
1259       }
1260     }
1261   }
1262
1263   {
1264     QFile f(searchDirName+"/search.js");
1265     if (f.open(IO_WriteOnly))
1266     {
1267       FTextStream t(&f);
1268       t << "// Search script generated by doxygen" << endl;
1269       t << "// Copyright (C) 2009 by Dimitri van Heesch." << endl << endl;
1270       t << "// The code in this file is loosly based on main.js, part of Natural Docs," << endl;
1271       t << "// which is Copyright (C) 2003-2008 Greg Valure" << endl;
1272       t << "// Natural Docs is licensed under the GPL." << endl << endl;
1273       t << "var indexSectionsWithContent =" << endl;
1274       t << "{" << endl;
1275       bool first=TRUE;
1276       int j=0;
1277       for (i=0;i<NUM_SEARCH_INDICES;i++)
1278       {
1279         if (g_searchIndexCount[i]>0)
1280         {
1281           if (!first) t << "," << endl;
1282           t << "  " << j << ": \"";
1283           for (p=0;p<MEMBER_INDEX_ENTRIES;p++)
1284           {
1285             t << (g_searchIndexSymbols[i][p].count()>0 ? "1" : "0");
1286           }
1287           t << "\"";
1288           first=FALSE;
1289           j++;
1290         }
1291       }
1292       if (!first) t << "\n";
1293       t << "};" << endl << endl;
1294       t << "var indexSectionNames =" << endl;
1295       t << "{" << endl;
1296       first=TRUE;
1297       j=0;
1298       for (i=0;i<NUM_SEARCH_INDICES;i++)
1299       {
1300         if (g_searchIndexCount[i]>0)
1301         {
1302           if (!first) t << "," << endl;
1303           t << "  " << j << ": \"" << g_searchIndexName[i] << "\"";
1304           first=FALSE;
1305           j++;
1306         }
1307       }
1308       if (!first) t << "\n";
1309       t << "};" << endl << endl;
1310       t << search_script;
1311     }
1312   }
1313   {
1314     QFile f(searchDirName+"/nomatches.html");
1315     if (f.open(IO_WriteOnly))
1316     {
1317       FTextStream t(&f);
1318       t << "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" "
1319            "\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">" << endl;
1320       t << "<html><head><title></title>" << endl;
1321       t << "<meta http-equiv=\"Content-Type\" content=\"text/xhtml;charset=UTF-8\"/>" << endl;
1322       t << "<link rel=\"stylesheet\" type=\"text/css\" href=\"search.css\"/>" << endl;
1323       t << "<script type=\"text/javascript\" src=\"search.js\"></script>" << endl;
1324       t << "</head>" << endl;
1325       t << "<body class=\"SRPage\">" << endl;
1326       t << "<div id=\"SRIndex\">" << endl;
1327       t << "<div class=\"SRStatus\" id=\"NoMatches\">"
1328         << theTranslator->trNoMatches() << "</div>" << endl;
1329       t << "</div>" << endl;
1330       t << "</body>" << endl;
1331       t << "</html>" << endl;
1332     }
1333   }
1334   Doxygen::indexList.addStyleSheetFile("search/search.js");
1335 }
1336
1337 void writeSearchCategories(FTextStream &t)
1338 {
1339   static SearchIndexCategoryMapping map;
1340   int i,j=0;
1341   for (i=0;i<NUM_SEARCH_INDICES;i++)
1342   {
1343     if (g_searchIndexCount[i]>0)
1344     {
1345       t << "<a class=\"SelectItem\" href=\"javascript:void(0)\" "
1346         << "onclick=\"searchBox.OnSelectItem(" << j << ")\">"
1347         << "<span class=\"SelectionMark\">&#160;</span>"
1348         << convertToXML(map.categoryLabel[i])
1349         << "</a>";
1350       j++;
1351     }
1352   }
1353 }
1354
1355 //---------------------------------------------------------------------------------------------
1356
1357 void initSearchIndexer()
1358 {
1359   static bool searchEngine = Config_getBool("SEARCHENGINE");
1360   static bool serverBasedSearch = Config_getBool("SERVER_BASED_SEARCH");
1361   if (searchEngine && serverBasedSearch)
1362   {
1363     //Doxygen::searchIndex = new SearchIndexExternal;
1364     Doxygen::searchIndex = new SearchIndex;
1365   }
1366   else // no search engine or pure javascript based search function
1367   {
1368     Doxygen::searchIndex = 0;
1369   }
1370 }
1371
1372 void finializeSearchIndexer()
1373 {
1374   delete Doxygen::searchIndex;
1375 }
1376
1377