Doxygen XLinks
by
V: 2511R0
Website: doxygen
Loading...
Searching...
No Matches
tagfile.cpp
1//==================================================================================================
2// This implementation-file is part of DoxygenXLinks - A doxygen post-processor that allows to
3// define smarter <b>Doxygen</b>-links.
4//
5// \emoji :copyright: 2025-2026 A-Worx GmbH, Germany.
6// Published under \ref mainpage_license "Boost Software License".
7//==================================================================================================
8#include "index.hpp"
9#include "dxl.hpp"
10#include "dxlapp.hpp"
11#include "ALib.ALox.H"
14#include "ALib.App.H"
15
16
17using namespace alib;
18using namespace std;
19
20namespace dxl {
21
22Index::Parser::Parser(Index& parent, alib::IStreamReader& pReader, const alib::String& pFilePath)
23: index {parent}
24, dxl {*alib::app::Get<DXLApp>().dxl}
25, ma {parent.ma}
26, reader {pReader}
27, FilePath {pFilePath}
28, actDocAnchors{ma} {}
29
30
32 START:
33 reader.Read(lineBuf);
36 line.TrimStart();
37 ++lineNo;
38 maxWidth= std::max(maxWidth, lineBuf.Length());
39
40 // <docanchor> might appear anywhere. That's why we handle them here.
41 if (line.StartsWith("<docanchor")){
42 String name = allocTag("docanchor");
43 String256 anchorFile= parseAttr("file");
44 String256 title = parseAttr("title", true);
45
46 // remove escape chars '\'
47 // Note: Since doxygen Version 1.16 we had to insert the backslash to the title, because
48 // otherwise the TOC did not work. (Strange new doxygen bug we think)
49 integer pos= 0;
50 for (;;) {
51 pos= title.IndexOf('\\', pos);
52 if (pos<0)
53 break;
54 title.Delete(pos,1);
55 pos++;
56 }
57 String titleStr(ma, title);
58 actDocAnchors.push_back(ma().New<TGTDocAnchor>( String(ma, anchorFile ),
59 lineNo, name, titleStr));
60 goto START;
61} }
62
64 for (auto* actDocAnchor: actDocAnchors) {
65 if ( !parent.Value()->HTMLFile.Equals(actDocAnchor->HTMLFile) ) {
66 if (verbosity >= Verbosity::Info) {
67 Lox_Info("Skipping anchor {!Q} in compound {!Q} of type {}, because it has a\n"
68 "different HTML file-target than its parent.\n"
69 " Compound target: {}\n"
70 " DocAnchor Target: {}\n"
71 " In tag-file: @ {}:{}\n"
72 ,actDocAnchor->Name, parent.Name(), parent.Value()->Kind()
73 ,parent.Value()->HTMLFile
74 ,actDocAnchor ->HTMLFile
75 ,FilePath, actDocAnchor->LineNo + 1)
76 }
77 continue;
78 }
79
80 Cursor newChild= parent.CreateChild(actDocAnchor->Name, actDocAnchor);
81 if ( newChild.IsInvalid() ) {
82 String256 path;
83 parent.AssemblePath(path);
84 Cursor previous= parent.Child(actDocAnchor->Name);
85 Lox_Error( "Doubly defined anchor {!Q} in compound {!Q} of type {} (ignoring)\n"
86 " First definition @ {}:{}\n"
87 " Second definition @ {3}:{}"
88 ,actDocAnchor->Name, parent.Name(), parent.Value()->Kind()
90 ,previous.Value()->LineNo, actDocAnchor->LineNo )
91 }
92
93 }
94 actDocAnchors.Clear();
95}
96
98 tagAttr= nullptr;
99 if (!line.ConsumeChar('<') )
101 if (!line.StartsWith(tagName))
103
104 line.ConsumeChars(tagName.Length());
105
106 // attributes following?
107 integer attrEnd= 1;
108 if ( line.CharAtStart() == ' ' ) {
109 // find next '>' which is not in a string.
110 bool inString= false;
111 for (; attrEnd<line.Length(); ++attrEnd) {
112 character c= line.CharAt<NC>(attrEnd);
113 if (c == '\"' && line.CharAt<NC>(attrEnd - 1) != '\\') {
114 inString= !inString;
115 continue;
116 }
117 if (!inString && c == '>' )
118 break;
119 }
120 if ( attrEnd == line.Length())
122 tagAttr= line.Substring(1, attrEnd-1);
123 line.ConsumeChars<NC>(attrEnd + 1 );
124 if (line.IsEmpty())
125 return line;
126 }
127 else {
128 if (line.CharAtStart() != '>')
130 line.ConsumeChar();
132 }
133
134 if (line.EndsWith(String64("</")._<NC>(tagName)._<NC>('>')) )
135 return line.Substring(0, line.Length()- (tagName.Length() + 3));
136
137 if (!line.EndsWith(">"))
139
140 line.ConsumeCharsFromEnd(1);
141 return line;
142
143}
144
145String Index::Parser::parseAttr(const String& attrName, bool isOptional) {
146 integer idxStart= 0;
147 for (;;) {
148 idxStart= tagAttr.IndexOf(attrName, idxStart);
149 if ( idxStart<0 ) {
150 if ( !isOptional )
151 Lox_Error("Unexpected Doxygen tag-file format: Missing attribute {!Q}\n"
152 " Line read: {}\n"
153 " @ {}:{}\n"
154 ,attrName
155 ,line
157 return nullptr;
158 }
159 if ( tagAttr.CharAt(idxStart + attrName.Length() ) == '='
160 && tagAttr.CharAt(idxStart + attrName.Length() + 1) == '\"' )
161 break;
162 idxStart+= attrName.Length() + 1;
163 }
164 idxStart+= attrName.Length() + 2;
165 bool escaped= false;
166 integer idxEnd= idxStart;
167 for (; idxEnd<tagAttr.Length(); ++idxEnd) {
168 character c= tagAttr.CharAt<NC>(idxEnd);
169 if (!escaped) {
170 if (c == '"')
171 break;
172 if ( c == '\\' )
173 escaped= true;
174 }
175 else
176 escaped= false;
177 }
178 if ( idxEnd == tagAttr.Length() )
181
182 return tagAttr.Substring( idxStart, idxEnd-idxStart);
183}
184
185/// todo: add parameter parent and if html file is the same, then re-use instead of allocation.
187 if (!line.ConsumeString( "<member kind=\"" ))
189 FilePath, lineNo, "member", line);
190 Target::Kinds kind;
191 if (! enumrecords::Parse(line, kind) )
193
194 // parse a refID: todo: I have no samples, yet for this. Not tested!
195 String128 refID;
196 line.ConsumeChar('"');
197 line.TrimStart();
198 if (line.ConsumeString("refid=\"")) {
199 refID = line.ConsumeToken('"');
200 }
201
202 key.Reset();
203
204 // pre-processor defines
205
206 if ( kind == Target::Macro ) {
207 int lNo= lineNo;
208 ALIB_DBG(String type=)
209 nextTag("type");
210 ALIB_ASSERT_ERROR(type.Equals("#define"), "DXL/TAGFILE", "Expected '#define' as type")
211 key << nextTag("name");
212 auto* member = ma().New<TGTMacro>(allocNextTag("anchorfile"), lNo);
213 member->Anchor = allocNextTag("anchor");
214 Substring argList = nextTag("arglist");
215 member->Args = Target::FunctionArguments::PARSE(ma, argList);
216 nextLine();
217 if (refID.IsNotEmpty())
218 member->RefID= String(ma, refID);
219 return member;
220 }
221
222 // typedef
223 if ( kind == Target::Typedef ) {
224 int lNo= lineNo;
225 String type = allocNextTag("type");
226 key << nextTag("name");
227
228 TGTTypedef* member= ma().New<TGTTypedef>(allocNextTag("anchorfile"), lNo);
229 member->Type = type;
230 member->Anchor= allocNextTag("anchor");
231 if ( nextTag("arglist").IsNotEmpty() )
233 nextLine();
234 if (refID.IsNotEmpty())
235 member->RefID= String(ma, refID);
236 return member;
237 }
238
239 // typedef or enumeration
240 if ( kind == Target::Enumeration ) {
241
242 int lNo= lineNo;
243 String type = allocNextTag("type");
244 key << nextTag("name");
245
246 TGTEnumeration* member= ma().New<TGTEnumeration>(allocNextTag("anchorfile"), lNo);
247 member->Type = type;
248 member->Anchor= allocNextTag("anchor");
249 if ( nextTag("arglist").IsNotEmpty() )
251 nextLine();
252 if (line.StartsWith("<enumvalue")) {
253 // the caller has to check whether line is "<enumvalue..." and add each value as its child.
254 return member;
255 }
256 if (refID.IsNotEmpty())
257 member->RefID= String(ma, refID);
258 return member;
259 }
260
261 // enum value
262 if ( kind == Target::EnumElement ) {
263 int lNo= lineNo;
264 key << nextTag("name");
265 TGTEnumValue* member= ma().New<TGTEnumValue>(allocNextTag("anchorfile"), lNo);
266 member->Anchor = allocNextTag("anchor");
267 if ( nextTag("arglist").IsNotEmpty() )
269
270 nextLine();
271 if (!line.StartsWith("</member")) {
272 if ( kind != Target::Enumeration )
274
275 // the caller has to check whether line is "<enumvalue..." and add each value as its child.
276 return member;
277 }
278
279 if (refID.IsNotEmpty())
280 member->RefID= String(ma, refID);
281 return member;
282 }
283
284 // variables
285 if ( kind == Target::Variable ) {
286 int lNo= lineNo;
287 String1K argList;
288 String type= allocNextTag("type");
289 key << nextTag("name");
290 TGTVariable* member= ma().New<TGTVariable>(allocNextTag("anchorfile"), lNo);
291 member->Type = type;
292 member->Anchor = allocNextTag("anchor");
293 member->Subscript= allocNextTag("arglist");
294
295 nextLine();
296 if (!line.Equals("</member>"))
298
299 // checks on variables
300 if ( argList.IsNotEmpty() && argList.CharAtStart()!='[' )
301 throw Exception(ALIB_CALLER_NULLED, Exceptions::UnexpectedXMLValue, FilePath,lineNo, "arglist starting with '['", argList);
302 if (!line.Equals("</member>"))
304
305 if (refID.IsNotEmpty())
306 member->RefID= String(ma, refID);
307 return member;
308 }
309
310 // functions
311 if ( kind == Target::Function ) {
312 int lNo= lineNo;
313 String type= allocNextTag("type");
314 key << nextTag("name");
315 TGTFunction* member= ma().New<TGTFunction>(allocNextTag("anchorfile"), lNo);
316 member->Type= type;
317 member->Anchor= allocNextTag("anchor");
318
319 Substring argList= nextTag("arglist");
320 if ( argList.IsNotEmpty() && !argList.Equals("()") ) {
321 if (argList.CharAtStart() != '(' )
323 "arglist starting with '('", argList);
324 member->Args= Target::FunctionArguments::PARSE(ma, argList);
325 if (member->Args == nullptr)
328 member->Args->Print(key);
329 if (argList.Trim().IsNotEmpty())
330 member->Qualifiers= String(ma, argList);
331 }
332
333 if ( member->Qualifiers.IsNotEmpty() )
334 key << ' ' << member->Qualifiers;
335
336 nextLine();
337 if (!line.Equals("</member>"))
339 "/member", line);
340 if (refID.IsNotEmpty())
341 member->RefID= String(ma, refID);
342 return member;
343 }
344
345 // warn if not a group-member (and not fetched above)
346 if (compoundType != Target::Group && verbosity >= Verbosity::Warning)
347 Lox_Warning("Unexpected member type {} in compound of type {}. Ignoring.",
348 kind, compoundType)
349
350 // read generic (group) members
352 if (refID.IsNotEmpty())
353 member->RefID= String(ma, refID);
354 for (;;) {
355 nextLine();
356 if (line.Equals("</member>"))
357 break;
358 if (line.StartsWith("<name")) key << nextTag("name");
359 else if (line.StartsWith("<anchorfile")) member->HTMLFile= allocNextTag("anchorfile");
360 else if (line.StartsWith("<anchor")) member->Anchor = allocTag("anchor");
361 }
362 if (key.IsNotEmpty())
363 return member;
364
365 Lox_Warning("Unnamed (generic) member of type {} in compound of type {}. Ignoring.",
366 member->Kind, compoundType)
367 return nullptr;
368}
369
370
372 Lox_SetDomain("DXL/TAGFILE", Scope::Filename )
373 Lox_Info("Reading tag-file: ", FilePath )
374 static TGTFilePathComponent filePathComponentTag(-1);
375
376 errno= 0;
377 std::ifstream ifstream;
378 ifstream.open( Path(FilePath).Terminate() );
379
381 if ( !ifstream.is_open() || errno ) {
382 app.cErr->Add(app.cli.ExitCodeDecls.Find(ExitCodes::TagFileNotFound).Mapped()->FormatString(),
383 FilePath, app.DoxyfilePath);
384 app.machine.SetExitCode(ExitCodes::TagFileNotFound);
385 throw Exception( ALIB_CALLER_NULLED, app::App::Exceptions::ControlledEarlyExit );
386 }
387
388 IStreamReader reader(1024,1024);
389 reader.SetStream( &ifstream );
390
392 Parser parser(*this, reader, FilePath);
394 // we want maximum speed and even spare the fast log calls in the loop.
395 Lox_GetVerbosity(parser.verbosity)
396
397 // skip 1st line
398 parser.nextLine();
399 ALIB_ASSERT_ERROR(parser.line.StartsWith("<?xml version='1.0'"), "DXL/TAGFILE", "Unknown file format")
400
401 // print doxygen version
402 parser.nextTag("tagfile");
403 Lox_Info("Doxygen version: ", parser.getAttr("doxygen_version") )
404 while( !reader.IsEOF() ) {
405 // read line
406 parser.nextLine();
407
408 if (parser.line.Equals("</tagfile>"))
409 break;
410
411 // not a compound?
412 if ( parser.tryTag("compound").IsNull() ) {
414 FilePath, parser.lineNo, "compound", parser.line);
415 }
416
417 //======================================== new compound ======================================
418 Target::Kinds kind;
419 Substring kindStr= parser.getAttr("kind");
420 if (! enumrecords::Parse( kindStr, kind ) ) {
421 Lox_Error("Unknown compound type {!Q} @ {}:{}", kindStr, FilePath, parser.lineNo)
422 // read to the end of unknown compound
423 for(;;) {
424 if (parser.line.Equals( "</compound>") )
425 break;
426 parser.nextLine();
427 }
428 continue;
429 }
430
431 //-------------------------------- Compound struct/class/union -------------------------------
432 if ( kind & Target::COMPOUND ) {
433 String1K compoundTagName = parser.nextTag("name");
434 String compoundHtmlFile= parser.allocNextTag("filename");
435
436 // local collection of template args given with tag <templarg>.
437 // Will be added on finalization to TGT
439 String* templateArgs= la().NewArray<String>(Target::MAX_TEMPLATE_ARGS);
440 int templateArgsSize= 0;
441
442
443 // cut off template specialization args"<..." from name.
444 Substring compoundNameWithoutTemplateParams= compoundTagName;
445 Substring templateSpecializationArgs= nullptr;
446 if ( compoundTagName.CharAtEnd<NC>() == '>') {
447 integer templateStart= compoundTagName.IndexOf('<');
448 ALIB_ASSERT(templateStart>0, "DXL/TAGFILE")
449 compoundNameWithoutTemplateParams= compoundTagName.Substring(0, templateStart);
450 compoundNameWithoutTemplateParams.TrimEnd();
451 templateSpecializationArgs = compoundTagName.Substring(templateStart);
452 templateSpecializationArgs .TrimStart();
453 }
454
455 // extract the path to the component by finding the last "::"
456 String compoundPathPortion= nullptr;
457 integer lastPathPos= compoundNameWithoutTemplateParams.LastIndexOf(':');
458 if ( lastPathPos>=0 ) {
459 ALIB_ASSERT_ERROR(compoundTagName.CharAt(lastPathPos -1) == ':', "DXL/TAGFILE",
460 "Single colon ':' found in compound name {!Q}", compoundTagName )
461 compoundPathPortion= compoundTagName.Substring(0, lastPathPos -1);
462 compoundNameWithoutTemplateParams= compoundNameWithoutTemplateParams.Substring(lastPathPos + 1);
463 }
464
465 // create the path to the new component.
466 Cursor pathCursor = Root();
467 if ( compoundPathPortion.IsNotEmpty() ) {
468 Tokenizer tok(compoundPathPortion, ':');
469 while (tok.HasNext()) {
470 String pathComponent= tok.Next();
471 Cursor component= pathCursor.CreateChild(pathComponent, nullptr);
472 // If it did not exist, we create an unknown component. A compound will
473 // hopefully replace this later. This may happen because "deeper" compounds
474 // can come first in doxygen tag-files.
475 if ( component.IsValid()) {
476 component.Value()= ma().New<TGTFilePathComponent>(parser.lineNo);
477 pathCursor= component;
478 }
479 else
480 pathCursor.GoToChild(tok.Actual);
481
482 // skip the next ':'
483 tok.Next();
484 } }
485
486 // Now create the child itself. Template parameters have to be included in the key.
487 String compoundKey= compoundPathPortion.IsNotEmpty()
488 ? compoundTagName.Substring(compoundPathPortion.Length() + 2)
489 : compoundTagName;
490 Cursor compoundCursor= pathCursor.CreateChild(compoundKey, nullptr);
491 if ( compoundCursor.IsInvalid()) {
492 compoundCursor= pathCursor.Child(compoundKey);
493
494 if (!compoundCursor.Value()->IsA(Target::UNKNOWN_COMPOUND | Target::FILEPATH_COMPONENT))
496 compoundKey, compoundPathPortion, FilePath,
497 pathCursor.Child(compoundKey).Value()->LineNo, parser.lineNo);
498 }
499
500 // Add Namespace or Struct/Class/Union tag
501 TGTNamespace* tagNamespace= nullptr;
502 TGTRecord* tagRecord= nullptr;
503 if ( kind == Target::Namespace) {
504 compoundCursor.Value()=
505 tagNamespace= ma().New<TGTNamespace>(compoundHtmlFile, parser.lineNo);
506 ALIB_ASSERT(templateSpecializationArgs.IsNull(), "DXL/TAGFILE")
507 }
508 else {
509 compoundCursor.Value()=
510 tagRecord= ma().New<TGTRecord>(ma, kind, compoundHtmlFile, parser.lineNo);
511 // Todo:
512 // The name is internally allocated, hence the template args could be just stored.
513 // We could do the same on the XLink-side by allocating the string to parse.
514 // This would spare us the allocation.
515 // Also: both types, TemplateArguments and FunctionArguments should use a
516 // forward list.
517 // but it is ok as it is...
518 if ( templateSpecializationArgs.IsNotEmpty())
519 tagRecord->SpecializationArgs= Target::TemplateArguments::PARSE(ma, templateSpecializationArgs);
520 }
521
522
523 //-------------------------------------- read members ------------------------------------
524 for(;;) {
525 parser.nextLine();
526 parser.addDocAnchors(compoundCursor);
527
528 if ( parser.line.StartsWith("<member kind=") ) {
529 String512 key;
530 TGTMember* member= parser.readMember(key, kind);
531 if (!member) continue;
532 //Dump(compoundCursor);
533 Cursor memberChild= compoundCursor.CreateChild(key, member);
534 if ( memberChild.IsInvalid() ) {
535 Target* existingValue= compoundCursor.Child(key).Value();
536 if ( member->IsA(Target::Function) ) {
537 // this is just strange: sometimes doxygen adds the very same
538 // member twice. We just ignore that.
539 // However, if just the anchor is different, then either DXL is
540 // wrong or a newer doxygen version does something different.
541 // Thus we throw.
542 if ( !existingValue->IsA(Target::Function)
543 && !Cast<TGTFunction,NC>(existingValue)->Anchor.Equals(member->Anchor)) {
544 Lox_Error("Doubly defined member function.\n"
545 "This happens with overloaded template functions that use keyword requires.\n"
546 "To fix this, dox only one function and include in the dox that\n"
547 "different versions for different requirements exist.\n"
548 " Function: {}::{}.\n"
549 " Tag-file first occurence: {}:{}.\n"
550 " Tag-file this occurence: {2}:{}.\n"
551 , compoundTagName, key, FilePath
552 , existingValue->LineNo
553 , parser.lineNo )
555 key, compoundTagName, FilePath,
556 existingValue->LineNo, member->LineNo);
557 } }
558 else if ( member->IsA(Target::EnumElement)) {
559 // enum values of non-class enums appear doubly in the tag-file.
560 // We throw only in the case that the previous definition was
561 // different.
562 if ( !existingValue->IsA(Target::EnumElement)
563 || !existingValue->HTMLFile.Equals(member->HTMLFile)
564 || !Cast<TGTMember,NC>(existingValue)->Anchor.Equals(member->Anchor) )
566 key, compoundPathPortion, FilePath,
567 existingValue->LineNo,
568 member ->LineNo);
569
570 }
571 else
573 key, compoundPathPortion, FilePath,
574 existingValue->LineNo,
575 member ->LineNo);
576 }
577
578 // add the doc anchor(s) if present
579 parser.addDocAnchors(memberChild);
580
581 // if member type is enum, enum values might come now
582 while (parser.line.StartsWith("<enumvalue")) {
583 int lNo= parser.lineNo;
584 String256 elemName= parser.parseTag("enumvalue");
585 TGTEnumValue* enumElementTag= ma().New<TGTEnumValue>(
586 String(ma, parser.parseAttr("file")), lNo );
587 enumElementTag->Anchor = String(ma, parser.parseAttr("anchor"));
588 Cursor elementCursor= memberChild.CreateChild(elemName, enumElementTag);
589 if ( elementCursor.IsInvalid() ) {
591 elemName, compoundPathPortion, FilePath,
592 memberChild.Child(elemName).Value()->LineNo, parser.lineNo );
593 }
594 parser.nextLine();
595 }
596
597 // now we expect the end of the member
598 if (!parser.line.Equals("</member>"))
600 FilePath,parser.lineNo, "/member", parser.line);
601
602 continue;
603 }
604
605 // Tag <templarg> gives the template parameters of the type.
606 // Not that specializations have their template parameters encoded in their name!
607 if ( parser.line.StartsWith("<templarg" ) ){
608 templateArgs[templateArgsSize++]=
609 String(static_cast<MonoAllocator&>(la), parser.parseTag("templarg"));
610 // todo: ^^^^why do we need to cast down here?
611 continue;
612 }
613
614 // ignore simple "announcements" of inner types
615 if ( parser.line.StartsWith("<class" )
616 && parser.line.EndsWith( "</class>") ) continue;
617
618 if ( parser.line.Equals( "</compound>") ) break;
619
620 // ignored tags
621 if ( parser.line.StartsWith("<base" ) ) {
622 ALIB_ASSERT_ERROR(tagRecord, "DXL/TAGFILE",
623 "Base type information found for non-record type")
624 tagRecord->BaseTypes.push_back(parser.allocTag("base"));
625 continue;
626 }
627
628 if (kind == Target::Namespace) {
629 if ( parser.line.StartsWith("<namespace" ) ) continue;
630 if ( parser.line.StartsWith("<concept" ) ) continue;
631 }
632
634 FilePath,parser.lineNo, "/compound", parser.line);
635 }
636
637 // add collected template args
638 if (templateArgsSize) {
639 ALIB_ASSERT_ERROR(tagRecord, "DXL/TAGFILE",
640 "Template args found for non-record type")
641 tagRecord->TemplateArgs= ma().New<Target::TemplateArguments>();
642 tagRecord->TemplateArgs->Count= templateArgsSize;
643 tagRecord->TemplateArgs->Arguments= ma().NewArray<String>(templateArgsSize);
644 for (int i= 0; i<templateArgsSize; ++i)
645 tagRecord->TemplateArgs->Arguments[i]= String(ma, templateArgs[i]);
646 } }
647
648 //---------------------------------------- Compound Dir --------------------------------------
649 else if ( kind == Target::Dir ){
650 String256 path;
651 Cursor dirCursor = Root();
652 String256 name = parser.nextTag("name");
653 path = parser.nextTag("path");
654 ReplaceToTreeSeparator(path, kind);
655 dirCursor.GoToCreatedPathIfNotExistent(path, &filePathComponentTag);
656
657 // create node with TDir : Tag
658 dirCursor= dirCursor.CreateChild(name, ma().New<TGTDir>( parser.allocNextTag("filename"),
659 parser.lineNo ));
660
661 for(;;) {
662 parser.nextLine();
663 parser.addDocAnchors(dirCursor);
664
665
666 //----------------------------------- global members ---------------------------------
667 if ( parser.line.StartsWith("<file>") ) continue;
668 if ( parser.line.StartsWith("<dir>") ) continue;
669 if ( parser.line.Equals( "</compound>") )
670 break;
671
672 // todo:don't throw, just warn
674 FilePath,parser.lineNo, "/compound", parser.line);
675 } }
676
677 //--------------------------------------- Compound File --------------------------------------
678 else if (kind == Target::File){
679 String256 path;
680 Cursor fileCursor= Root();
681 String256 name = parser.nextTag("name");
682 path = parser.nextTag("path");
683 ReplaceToTreeSeparator(path, kind);
684 fileCursor.GoToCreatedPathIfNotExistent(path, &filePathComponentTag);
685
686 // doxygen sometimes adds the last name of the path (probably to avoid duplicates)
687 if ( name.StartsWith(fileCursor.Name())
688 && name.CharAt(fileCursor.Name().Length() ) == DIRECTORY_SEPARATOR )
689 name.DeleteStart(fileCursor.Name().Length() +1 );
690
691 // create node with TFile : Tag
692 fileCursor= fileCursor.CreateChild(name, ma().New<TGTFile>(
693 parser.allocNextTag("filename"), parser.lineNo));
694 for(;;) {
695 parser.nextLine();
696 parser.addDocAnchors(fileCursor);
697 //----------------------------------- global members ---------------------------------
698 if ( parser.line.StartsWith("<member kind=") ) {
699 String512 key;
700 TGTMember* member= parser.readMember(key, kind);
701 if (!member) continue;
702 Cursor memberChild= fileCursor.CreateChild(key, member);
703 if ( memberChild.IsInvalid() ) {
704 fileCursor.AssemblePath(path);
705 ReplaceFromTreeSeparator(path, kind);
706 Cursor previous= fileCursor.Child(key);
707 // this is just strange: sometimes doxygen adds the very same
708 // member twice. We just ignore that.
709 // However, if just the anchor is different, then either DXL is
710 // wrong or a newer doxygen version does something different.
711 if ( Cast<TGTMember,NC>(previous)->Anchor != member->Anchor
712 || !previous.Value()->HTMLFile.Equals(member->HTMLFile) ) {
713 if ( previous.Value()->IsA(Target::Macro) ) {
714 Lox_Warning("Doubly defined preprocessor macro.\n"
715 "This happens when a preprocessor constant or macro is defined twice within\n"
716 "the same source file. This is not supported by Doxygen.\n"
717 "Instead, exclude the second (and further) occurrences in the same file from\n"
718 "doxygen and add all different uses to the first definition.\n"
719 " Preprocessor define: {}::{}.\n"
720 " Tag-file first occurence: {}:{}.\n"
721 " Tag-file this occurence: {2}:{}.\n"
722 , path, key, FilePath
723 , previous.Value()->LineNo
724 , parser.lineNo )
725 } else if (!Cast<TGTMacro,NC>(previous.Value())->Anchor.Equals(member->Anchor)) {
726 Lox_Error("Doubly defined member {!Q} in tag-file path {}:1\n"
727 " First definition @ {}:{}\n"
728 " Second definition @ {2}:{}", key, path, FilePath,
729 previous.Value()->LineNo, member->LineNo)
730
731 // todo: I think we should not throw. The error is enough.
732 // Lets try to work as far as we can to continue to work with
733 // upcoming releases.
734 // So: find (and try to remove) all Exceptions::DuplicateChildName
735 // and all exceptions anyhow!
737 key, path, FilePath,
738 previous.Value()->LineNo, member->LineNo);
739 } }
740 }
741 else
742 // add the doc anchor(s) if present
743 parser.addDocAnchors(memberChild);
744
745 continue;
746 }
747
748 if ( parser.line.Equals( "</compound>") )
749 break;
750
751// todo:
752// a) check if these really occur (or if this is a copy/paste error)
753// b) don't throw, just warn
754// c) change this everywhere
755
756
757 // we are not interested in the the other contents of file compounds.
758 // we just check if there is something unexpected
759 if ( parser.line.StartsWith("<includes" ) ) continue;
760 if ( parser.line.StartsWith("<concept" ) ) continue;
761 if ( parser.line.StartsWith("<class" ) ) continue;
762 if ( parser.line.StartsWith("<namespace" ) ) continue;
763
764
766 FilePath,parser.lineNo, "/compound", parser.line);
767 } }
768
769 //--------------------------------------- Compound Group -------------------------------------
770 else if (kind == Target::Group){
771 String128 name = parser.nextTag("name");
772 String title = parser.allocNextTag("title");
773
774 // create compound entry
775 Cursor groupCursor= Root().CreateChild(name, ma().New<TGTGroup>(
776 parser.allocNextTag("filename"), parser.lineNo, title));
777 for(;;) {
778 parser.nextLine();
779 parser.addDocAnchors(groupCursor);
780
781 //----------------------------------- global members ---------------------------------
782 if ( parser.line.StartsWith("<member kind=") ) {
783 String512 key;
784 TGTMember* member= parser.readMember(key, kind);
785 if (!member) continue;
786
787 Cursor memberChild= groupCursor.CreateChild(key, member);
788 if ( memberChild.IsInvalid() ) {
789 String256 path;
790 groupCursor.AssemblePath(path);
791 Cursor previous= groupCursor.Child(key);
792 Lox_Warning("Doubly defined member {!Q} in tag-file path {}:1\n"
793 " First definition @ {}:{}\n"
794 " Second definition @ {2}:{}"
795 ,key, path, FilePath
796 ,previous.Value()->LineNo, member->LineNo )
797 }
798
799 // add the doc anchor(s) if present
800 parser.addDocAnchors(memberChild);
801
802 continue;
803 }
804
805 if ( parser.line.Equals( "</compound>") )
806 break;
807
808 // we are not interested in the the other contents of file compounds.
809 // we just check if there is something unexpected
810 //if ( parser.line.StartsWith("<includes" ) ) continue;
811 //if ( parser.line.StartsWith("<concept" ) ) continue;
812 //if ( parser.line.StartsWith("<class" ) ) continue;
813 //if ( parser.line.StartsWith("<namespace" ) ) continue;
814
816 FilePath,parser.lineNo, "/compound", parser.line);
817 } }
818
819 //--------------------------------------- Compound Page --------------------------------------
820 else if (kind == Target::Page){
821 String128 name = parser.nextTag("name");
822 String title = parser.allocNextTag("title");
823
824 // create compound entry
825 Cursor pageCursor= Root().CreateChild(name, ma().New<TGTPage>(
826 parser.allocNextTag("filename"), parser.lineNo, title));
827
828 for(;;) {
829 parser.nextLine(); // this reads anchors!
830 parser.addDocAnchors(pageCursor);
831
832 if ( parser.line.Equals( "</compound>") )
833 break;
834
836 FilePath,parser.lineNo, "/compound", parser.line);
837 } }
838
839 //-------------------------------------- C++ 20 Module -------------------------------------
840 else if (kind == Target::Module) {
841 String256 name = parser.nextTag("name");
842 // Note: The filename written by doxygen is obviously wrong (as of Doxygen 1.16.1)
843 // Its rather the module's interface file's name (ixx) than the module page.
844 // See other notes tagged DOXYMODFILENAME.
845 auto htmlFile= parser.allocNextTag("filename");
846 ReplaceToTreeSeparator(name, kind);
847 Cursor cursor= Root();
848 int qtyCreated= cursor.GoToCreatedPathIfNotExistent(name, &filePathComponentTag);
849 if ( qtyCreated == 0 ) {
850 Cursor previous= cursor.Child(name);
851 if ( !previous.Value()->IsA(Target::Concept)
852 || previous.Value()->HTMLFile.Equals(htmlFile) )
854 name, FilePath, previous.Value()->LineNo, parser.lineNo);
855 Lox_Warning("Doubly defined 'Module' {!Q}\n"
856 " First definition @ {}:{}\n"
857 " Second definition @ {2}:{}",
858 name, FilePath, previous.Value()->LineNo, parser.lineNo)
859 }
860
861 // create node with TGTModule value
862 cursor.Value()= ma().New<TGTModule>(htmlFile, parser.lineNo);
863 parser.nextLine();
864 if (!parser.line.Equals("</compound>"))
866 FilePath, parser.lineNo, "/compound", parser.line);
867 }
868 //-------------------------------------- C++ 20 Concepts -------------------------------------
869 else if (kind == Target::Concept) {
870 String256 name = parser.nextTag("name");
871 auto htmlFile= parser.allocNextTag("filename");
872 ReplaceToTreeSeparator(name, kind);
873 Cursor cursor= Root();
874 int qtyCreated= cursor.GoToCreatedPathIfNotExistent(name, &filePathComponentTag);
875 if ( qtyCreated == 0 ) {
876 Cursor previous= cursor.Child(name);
877 if ( !previous.Value()->IsA(Target::Concept)
878 || previous.Value()->HTMLFile.Equals(htmlFile) )
880 name, FilePath, previous.Value()->LineNo, parser.lineNo);
881 Lox_Warning("Doubly defined 'Concept' {!Q}\n"
882 " First definition @ {}:{}\n"
883 " Second definition @ {2}:{}",
884 name, FilePath, previous.Value()->LineNo, parser.lineNo)
885 }
886
887 // create node with TGTConcept value
888 cursor.Value()= ma().New<TGTConcept>(htmlFile, parser.lineNo);
889 parser.nextLine();
890 if (!parser.line.Equals("</compound>"))
892 FilePath, parser.lineNo, "/compound", parser.line);
893 }
894
895 else
897 FilePath,parser.lineNo, kind);
898
899 }
900
901 StatCtdLines= parser.lineNo;
902 Lox_Verbose("Finished reading tag-file: {}, maximum line width: ", FilePath, parser.maxWidth )
903 ALIB_ASSERT_ERROR(parser.maxWidth < 1024, "DXL/TAGFILE", "Line buffer to small.")
904}
905
907 if ( branch.IsInvalid())
908 branch= Root();
910 AString actIndent;
911 Lox_SetDomain("DUMP", Scope::Method )
912 Lox_SetPrefix( std::reference_wrapper(actIndent) )
913
914 Lox_Info("Dumping tag-file: " )
916 stit.SetPathGeneration(lang::Switch::On);
917 stit.Initialize(branch.AsCursor(), lang::Inclusion::Exclude);
918 while (stit.IsValid()) {
920 // ReSharper disable once CppDFAUnreachableCode
921 Target& tag= *stit.Node().Value();
922 actIndent.Reset(String(spaces.Buffer(), stit.CurrentDepth()*4 ));
923 switch (tag.Kind()) {
925 Lox_Warning( "Unknown Compound: {}::{}\n"
926 " Documented in: {}\n"
927 " Tag-file location: {}:{}",
928 stit.Path(), stit.Node().Name(),
929 tag.HTMLFile, FilePath, tag.LineNo )
930 break;
931
933 Lox_Info(stit.Node().Name(), DIRECTORY_SEPARATOR)
934 } break;
935
936 case Target::File: {
937 auto& data= *Cast<TGTFile,NC>(stit.Node());
938 Lox_Info("File: ", data.HTMLFile)
939 } break;
940
941 case Target::Variable:
942 Lox_Info("Variable: ", stit.Node().Name())
943 break;
944 case Target::Function:
945 Lox_Info("Method: ", stit.Node().Name())
946 break;
947 case Target::Typedef:
948 Lox_Info("Typedef: ", stit.Node().Name())
949 break;
951 Lox_Info("Enum: ", stit.Node().Name())
952 break;
954 Lox_Info("Elem: ", stit.Node().Name())
955 break;
956 case Target::Page:
957 case Target::Group:
958 ALIB_ERROR("DXL/DUMP", "Page/Group should not be read today" )
959 break;
960
961 case Target::Namespace: {
962 const TGTNamespace* target= Cast<TGTNamespace,NC>(stit.Node());
963 Lox_Info("{}: ", target->Kind(), stit.Node().Name())
964 break;
965 }
966 case Target::Struct:
967 case Target::Class:
968 case Target::Union: {
969 const TGTRecord* target= Cast<TGTRecord,NC>(stit.Node());
970 Lox_Info("{}: {}<{}> : {}]", target->Kind(),
971 stit.Node().Name(), target)
972 break;
973 }
974
975 case Target::Concept:{
976 const TGTConcept* target= Cast<TGTConcept,NC>(stit.Node());
977 Lox_Info("{}: {}]", target->Kind(), stit.Node().Name())
978 break;
979 }
980
981 case Target::Macro:
982 default: {
983 const Target* target= stit.Node().Value();
984 Lox_Error("UNHANDLED KIND IN DUMP: ", target->Kind())
985 break;
986 }
988
989 stit.Next();
990} }
991
992} //namespace [dxl]
#define ALIB_ALLOW_SPARSE_ENUM_SWITCH
#define ALIB_CALLER_NULLED
#define ALIB_ASSERT(cond, domain)
#define ALIB_ERROR(domain,...)
#define ALIB_POP_ALLOWANCE
#define ALIB_DBG(...)
#define ALIB_ASSERT_ERROR(cond, domain,...)
#define ALIB_ALLOW_MISSING_FIELD_INITIALIZERS
#define Lox_SetPrefix(...)
#define Lox_Info(...)
#define Lox_Error(...)
#define Lox_SetDomain(...)
#define Lox_GetVerbosity(result,...)
#define Lox_Verbose(...)
#define Lox_Warning(...)
void Initialize(CursorType startNode, lang::Inclusion includeStartNode)
const TStringTree::NameType Path() const
void SetPathGeneration(lang::Switch pathGeneration)
TAString & Delete(integer regionStart, integer regionLength=MAX_LEN)
TAString & DeleteStart(const TString< TChar > &deleteIfMatch)
constexpr integer Length() const
TChar CharAtStart() const
TChar CharAt(integer idx) const
constexpr bool IsNotEmpty() const
integer IndexOf(const TString &needle, integer startIdx=0, integer endIdx=strings::MAX_LEN) const
integer LastIndexOf(TChar needle, integer startIndex=MAX_LEN) const
TChar CharAtEnd() const
TString< TChar > Substring(integer regionStart, integer regionLength=MAX_LEN) const
bool Equals(const TString< TChar > &rhs) const
constexpr bool IsNull() const
bool StartsWith(const TString &needle) const
TSubstring & TrimStart(const TCString< TChar > &whiteSpaces=CStringConstantsTraits< TChar >::DefaultWhitespaces())
TSubstring & Trim(const TCString< TChar > &whiteSpaces=CStringConstantsTraits< TChar >::DefaultWhitespaces())
TSubstring & TrimEnd(const TCString< TChar > &whiteSpaces=CStringConstantsTraits< TChar >::DefaultWhitespaces())
TSubstring< TChar > Actual
TSubstring< TChar > & Next(lang::Whitespaces trimming=lang::Whitespaces::Trim, TChar newDelim='\0')
class DXLApp
Definition dxlapp.hpp:37
Index(const alib::PathString &tagFilePath, const alib::String &baseURL, bool isMainTagFile)
Definition index.cpp:24
alib::MonoAllocator ma
The allocator used for the parent #"StringTree" and for the hashtable in the field map.
Definition index.hpp:50
void DumpTree(Node branch)
Definition tagfile.cpp:906
alib::PathString FilePath
The path to the doxygenTagFile.
Definition index.hpp:227
void ReplaceToTreeSeparator(alib::AString &buffer, Target::Kinds kind, alib::integer startPos=0)
Definition index.cpp:223
void ReplaceFromTreeSeparator(alib::AString &buffer, Target::Kinds kind, alib::integer startPos=0)
Definition index.cpp:230
void loadTagFile()
Loads the tag-file. This is called by Load.
Definition tagfile.cpp:371
int StatCtdLines
Statistics on the number of lines in the tag-file.
Definition index.hpp:381
bool IsA(Kinds aKind) const
Definition target.hpp:241
Kinds Kind() const
Definition target.hpp:236
static size_t MAX_TEMPLATE_ARGS
Definition target.hpp:26
alib::String HTMLFile
The HTML file that this target links to (or into).
Definition target.hpp:220
Kinds
Enumerates the kinds of compounds found in a the Doxygen tagfile.
Definition target.hpp:28
@ COMPOUND
Mask to identify compound types.
Definition target.hpp:71
@ Struct
Denotes a struct.
Definition target.hpp:37
@ Function
Denotes a namespace- or member-function.
Definition target.hpp:47
@ Module
Denotes a C++20 concept.
Definition target.hpp:41
@ Variable
Denotes a namespace- or member-variable.
Definition target.hpp:46
@ Union
Denotes a union.
Definition target.hpp:39
@ Class
Denotes a class.
Definition target.hpp:38
@ UNKNOWN_COMPOUND
Definition target.hpp:54
@ Typedef
Denotes a type definition.
Definition target.hpp:45
@ File
Denotes a source file.
Definition target.hpp:31
@ EnumElement
Denotes an enumeration element.
Definition target.hpp:49
@ Concept
Denotes a C++20 concept.
Definition target.hpp:40
@ Macro
Denotes a preprocessor definition.
Definition target.hpp:44
@ Dir
Denotes a source folder.
Definition target.hpp:30
@ Page
Denotes a page.
Definition target.hpp:32
@ Group
Denotes a group.
Definition target.hpp:33
@ Enumeration
Denotes an enumeration.
Definition target.hpp:48
@ FILEPATH_COMPONENT
A node of a file path (not a doxygen kind).
Definition target.hpp:53
@ Namespace
Denotes a namespace.
Definition target.hpp:36
int LineNo
The line number in the Doxygen tag-file where this entity is defined.
Definition target.hpp:217
TApp & Get()
bool Parse(strings::TSubstring< TChar > &input, TEnum &result)
monomem::TMonoAllocator< lang::HeapAllocator > MonoAllocator
constexpr String NULL_STRING
strings::compatibility::std::IStreamReader IStreamReader
strings::util::TTokenizer< character > Tokenizer
LocalString< 64 > String64
constexpr const String EMPTY_STRING
lang::integer integer
monomem::TLocalAllocator< 2 > LocalAllocator2K
strings::TString< character > String
system::Path Path
strings::TSubstring< character > Substring
LocalString< 1024 > String1K
exceptions::Exception Exception
LocalString< 128 > String128
LocalString< 256 > String256
constexpr PathCharType DIRECTORY_SEPARATOR
strings::TAString< character, lang::HeapAllocator > AString
characters::character character
containers::StringTreeIterator< TTree > StringTreeIterator
LocalString< 512 > String512
todox
Definition doxyfile.cpp:20
@ DuplicateChildName
todox
Definition dxl.hpp:116
@ UnknownKind
todox
Definition dxl.hpp:110
@ XMLSyntaxError
todox
Definition dxl.hpp:111
@ UnexpectedXMLTag
todox
Definition dxl.hpp:113
@ UnexpectedXMLValue
todox
Definition dxl.hpp:118
@ NotATagStart
todox
Definition dxl.hpp:112
void ConvertHTMLEntitiesToAscii(alib::AString &buffer)
Definition dxl.cpp:103
const TGT * Cast(const Index::Node &node)
Definition index.hpp:544
@ TagFileNotFound
Doxygen tag-file not created, yet. Needs a second run.
Definition dxl.hpp:94
constexpr const TChar * Buffer() const noexcept
The cursor type of the #"StringTree".
Definition index.hpp:105
Cursor & AsCursor()
Definition index.hpp:223
Internal struct that parses a tag-file. Used by the method loadTagFile.
Definition index.hpp:255
alib::String nextTag(const alib::String &tagName)
Definition index.hpp:337
TGTMember * readMember(alib::String512 &key, Target::Kinds compoundKind)
todo: add parameter parent and if html file is the same, then re-use instead of allocation.
Definition tagfile.cpp:186
alib::IStreamReader & reader
A referenced to the ALib text file reader tool.
Definition index.hpp:269
alib::Substring tagAttr
The current tag attributes. Set by parseTag.
Definition index.hpp:284
Parser(Index &parent, alib::IStreamReader &pReader, const alib::String &pFilePath)
Definition tagfile.cpp:22
alib::lox::Verbosity verbosity
The log verbosity of the parser (used for performance optimization).
Definition index.hpp:266
alib::ListMA< TGTDocAnchor * > actDocAnchors
Set by nextLine() in case it finds a <docanchor>.
Definition index.hpp:290
void addDocAnchors(Cursor &parent)
Definition tagfile.cpp:63
alib::Substring line
A parser for the currently read line.
Definition index.hpp:281
alib::MonoAllocator & ma
A referenced to the allocator of outer class Index.
Definition index.hpp:263
alib::String allocNextTag(const alib::String &tagName)
Definition index.hpp:342
alib::String allocTag(const alib::String &tagName)
Definition index.hpp:323
int lineNo
The current line number when reading.
Definition index.hpp:275
alib::String2K lineBuf
The buffer of the line currently read.
Definition index.hpp:278
Index & index
our parent
Definition index.hpp:257
alib::String parseTag(const alib::String &tagName)
Definition tagfile.cpp:97
alib::PathString FilePath
The path to the doxygenTagFile.
Definition index.hpp:272
alib::String parseAttr(const alib::String &attrName, bool isOptional=false)
Definition tagfile.cpp:145
alib::integer maxWidth
The maximum line width found.
Definition index.hpp:287
XLink target information for C++ concepts.
Definition target.hpp:334
XLink target information for C++ enum elements.
Definition target.hpp:418
XLink target information for C++ enums.
Definition target.hpp:438
alib::String Type
The underlying type of the enum.
Definition target.hpp:440
XLink target information for C++ namespace- and member-functions.
Definition target.hpp:466
FunctionArguments * Args
The list of arguments.
Definition target.hpp:471
alib::String Type
The return type of the function or method.
Definition target.hpp:468
alib::String Qualifiers
Additional qualifiers like const or nothrow.
Definition target.hpp:474
Target::Kinds Kind
The kind of the member, which was not fully read.
Definition target.hpp:393
XLink target information for C++ preprocessor definitions.
Definition target.hpp:406
alib::String Anchor
The HTML anchor.
Definition target.hpp:375
alib::String RefID
This ID is (sometimes) set if the member is read from a Doxygen group.
Definition target.hpp:378
XLink target information for C++ concepts.
Definition target.hpp:326
XLink target information for C++ namespaces.
Definition target.hpp:342
XLink target information for C++ structs, classes and unions.
Definition target.hpp:350
TemplateArguments * SpecializationArgs
Definition target.hpp:356
TemplateArguments * TemplateArgs
Template arguments provided with tags <templarg>.
Definition target.hpp:352
alib::ListMA< alib::String > BaseTypes
The base type of the record.
Definition target.hpp:359
XLink target information for type definitions.
Definition target.hpp:426
alib::String Type
The type of the definition.
Definition target.hpp:428
XLink target information for C++ namespace- and member-variables.
Definition target.hpp:452
alib::String Subscript
The subscript of the variable.
Definition target.hpp:457
alib::String Type
The type of the variable.
Definition target.hpp:454
void Print(alib::AString &dest)
Definition target.cpp:17
static FunctionArguments * PARSE(alib::MonoAllocator &ma, alib::Substring &parser)
Definition target.cpp:28
int Count
The number of arguments.
Definition target.hpp:142
static TemplateArguments * PARSE(alib::MonoAllocator &ma, alib::Substring &parser)
Definition target.cpp:153
alib::String * Arguments
An array of length Count of strings.
Definition target.hpp:145