Doxygen
Loading...
Searching...
No Matches
searchindex.cpp
Go to the documentation of this file.
1/******************************************************************************
2 *
3 * Copyright (C) 1997-2020 by Dimitri van Heesch.
4 *
5 * Permission to use, copy, modify, and distribute this software and its
6 * documentation under the terms of the GNU General Public License is hereby
7 * granted. No representations are made about the suitability of this software
8 * for any purpose. It is provided "as is" without express or implied warranty.
9 * See the GNU General Public License for more details.
10 *
11 * Documents produced by Doxygen are derivative works derived from the
12 * input used in their production; they are not affected by this license.
13 *
14 */
15
16// own header
17#include "searchindex.h"
18
19// standard includes
20#include <cctype>
21#include <mutex>
22
23// other includes
24#include "config.h"
25#include "doxygen.h"
26#include "filedef.h"
27#include "groupdef.h"
28#include "language.h"
29#include "message.h"
30#include "pagedef.h"
31#include "portable.h"
32#include "util.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
46const size_t numIndexEntries = 256*256;
47
48static std::mutex g_searchIndexMutex;
49
50//--------------------------------------------------------------------
51
52void SearchIndex::IndexWord::addUrlIndex(int idx,bool hiPriority)
53{
54 //printf("IndexWord::addUrlIndex(%d,%d)\n",idx,hiPriority);
55 auto it = m_urls.find(idx);
56 if (it==m_urls.end())
57 {
58 //printf("URLInfo::URLInfo(%d)\n",idx);
59 it = m_urls.emplace(idx,URLInfo(idx,0)).first;
60 }
61 it->second.freq+=2;
62 if (hiPriority) it->second.freq|=1; // mark as high priority document
63}
64
65//--------------------------------------------------------------------
66
71
72void SearchIndex::setCurrentDoc(const Definition *ctx,const DString &anchor,bool isSourceFile)
73{
74 if (ctx==nullptr) return;
75 std::lock_guard<std::mutex> lock(g_searchIndexMutex);
76 ASSERT(!isSourceFile || ctx->definitionType()==Definition::TypeFile);
77 //printf("SearchIndex::setCurrentDoc(%s,%s,%s)\n",name,baseName,anchor);
78 DString url=isSourceFile ? (toFileDef(ctx))->getSourceFileBase() : ctx->getOutputFileBase();
79 url+=Config_getString(HTML_FILE_EXTENSION);
80 DString baseUrl = url;
81 if (!anchor.empty()) url+=DString("#")+anchor;
82 if (!isSourceFile) baseUrl=url;
83 DString name=ctx->qualifiedName();
85 {
86 const MemberDef *md = toMemberDef(ctx);
87 name.prepend((md->getLanguage()==SrcLangExt::Fortran ?
88 theTranslator->trSubprogram(true,true) :
89 theTranslator->trMember(true,true))+" ");
90 }
91 else // compound type
92 {
93 SrcLangExt lang = ctx->getLanguage();
95 if (sep!="::")
96 {
97 name = substitute(name,"::",sep);
98 }
99 switch (ctx->definitionType())
100 {
102 {
103 const PageDef *pd = toPageDef(ctx);
104 if (pd->hasTitle())
105 {
106 name = theTranslator->trPage(true,true)+" "+pd->title();
107 }
108 else
109 {
110 name = theTranslator->trPage(true,true)+" "+pd->name();
111 }
112 }
113 break;
115 {
116 const ClassDef *cd = toClassDef(ctx);
117 name.prepend(cd->compoundTypeString()+" ");
118 }
119 break;
121 {
122 if (lang==SrcLangExt::Java || lang==SrcLangExt::CSharp)
123 {
124 name = theTranslator->trPackage(name);
125 }
126 else if (lang==SrcLangExt::Fortran)
127 {
128 name.prepend(theTranslator->trModule(true,true)+" ");
129 }
130 else
131 {
132 name.prepend(theTranslator->trNamespace(true,true)+" ");
133 }
134 }
135 break;
137 {
138 const GroupDef *gd = toGroupDef(ctx);
139 if (!gd->groupTitle().empty())
140 {
141 name = theTranslator->trGroup(true,true)+" "+gd->groupTitle();
142 }
143 else
144 {
145 name.prepend(theTranslator->trGroup(true,true)+" ");
146 }
147 }
148 break;
150 {
151 name.prepend(theTranslator->trModule(true,true)+" ");
152 }
153 break;
154 default:
155 break;
156 }
157 }
158
159 auto it = m_url2IdMap.find(baseUrl.str());
160 if (it == m_url2IdMap.end()) // new entry
161 {
163 m_url2IdMap.emplace(baseUrl.str(),m_urlIndex);
164 m_urls.emplace(m_urlIndex,URL(name,url));
165 }
166 else // existing entry
167 {
168 m_urlIndex=it->second;
169 m_urls.emplace(it->second,URL(name,url));
170 }
171}
172
173static int charsToIndex(const DString &word)
174{
175 if (word.length()<2) return -1;
176
177 // Fast string hashing algorithm
178 //register uint16_t h=0;
179 //const char *k = word;
180 //uint16_t mask=0xfc00;
181 //while ( *k )
182 //{
183 // h = (h&mask)^(h<<6)^(*k++);
184 //}
185 //return h;
186
187 // Simple hashing that allows for substring searching
188 uint32_t c1=static_cast<uint8_t>(word[0]);
189 uint32_t c2=static_cast<uint8_t>(word[1]);
190 return c1*256+c2;
191}
192
193void SearchIndex::addWordRec(const DString &word,bool hiPriority,bool recurse)
194{
195 if (word.empty()) return;
196 DString wStr = DString(word).lower();
197 //printf("SearchIndex::addWord(%s,%d) wStr=%s\n",word,hiPriority,qPrint(wStr));
198 int idx=charsToIndex(wStr);
199 if (idx<0 || idx>=static_cast<int>(m_index.size())) return;
200 auto it = m_words.find(wStr.str());
201 if (it==m_words.end())
202 {
203 //fprintf(stderr,"addWord(%s) at index %d\n",word,idx);
204 m_index[idx].emplace_back(wStr);
205 it = m_words.emplace( wStr.str(), static_cast<int>(m_index[idx].size())-1 ).first;
206 }
207 m_index[idx][it->second].addUrlIndex(m_urlIndex,hiPriority);
208 bool found=false;
209 if (!recurse) // the first time we check if we can strip the prefix
210 {
211 int i=getPrefixIndex(word);
212 if (i>0)
213 {
214 addWordRec(word.data()+i,hiPriority,true);
215 found=true;
216 }
217 }
218 if (!found) // no prefix stripped
219 {
220 int i=0;
221 while (word[i]!=0 &&
222 !((word[i]=='_' || word[i]==':' || (word[i]>='a' && word[i]<='z')) && // [_a-z:]
223 (word[i+1]>='A' && word[i+1]<='Z'))) // [A-Z]
224 {
225 i++;
226 }
227 if (word[i]!=0 && i>=1)
228 {
229 addWordRec(word.data()+i+1,hiPriority,true);
230 }
231 }
232}
233
234void SearchIndex::addWord(const DString &word,bool hiPriority)
235{
236 std::lock_guard<std::mutex> lock(g_searchIndexMutex);
237 addWordRec(word,hiPriority,false);
238}
239
240static void writeInt(std::ostream &f,size_t index)
241{
242 f.put(static_cast<int>((index>>24)&0xff));
243 f.put(static_cast<int>((index>>16)&0xff));
244 f.put(static_cast<int>((index>>8)&0xff));
245 f.put(static_cast<int>(index&0xff));
246}
247
248static void writeString(std::ostream &f,const DString &s)
249{
250 size_t l = s.length();
251 for (size_t i=0;i<l;i++) f.put(s[i]);
252 f.put(0);
253}
254
255void SearchIndex::write(const DString &fileName)
256{
257 size_t size=4; // for the header
258 size+=4*numIndexEntries; // for the index
259 size_t wordsOffset = size;
260 // first pass: compute the size of the wordlist
261 for (size_t i=0;i<numIndexEntries;i++)
262 {
263 const auto &wlist = m_index[i];
264 if (!wlist.empty())
265 {
266 for (const auto &iw : wlist)
267 {
268 size_t ws = iw.word().length()+1;
269 size+=ws+4; // word + url info list offset
270 }
271 size+=1; // zero list terminator
272 }
273 }
274
275 // second pass: compute the offsets in the index
276 size_t indexOffsets[numIndexEntries];
277 size_t offset=wordsOffset;
278 for (size_t i=0;i<numIndexEntries;i++)
279 {
280 const auto &wlist = m_index[i];
281 if (!wlist.empty())
282 {
283 indexOffsets[i]=offset;
284 for (const auto &iw : wlist)
285 {
286 offset+= iw.word().length()+1;
287 offset+=4; // word + offset to url info array
288 }
289 offset+=1; // zero list terminator
290 }
291 else
292 {
293 indexOffsets[i]=0;
294 }
295 }
296 size_t padding = size;
297 size = (size+3)&~3; // round up to 4 byte boundary
298 padding = size - padding;
299
300 std::vector<size_t> wordStatOffsets(m_words.size());
301
302 int count=0;
303
304 // third pass: compute offset to stats info for each word
305 for (size_t i=0;i<numIndexEntries;i++)
306 {
307 const auto &wlist = m_index[i];
308 if (!wlist.empty())
309 {
310 for (const auto &iw : wlist)
311 {
312 //printf("wordStatOffsets[%d]=%d\n",count,size);
313 wordStatOffsets[count++] = size;
314 size+=4 + iw.urls().size() * 8; // count + (url_index,freq) per url
315 }
316 }
317 }
318 std::vector<size_t> urlOffsets(m_urls.size());
319 for (const auto &udi : m_urls)
320 {
321 urlOffsets[udi.first]=size;
322 size+=udi.second.name.length()+1+
323 udi.second.url.length()+1;
324 }
325
326 //printf("Total size %x bytes (word=%x stats=%x urls=%x)\n",size,wordsOffset,statsOffset,urlsOffset);
327 std::ofstream f = Portable::openOutputStream(fileName);
328 if (f.is_open())
329 {
330 // write header
331 f.put('D'); f.put('O'); f.put('X'); f.put('S');
332 // write index
333 for (size_t i=0;i<numIndexEntries;i++)
334 {
335 writeInt(f,indexOffsets[i]);
336 }
337 // write word lists
338 count=0;
339 for (size_t i=0;i<numIndexEntries;i++)
340 {
341 const auto &wlist = m_index[i];
342 if (!wlist.empty())
343 {
344 for (const auto &iw : wlist)
345 {
346 writeString(f,iw.word());
347 writeInt(f,wordStatOffsets[count++]);
348 }
349 f.put(0);
350 }
351 }
352 // write extra padding bytes
353 for (size_t i=0;i<padding;i++) f.put(0);
354 // write word statistics
355 for (size_t i=0;i<numIndexEntries;i++)
356 {
357 const auto &wlist = m_index[i];
358 if (!wlist.empty())
359 {
360 for (const auto &iw : wlist)
361 {
362 size_t numUrls = iw.urls().size();
363 writeInt(f,numUrls);
364 for (const auto &ui : iw.urls())
365 {
366 writeInt(f,urlOffsets[ui.second.urlIdx]);
367 writeInt(f,ui.second.freq);
368 }
369 }
370 }
371 }
372 // write urls
373 for (const auto &udi : m_urls)
374 {
375 writeString(f,udi.second.name);
376 writeString(f,udi.second.url);
377 }
378 }
379
380}
381
382//---------------------------------------------------------------------------
383// the following part is for writing an external search index
384
388
390{
391 if (ctx && ctx->definitionType()==Definition::TypeMember)
392 {
393 const MemberDef *md = toMemberDef(ctx);
394 if (md->isFunction())
395 return "function";
396 else if (md->isSlot())
397 return "slot";
398 else if (md->isSignal())
399 return "signal";
400 else if (md->isVariable())
401 return "variable";
402 else if (md->isTypedef())
403 return "typedef";
404 else if (md->isEnumerate())
405 return "enum";
406 else if (md->isEnumValue())
407 return "enumvalue";
408 else if (md->isProperty())
409 return "property";
410 else if (md->isEvent())
411 return "event";
412 else if (md->isRelated() || md->isForeign())
413 return "related";
414 else if (md->isFriend())
415 return "friend";
416 else if (md->isDefine())
417 return "define";
418 }
419 else if (ctx)
420 {
421 switch(ctx->definitionType())
422 {
424 return (toClassDef(ctx))->compoundTypeString();
426 return "file";
428 return "namespace";
430 return "concept";
432 return "group";
434 return "package";
436 return "page";
438 return "dir";
440 return "module";
441 default:
442 break;
443 }
444 }
445 return "unknown";
446}
447
448void SearchIndexExternal::setCurrentDoc(const Definition *ctx,const DString &anchor,bool isSourceFile)
449{
450 std::lock_guard<std::mutex> lock(g_searchIndexMutex);
451 DString extId = stripPath(Config_getString(EXTERNAL_SEARCH_ID));
452 DString url = isSourceFile ? (toFileDef(ctx))->getSourceFileBase() : ctx->getOutputFileBase();
454 if (!anchor.empty()) url+=DString("#")+anchor;
455 DString key = extId+";"+url;
456
457 auto it = m_docEntries.find(key.str());
458 if (it == m_docEntries.end())
459 {
461 e.type = isSourceFile ? DString("source") : definitionToName(ctx);
462 e.name = ctx->qualifiedName();
464 {
465 e.args = (toMemberDef(ctx))->argsString();
466 }
467 else if (ctx->definitionType()==Definition::TypeGroup)
468 {
469 const GroupDef *gd = toGroupDef(ctx);
470 if (!gd->groupTitle().empty())
471 {
472 e.name = filterTitle(gd->groupTitle());
473 }
474 }
475 else if (ctx->definitionType()==Definition::TypePage)
476 {
477 const PageDef *pd = toPageDef(ctx);
478 if (pd->hasTitle())
479 {
480 e.name = filterTitle(pd->title());
481 }
482 }
483 e.extId = extId;
484 e.url = url;
485 it = m_docEntries.emplace(key.str(),e).first;
486 //printf("searchIndexExt %s : %s\n",qPrint(e->name),qPrint(e->url));
487 }
488 m_current = &it->second;
489}
490
491void SearchIndexExternal::addWord(const DString &word,bool hiPriority)
492{
493 std::lock_guard<std::mutex> lock(g_searchIndexMutex);
494 if (word.empty() || !isId(word[0]) || m_current==nullptr) return;
495 DString &text = hiPriority ? m_current->importantText : m_current->normalText;
496 if (!text.empty()) text+=' ';
497 text+=word;
498 //printf("addWord %s\n",word);
499}
500
502{
503 std::ofstream t = Portable::openOutputStream(fileName);
504 if (t.is_open())
505 {
506 t << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
507 t << "<add>\n";
508 for (auto &[name,doc] : m_docEntries)
509 {
510 t << " <doc>\n";
511 t << " <field name=\"type\">" << doc.type << "</field>\n";
512 t << " <field name=\"name\">" << convertToXML(doc.name) << "</field>\n";
513 if (!doc.args.empty())
514 {
515 t << " <field name=\"args\">" << convertToXML(doc.args) << "</field>\n";
516 }
517 if (!doc.extId.empty())
518 {
519 t << " <field name=\"tag\">" << convertToXML(doc.extId) << "</field>\n";
520 }
521 t << " <field name=\"url\">" << convertToXML(doc.url) << "</field>\n";
522 t << " <field name=\"keywords\">" << convertToXML(doc.importantText) << "</field>\n";
523 t << " <field name=\"text\">" << convertToXML(doc.normalText) << "</field>\n";
524 t << " </doc>\n";
525 }
526 t << "</add>\n";
527 }
528 else
529 {
530 err("Failed to open file {} for writing!\n",fileName);
531 }
532}
533
534//---------------------------------------------------------------------------------------------
535
537{
538 bool searchEngine = Config_getBool(SEARCHENGINE);
539 bool serverBasedSearch = Config_getBool(SERVER_BASED_SEARCH);
540 bool externalSearch = Config_getBool(EXTERNAL_SEARCH);
541 if (searchEngine && serverBasedSearch)
542 {
544 }
545}
546
551
552
A abstract class representing of a compound symbol.
Definition classdef.h:100
virtual DString compoundTypeString() const =0
Returns the type of compound as a string.
A String class for use with Doxygen wrapping std::string and adding some additional functionality off...
Definition dstring.h:84
DString lower() const
Definition dstring.h:326
bool empty() const
Returns true iff the string is empty (std::string compatible alias for isEmpty()).
Definition dstring.h:148
DString & prepend(const char *s)
Definition dstring.h:515
size_t find(char c, size_t pos=0) const
Definition dstring.h:239
const std::string & str() const
Definition dstring.h:645
const char * data() const
Returns a pointer to the contents of the string in the form of a 0-terminated C string.
Definition dstring.h:157
size_t length() const
Returns the length of the string, not counting the 0-terminator.
Definition dstring.h:151
The common base class of all entity definitions found in the sources.
Definition definition.h:77
virtual SrcLangExt getLanguage() const =0
Returns the programming language this definition was written in.
virtual DefType definitionType() const =0
virtual const DString & name() const =0
virtual DString qualifiedName() const =0
virtual DString getOutputFileBase() const =0
static SearchIndexIntf searchIndex
Definition doxygen.h:117
A model of a group of symbols.
Definition groupdef.h:48
virtual DString groupTitle() const =0
A model of a class/file/namespace member symbol.
Definition memberdef.h:45
virtual bool isSignal() const =0
virtual bool isFriend() const =0
virtual bool isForeign() const =0
virtual bool isRelated() const =0
virtual bool isTypedef() const =0
virtual bool isSlot() const =0
virtual bool isEvent() const =0
virtual bool isFunction() const =0
virtual bool isDefine() const =0
virtual bool isEnumerate() const =0
virtual bool isVariable() const =0
virtual bool isEnumValue() const =0
virtual bool isProperty() const =0
A model of a page symbol.
Definition pagedef.h:27
virtual bool hasTitle() const =0
virtual DString title() const =0
void addUrlIndex(int, bool)
void setCurrentDoc(const Definition *ctx, const DString &anchor, bool isSourceFile)
SearchDocEntry * m_current
void write(const DString &file)
std::map< std::string, SearchDocEntry > m_docEntries
void addWord(const DString &word, bool hiPriority)
void addWord(const DString &word, bool hiPriority)
std::map< int, URL > m_urls
std::vector< std::vector< IndexWord > > m_index
void write(const DString &file)
std::unordered_map< std::string, int > m_words
std::unordered_map< std::string, int > m_url2IdMap
void addWordRec(const DString &word, bool hiPrio, bool recurse)
void setCurrentDoc(const Definition *ctx, const DString &anchor, bool isSourceFile)
void setKind(Kind k)
virtual DString trGroup(bool first_capital, bool singular)=0
virtual DString trPackage(const DString &name)=0
virtual DString trNamespace(bool first_capital, bool singular)=0
virtual DString trSubprogram(bool first_capital, bool singular)=0
virtual DString trModule(bool first_capital, bool singular)=0
virtual DString trMember(bool first_capital, bool singular)=0
virtual DString trPage(bool first_capital, bool singular)=0
ClassDef * toClassDef(Definition *d)
#define Config_getBool(name)
Definition config.h:33
#define Config_getString(name)
Definition config.h:32
DString substitute(const DString &s, const DString &src, const DString &dst)
substitute all occurrences of src in s by dst
Definition dstring.cpp:485
bool isId(char c)
Returns true if c is a valid character for an identifier.
Definition dstring.h:895
FileDef * toFileDef(Definition *d)
Definition filedef.cpp:1973
GroupDef * toGroupDef(Definition *d)
Translator * theTranslator
Definition language.cpp:76
MemberDef * toMemberDef(Definition *d)
#define err(fmt,...)
Definition message.h:127
#define ASSERT(x)
Definition message.h:142
std::ofstream openOutputStream(const DString &name, bool append=false)
Definition portable.cpp:681
PageDef * toPageDef(Definition *d)
Definition pagedef.cpp:656
Portable versions of functions that are platform dependent.
static std::mutex g_searchIndexMutex
static DString definitionToName(const Definition *ctx)
static void writeInt(std::ostream &f, size_t index)
const size_t numIndexEntries
static void writeString(std::ostream &f, const DString &s)
static int charsToIndex(const DString &word)
Web server based search engine.
void initSearchIndexer()
void finalizeSearchIndexer()
DString extId
DString name
DString type
DString url
DString args
SrcLangExt
Definition types.h:207
DString filterTitle(const DString &title)
Definition util.cpp:4454
void addHtmlExtensionIfMissing(DString &fName)
Definition util.cpp:3931
DString convertToXML(const DString &s, bool keepEntities, const bool citeEntry)
Definition util.cpp:3232
int getPrefixIndex(const DString &name)
Definition util.cpp:2651
DString getLanguageSpecificSeparator(SrcLangExt lang, bool classScope)
Definition util.cpp:4629
DString stripPath(const DString &s)
Definition util.cpp:3960
A bunch of utility functions.