1 /******************************************************************************
5 * Copyright (C) 1997-2015 by Dimitri van Heesch.
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.
13 * Documents produced by Doxygen are derivative works derived from the
14 * input used in their production; they are not affected by this license.
24 #include "searchindex.h"
34 #include "classlist.h"
36 #include "memberdef.h"
38 #include "membername.h"
39 #include "resourcemgr.h"
41 // file format: (all multi-byte values are stored in big endian format)
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
52 const int numIndexEntries = 256*256;
54 //--------------------------------------------------------------------
56 IndexWord::IndexWord(const char *word) : m_word(word), m_urls(17)
58 m_urls.setAutoDelete(TRUE);
59 //printf("IndexWord::IndexWord(%s)\n",word);
62 void IndexWord::addUrlIndex(int idx,bool hiPriority)
64 //printf("IndexWord::addUrlIndex(%d,%d)\n",idx,hiPriority);
65 URLInfo *ui = m_urls.find(idx);
68 //printf("URLInfo::URLInfo(%d)\n",idx);
69 ui=new URLInfo(idx,0);
70 m_urls.insert(idx,ui);
73 if (hiPriority) ui->freq|=1; // mark as high priority document
76 //--------------------------------------------------------------------
78 SearchIndex::SearchIndex() : SearchIndexIntf(Internal),
79 m_words(328829), m_index(numIndexEntries), m_url2IdMap(10007), m_urls(10007), m_urlIndex(-1)
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>);
89 void SearchIndex::setCurrentDoc(Definition *ctx,const char *anchor,bool isSourceFile)
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)
102 MemberDef *md = (MemberDef *)ctx;
103 name.prepend((md->getLanguage()==SrcLangExt_Fortran ?
104 theTranslator->trSubprogram(TRUE,TRUE) :
105 theTranslator->trMember(TRUE,TRUE))+" ");
107 else // compound type
109 SrcLangExt lang = ctx->getLanguage();
110 QCString sep = getLanguageSpecificSeparator(lang);
113 name = substitute(name,"::",sep);
115 switch (ctx->definitionType())
117 case Definition::TypePage:
119 PageDef *pd = (PageDef *)ctx;
120 if (!pd->title().isEmpty())
122 name = theTranslator->trPage(TRUE,TRUE)+" "+pd->title();
126 name = theTranslator->trPage(TRUE,TRUE)+" "+pd->name();
130 case Definition::TypeClass:
132 ClassDef *cd = (ClassDef *)ctx;
133 name.prepend(cd->compoundTypeString()+" ");
136 case Definition::TypeNamespace:
138 if (lang==SrcLangExt_Java || lang==SrcLangExt_CSharp)
140 name = theTranslator->trPackage(name);
142 else if (lang==SrcLangExt_Fortran)
144 name.prepend(theTranslator->trModule(TRUE,TRUE)+" ");
148 name.prepend(theTranslator->trNamespace(TRUE,TRUE)+" ");
152 case Definition::TypeGroup:
154 GroupDef *gd = (GroupDef *)ctx;
155 if (gd->groupTitle())
157 name = theTranslator->trGroup(TRUE,TRUE)+" "+gd->groupTitle();
161 name.prepend(theTranslator->trGroup(TRUE,TRUE)+" ");
170 int *pIndex = m_url2IdMap.find(baseUrl);
174 m_url2IdMap.insert(baseUrl,new int(m_urlIndex));
175 m_urls.insert(m_urlIndex,new URL(name,url));
179 m_urls.insert(*pIndex,new URL(name,url));
183 static int charsToIndex(const char *word)
185 if (word==0) return -1;
187 // Fast string hashing algorithm
188 //register ushort h=0;
189 //const char *k = word;
190 //ushort mask=0xfc00;
193 // h = (h&mask)^(h<<6)^(*k++);
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;
205 void SearchIndex::addWord(const char *word,bool hiPriority,bool recurse)
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];
214 int idx=charsToIndex(wStr);
215 //fprintf(stderr,"addWord(%s) at index %d\n",word,idx);
217 w = new IndexWord(wStr);
218 m_index[idx]->append(w);
219 m_words.insert(wStr,w);
221 w->addUrlIndex(m_urlIndex,hiPriority);
224 if (!recurse) // the first time we check if we can strip the prefix
226 i=getPrefixIndex(word);
229 addWord(word+i,hiPriority,TRUE);
233 if (!found) // no prefix stripped
235 if ((i=nextPart.match(word))>=1)
237 addWord(word+i+1,hiPriority,TRUE);
242 void SearchIndex::addWord(const char *word,bool hiPriority)
244 addWord(word,hiPriority,FALSE);
247 static void writeInt(QFile &f,int index)
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);
255 static void writeString(QFile &f,const char *s)
258 while (*p) f.putch(*p++);
262 void SearchIndex::write(const char *fileName)
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++)
271 QList<IndexWord> *wlist = m_index[i];
272 if (!wlist->isEmpty())
274 QListIterator<IndexWord> iwi(*wlist);
276 for (iwi.toFirst();(iw=iwi.current());++iwi)
278 int ws = iw->word().length()+1;
279 size+=ws+4; // word + url info list offset
281 size+=1; // zero list terminator
285 // second pass: compute the offsets in the index
286 int indexOffsets[numIndexEntries];
287 int offset=wordsOffset;
288 for (i=0;i<numIndexEntries;i++)
290 QList<IndexWord> *wlist = m_index[i];
291 if (!wlist->isEmpty())
293 indexOffsets[i]=offset;
294 QListIterator<IndexWord> iwi(*wlist);
296 for (iwi.toFirst();(iw=iwi.current());++iwi)
298 offset+= iw->word().length()+1;
299 offset+=4; // word + offset to url info array
301 offset+=1; // zero list terminator
309 size = (size+3)&~3; // round up to 4 byte boundary
310 padding = size - padding;
312 //int statsOffset = size;
314 int *wordStatOffsets = new int[m_words.count()];
318 // third pass: compute offset to stats info for each word
319 for (i=0;i<numIndexEntries;i++)
321 QList<IndexWord> *wlist = m_index[i];
322 if (!wlist->isEmpty())
324 QListIterator<IndexWord> iwi(*wlist);
326 for (iwi.toFirst();(iw=iwi.current());++iwi)
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
334 int *urlOffsets = new int[m_urls.count()];
335 //int urlsOffset = size;
336 QIntDictIterator<URL> udi(m_urls);
338 for (udi.toFirst();(url=udi.current());++udi)
340 urlOffsets[udi.currentKey()]=size;
341 size+=url->name.length()+1+
344 //printf("Total size %x bytes (word=%x stats=%x urls=%x)\n",size,wordsOffset,statsOffset,urlsOffset);
346 if (f.open(IO_WriteOnly))
349 f.putch('D'); f.putch('O'); f.putch('X'); f.putch('S');
351 for (i=0;i<numIndexEntries;i++)
353 writeInt(f,indexOffsets[i]);
357 for (i=0;i<numIndexEntries;i++)
359 QList<IndexWord> *wlist = m_index[i];
360 if (!wlist->isEmpty())
362 QListIterator<IndexWord> iwi(*wlist);
364 for (iwi.toFirst();(iw=iwi.current());++iwi)
366 writeString(f,iw->word());
367 writeInt(f,wordStatOffsets[count++]);
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++)
377 QList<IndexWord> *wlist = m_index[i];
378 if (!wlist->isEmpty())
380 QListIterator<IndexWord> iwi(*wlist);
382 for (iwi.toFirst();(iw=iwi.current());++iwi)
384 int numUrls = iw->urls().count();
386 QIntDictIterator<URLInfo> uli(iw->urls());
388 for (uli.toFirst();(ui=uli.current());++uli)
390 writeInt(f,urlOffsets[ui->urlIdx]);
391 writeInt(f,ui->freq);
397 QIntDictIterator<URL> udi(m_urls);
399 for (udi.toFirst();(url=udi.current());++udi)
401 writeString(f,url->name);
402 writeString(f,url->url);
407 delete[] wordStatOffsets;
411 //---------------------------------------------------------------------------
412 // the following part is for writing an external search index
414 struct SearchDocEntry
421 GrowBuf importantText;
425 struct SearchIndexExternal::Private
427 Private() : docEntries(12251) {}
428 SDict<SearchDocEntry> docEntries;
429 SearchDocEntry *current;
432 SearchIndexExternal::SearchIndexExternal() : SearchIndexIntf(External)
434 p = new SearchIndexExternal::Private;
435 p->docEntries.setAutoDelete(TRUE);
439 SearchIndexExternal::~SearchIndexExternal()
441 //printf("p->docEntries.count()=%d\n",p->docEntries.count());
445 static QCString definitionToName(Definition *ctx)
447 if (ctx && ctx->definitionType()==Definition::TypeMember)
449 MemberDef *md = (MemberDef*)ctx;
450 if (md->isFunction())
452 else if (md->isSlot())
454 else if (md->isSignal())
456 else if (md->isVariable())
458 else if (md->isTypedef())
460 else if (md->isEnumerate())
462 else if (md->isEnumValue())
464 else if (md->isProperty())
466 else if (md->isEvent())
468 else if (md->isRelated() || md->isForeign())
470 else if (md->isFriend())
472 else if (md->isDefine())
477 switch(ctx->definitionType())
479 case Definition::TypeClass:
480 return ((ClassDef*)ctx)->compoundTypeString();
481 case Definition::TypeFile:
483 case Definition::TypeNamespace:
485 case Definition::TypeGroup:
487 case Definition::TypePackage:
489 case Definition::TypePage:
491 case Definition::TypeDir:
500 void SearchIndexExternal::setCurrentDoc(Definition *ctx,const char *anchor,bool isSourceFile)
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;
508 p->current = p->docEntries.find(key);
509 //printf("setCurrentDoc(url=%s,isSourceFile=%d) current=%p\n",url.data(),isSourceFile,p->current);
512 SearchDocEntry *e = new SearchDocEntry;
513 e->type = isSourceFile ? QCString("source") : definitionToName(ctx);
514 e->name = ctx->qualifiedName();
515 if (ctx->definitionType()==Definition::TypeMember)
517 e->args = ((MemberDef*)ctx)->argsString();
522 p->docEntries.append(key,e);
523 //printf("searchIndexExt %s : %s\n",e->name.data(),e->url.data());
527 void SearchIndexExternal::addWord(const char *word,bool hiPriority)
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(' ');
533 //printf("addWord %s\n",word);
536 void SearchIndexExternal::write(const char *fileName)
539 if (f.open(IO_WriteOnly))
542 t << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" << endl;
543 t << "<add>" << endl;
544 SDict<SearchDocEntry>::Iterator it(p->docEntries);
546 for (it.toFirst();(doc=it.current());++it)
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())
555 t << " <field name=\"args\">" << convertToXML(doc->args) << "</field>" << endl;
557 if (!doc->extId.isEmpty())
559 t << " <field name=\"tag\">" << convertToXML(doc->extId) << "</field>" << endl;
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;
566 t << "</add>" << endl;
570 err("Failed to open file %s for writing!\n",fileName);
574 //---------------------------------------------------------------------------
575 // the following part is for the javascript based search engine
577 #include "memberdef.h"
578 #include "namespacedef.h"
580 #include "classdef.h"
582 #include "language.h"
586 static SearchIndexInfo g_searchIndexInfo[NUM_SEARCH_INDICES];
588 static void addMemberToSearchIndex(MemberDef *md)
590 static bool hideFriendCompounds = Config_getBool(HIDE_FRIEND_COMPOUNDS);
591 bool isLinkable = md->isLinkable();
598 ((cd=md->getClassDef()) && cd->isLinkable() && cd->templateMaster()==0) ||
599 ((gd=md->getGroupDef()) && gd->isLinkable())
603 QCString n = md->name();
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))
613 g_searchIndexInfo[SEARCH_INDEX_ALL].symbolList.append(letter,md);
615 if (md->isFunction() || md->isSlot() || md->isSignal())
617 g_searchIndexInfo[SEARCH_INDEX_FUNCTIONS].symbolList.append(letter,md);
619 else if (md->isVariable())
621 g_searchIndexInfo[SEARCH_INDEX_VARIABLES].symbolList.append(letter,md);
623 else if (md->isTypedef())
625 g_searchIndexInfo[SEARCH_INDEX_TYPEDEFS].symbolList.append(letter,md);
627 else if (md->isEnumerate())
629 g_searchIndexInfo[SEARCH_INDEX_ENUMS].symbolList.append(letter,md);
631 else if (md->isEnumValue())
633 g_searchIndexInfo[SEARCH_INDEX_ENUMVALUES].symbolList.append(letter,md);
635 else if (md->isProperty())
637 g_searchIndexInfo[SEARCH_INDEX_PROPERTIES].symbolList.append(letter,md);
639 else if (md->isEvent())
641 g_searchIndexInfo[SEARCH_INDEX_EVENTS].symbolList.append(letter,md);
643 else if (md->isRelated() || md->isForeign() ||
644 (md->isFriend() && !isFriendToHide))
646 g_searchIndexInfo[SEARCH_INDEX_RELATED].symbolList.append(letter,md);
650 else if (isLinkable &&
651 (((nd=md->getNamespaceDef()) && nd->isLinkable()) ||
652 ((fd=md->getFileDef()) && fd->isLinkable())
656 QCString n = md->name();
659 uint letter = getUtf8CodeToLower(n,0);
660 g_searchIndexInfo[SEARCH_INDEX_ALL].symbolList.append(letter,md);
662 if (md->isFunction())
664 g_searchIndexInfo[SEARCH_INDEX_FUNCTIONS].symbolList.append(letter,md);
666 else if (md->isVariable())
668 g_searchIndexInfo[SEARCH_INDEX_VARIABLES].symbolList.append(letter,md);
670 else if (md->isTypedef())
672 g_searchIndexInfo[SEARCH_INDEX_TYPEDEFS].symbolList.append(letter,md);
674 else if (md->isEnumerate())
676 g_searchIndexInfo[SEARCH_INDEX_ENUMS].symbolList.append(letter,md);
678 else if (md->isEnumValue())
680 g_searchIndexInfo[SEARCH_INDEX_ENUMVALUES].symbolList.append(letter,md);
682 else if (md->isDefine())
684 g_searchIndexInfo[SEARCH_INDEX_DEFINES].symbolList.append(letter,md);
690 // see also function convertToId() in search.js, which should match in
692 static QCString searchId(const QCString &s)
697 for (i=0;i<s.length();i++)
700 if (c>0x7f || c<0) // part of multibyte character
704 else if (isalnum(c)) // simply alpha numerical character
706 result+=(char)tolower(c);
708 else // other 'unprintable' characters
711 sprintf(val,"_%02x",(uchar)c);
718 void createJavascriptSearchIndex()
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";
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);
754 // add symbols to letter -> symbol list map
757 ClassSDict::Iterator cli(*Doxygen::classSDict);
759 for (;(cd=cli.current());++cli)
761 uint letter = getUtf8CodeToLower(cd->localName(),0);
762 if (cd->isLinkable() && isId(letter))
764 g_searchIndexInfo[SEARCH_INDEX_ALL].symbolList.append(letter,cd);
765 g_searchIndexInfo[SEARCH_INDEX_CLASSES].symbolList.append(letter,cd);
770 NamespaceSDict::Iterator nli(*Doxygen::namespaceSDict);
772 for (;(nd=nli.current());++nli)
774 uint letter = getUtf8CodeToLower(nd->name(),0);
775 if (nd->isLinkable() && isId(letter))
777 g_searchIndexInfo[SEARCH_INDEX_ALL].symbolList.append(letter,nd);
778 g_searchIndexInfo[SEARCH_INDEX_NAMESPACES].symbolList.append(letter,nd);
783 FileNameListIterator fnli(*Doxygen::inputNameList);
785 for (;(fn=fnli.current());++fnli)
787 FileNameIterator fni(*fn);
789 for (;(fd=fni.current());++fni)
791 uint letter = getUtf8CodeToLower(fd->name(),0);
792 if (fd->isLinkable() && isId(letter))
794 g_searchIndexInfo[SEARCH_INDEX_ALL].symbolList.append(letter,fd);
795 g_searchIndexInfo[SEARCH_INDEX_FILES].symbolList.append(letter,fd);
800 // index class members
802 MemberNameSDict::Iterator mnli(*Doxygen::memberNameSDict);
804 // for each member name
805 for (mnli.toFirst();(mn=mnli.current());++mnli)
808 MemberNameIterator mni(*mn);
809 // for each member definition
810 for (mni.toFirst();(md=mni.current());++mni)
812 addMemberToSearchIndex(md);
817 // index file/namespace members
819 MemberNameSDict::Iterator fnli(*Doxygen::functionNameSDict);
821 // for each member name
822 for (fnli.toFirst();(mn=fnli.current());++fnli)
825 MemberNameIterator mni(*mn);
826 // for each member definition
827 for (mni.toFirst();(md=mni.current());++mni)
829 addMemberToSearchIndex(md);
835 GroupSDict::Iterator gli(*Doxygen::groupSDict);
837 for (gli.toFirst();(gd=gli.current());++gli)
839 if (gd->isLinkable())
841 QCString title = gd->groupTitle();
842 if (!title.isEmpty()) // TODO: able searching for all word in the title
844 uchar charCode = title.at(0);
845 uint letter = charCode<128 ? tolower(charCode) : charCode;
848 g_searchIndexInfo[SEARCH_INDEX_ALL].symbolList.append(letter,gd);
849 g_searchIndexInfo[SEARCH_INDEX_GROUPS].symbolList.append(letter,gd);
856 PageSDict::Iterator pdi(*Doxygen::pageSDict);
858 for (pdi.toFirst();(pd=pdi.current());++pdi)
860 if (pd->isLinkable())
862 QCString title = pd->title();
863 if (!title.isEmpty())
865 uchar charCode = title.at(0);
866 uint letter = charCode<128 ? tolower(charCode) : charCode;
869 g_searchIndexInfo[SEARCH_INDEX_ALL].symbolList.append(letter,pd);
870 g_searchIndexInfo[SEARCH_INDEX_PAGES].symbolList.append(letter,pd);
875 if (Doxygen::mainPage)
877 QCString title = Doxygen::mainPage->title();
878 if (!title.isEmpty())
880 uchar charCode = title.at(0);
881 uint letter = charCode<128 ? tolower(charCode) : charCode;
884 g_searchIndexInfo[SEARCH_INDEX_ALL].symbolList.append(letter,Doxygen::mainPage);
885 g_searchIndexInfo[SEARCH_INDEX_PAGES].symbolList.append(letter,Doxygen::mainPage);
892 for (i=0;i<NUM_SEARCH_INDICES;i++)
894 SIntDict<SearchIndexList>::Iterator it(g_searchIndexInfo[i].symbolList);
896 for (it.toFirst();(sl=it.current());++it)
903 void writeJavascriptSearchIndex()
907 QCString searchDirName = Config_getString(HTML_OUTPUT)+"/search";
909 for (i=0;i<NUM_SEARCH_INDICES;i++) // for each index
911 SIntDict<SearchIndexList>::Iterator it(g_searchIndexInfo[i].symbolList);
914 for (it.toFirst();(sl=it.current());++it,++p) // for each letter
917 baseName.sprintf("%s_%x",g_searchIndexInfo[i].name.data(),p);
919 QCString fileName = searchDirName + "/"+baseName+".html";
920 QCString dataFileName = searchDirName + "/"+baseName+".js";
922 QFile outFile(fileName);
923 QFile dataOutFile(dataFileName);
924 if (outFile.open(IO_WriteOnly) && dataOutFile.open(IO_WriteOnly))
927 FTextStream t(&outFile);
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&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;
952 t << "<script type=\"text/javascript\"><!--" << endl;
953 t << "/* @license magnet:?xt=urn:btih:cf05388f2679ee054f2beb29a391d25f4e673ac3&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;
964 FTextStream ti(&dataOutFile);
966 ti << "var searchData=" << endl;
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
978 bool firstEntry=TRUE;
980 SDict<SearchDefinitionList>::Iterator li(*sl);
981 SearchDefinitionList *dl;
983 for (li.toFirst();(dl=li.current());++li)
985 Definition *d = dl->getFirst();
993 ti << " ['" << dl->id() << "',['" << convertToXML(dl->name()) << "',[";
995 if (dl->count()==1) // item with a unique name
998 bool isMemberDef = d->definitionType()==Definition::TypeMember;
999 if (isMemberDef) md = (MemberDef*)d;
1000 QCString anchor = d->anchor();
1002 ti << "'" << externalRef("../",d->getReference(),TRUE)
1003 << d->getOutputFileBase() << Doxygen::htmlFileExtension;
1004 if (!anchor.isEmpty())
1006 ti << "#" << anchor;
1010 static bool extLinksInWindow = Config_getBool(EXT_LINKS_IN_WINDOW);
1011 if (!extLinksInWindow || d->getReference().isEmpty())
1020 if (d->getOuterScope()!=Doxygen::globalScope)
1022 ti << "'" << convertToXML(d->getOuterScope()->name()) << "'";
1026 FileDef *fd = md->getBodyDef();
1027 if (fd==0) fd = md->getFileDef();
1030 ti << "'" << convertToXML(fd->localName()) << "'";
1039 else // multiple items with the same name
1041 QListIterator<Definition> di(*dl);
1042 bool overloadedFunction = FALSE;
1043 Definition *prevScope = 0;
1045 for (di.toFirst();(d=di.current());)
1048 Definition *scope = d->getOuterScope();
1049 Definition *next = di.current();
1050 Definition *nextScope = 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();
1061 ti << "'" << externalRef("../",d->getReference(),TRUE)
1062 << d->getOutputFileBase() << Doxygen::htmlFileExtension;
1063 if (!anchor.isEmpty())
1065 ti << "#" << anchor;
1069 static bool extLinksInWindow = Config_getBool(EXT_LINKS_IN_WINDOW);
1070 if (!extLinksInWindow || d->getReference().isEmpty())
1079 overloadedFunction = ((prevScope!=0 && scope==prevScope) ||
1080 (scope && scope==nextScope)
1082 (md->isFunction() || md->isSlot());
1084 if (md) prefix=convertToXML(md->localName());
1085 if (overloadedFunction) // overloaded member function
1087 prefix+=convertToXML(md->argsString());
1088 // show argument list to disambiguate overloaded functions
1090 else if (md) // unique member function
1092 prefix+="()"; // only to show it is a function
1095 if (d->definitionType()==Definition::TypeClass)
1097 name = convertToXML(((ClassDef*)d)->displayName());
1100 else if (d->definitionType()==Definition::TypeNamespace)
1102 name = convertToXML(((NamespaceDef*)d)->displayName());
1105 else if (scope==0 || scope==Doxygen::globalScope) // in global scope
1109 FileDef *fd = md->getBodyDef();
1110 if (fd==0) fd = md->getFileDef();
1113 if (!prefix.isEmpty()) prefix+=": ";
1114 name = prefix + convertToXML(fd->localName());
1119 else if (md && (md->getClassDef() || md->getNamespaceDef()))
1120 // member in class or namespace scope
1122 SrcLangExt lang = md->getLanguage();
1123 name = convertToXML(d->getOuterScope()->qualifiedName())
1124 + getLanguageSpecificSeparator(lang) + prefix;
1127 else if (scope) // some thing else? -> show scope
1129 name = prefix + convertToXML(scope->name());
1132 if (!found) // fallback
1134 name = prefix + "("+theTranslator->trGlobalNamespace()+")";
1137 ti << "'" << name << "'";
1158 err("Failed to open file '%s' for writing...\n",fileName.data());
1164 QFile f(searchDirName+"/searchdata.js");
1165 if (f.open(IO_WriteOnly))
1168 t << "var indexSectionsWithContent =" << endl;
1172 for (i=0;i<NUM_SEARCH_INDICES;i++)
1174 if (g_searchIndexInfo[i].symbolList.count()>0)
1176 if (!first) t << "," << endl;
1177 t << " " << j << ": \"";
1179 SIntDict<SearchIndexList>::Iterator it(g_searchIndexInfo[i].symbolList);
1180 SearchIndexList *sl;
1181 for (it.toFirst();(sl=it.current());++it) // for each letter
1183 t << QString( QChar( sl->letter() ) ).utf8();
1190 if (!first) t << "\n";
1191 t << "};" << endl << endl;
1192 t << "var indexSectionNames =" << endl;
1196 for (i=0;i<NUM_SEARCH_INDICES;i++)
1198 if (g_searchIndexInfo[i].symbolList.count()>0)
1200 if (!first) t << "," << endl;
1201 t << " " << j << ": \"" << g_searchIndexInfo[i].name << "\"";
1206 if (!first) t << "\n";
1207 t << "};" << endl << endl;
1208 t << "var indexSectionLabels =" << endl;
1212 for (i=0;i<NUM_SEARCH_INDICES;i++)
1214 if (g_searchIndexInfo[i].symbolList.count()>0)
1216 if (!first) t << "," << endl;
1217 t << " " << j << ": \"" << convertToXML(g_searchIndexInfo[i].text) << "\"";
1222 if (!first) t << "\n";
1223 t << "};" << endl << endl;
1225 ResourceMgr::instance().copyResource("search.js",searchDirName);
1228 QFile f(searchDirName+"/nomatches.html");
1229 if (f.open(IO_WriteOnly))
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;
1248 Doxygen::indexList->addStyleSheetFile("search/search.js");
1251 const SearchIndexInfo *getSearchIndices()
1253 return g_searchIndexInfo;
1256 //---------------------------------------------------------------------------------------------
1258 SearchIndexList::SearchIndexList(uint letter)
1259 : SDict< SearchDefinitionList >(17,FALSE), m_letter(letter)
1261 setAutoDelete(TRUE);
1264 SearchIndexList::~SearchIndexList()
1268 void SearchIndexList::append(Definition *d)
1270 QCString dispName = d->localName();
1271 SearchDefinitionList *l = find(dispName);
1274 if (d->definitionType()==Definition::TypeGroup)
1276 dispName = ((GroupDef*)d)->groupTitle();
1278 else if (d->definitionType()==Definition::TypePage)
1280 dispName = ((PageDef*)d)->title();
1282 l=new SearchDefinitionList(searchId(dispName),dispName);
1283 SDict< SearchDefinitionList >::append(dispName,l);
1288 uint SearchIndexList::letter() const
1293 int SearchIndexList::compareValues(const SearchDefinitionList *md1, const SearchDefinitionList *md2) const
1295 QCString n1 = md1->getFirst()->localName();
1296 QCString n2 = md2->getFirst()->localName();
1297 return qstricmp(n1.data(),n2.data());
1300 //---------------------------------------------------------------------------------------------
1302 void initSearchIndexer()
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)
1309 if (externalSearch) // external tools produce search index and engine
1311 Doxygen::searchIndex = new SearchIndexExternal;
1313 else // doxygen produces search index and engine
1315 Doxygen::searchIndex = new SearchIndex;
1318 else // no search engine or pure javascript based search function
1320 Doxygen::searchIndex = 0;
1324 void finializeSearchIndexer()
1326 delete Doxygen::searchIndex;