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