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}
63
65 for (auto* actDocAnchor: actDocAnchors) {
66 if ( !parent.Value()->HTMLFile.Equals(actDocAnchor->HTMLFile) ) {
67 if (verbosity >= Verbosity::Info) {
68 Lox_Info("Skipping anchor {!Q} in compound {!Q} of type {}, because it has a\n"
69 "different HTML file-target than its parent.\n"
70 " Compound target: {}\n"
71 " DocAnchor Target: {}\n"
72 " In tag-file: @ {}:{}\n"
73 ,actDocAnchor->Name, parent.Name(), parent.Value()->Kind()
74 ,parent.Value()->HTMLFile
75 ,actDocAnchor ->HTMLFile
76 ,FilePath, actDocAnchor->LineNo + 1)
77 }
78 continue;
79 }
80
81 Cursor newChild= parent.CreateChild(actDocAnchor->Name, actDocAnchor);
82 if ( newChild.IsInvalid() ) {
83 String256 path;
84 parent.AssemblePath(path);
85 Cursor previous= parent.Child(actDocAnchor->Name);
86 Lox_Error( "Doubly defined anchor {!Q} in compound {!Q} of type {} (ignoring)\n"
87 " First definition @ {}:{}\n"
88 " Second definition @ {3}:{}"
89 ,actDocAnchor->Name, parent.Name(), parent.Value()->Kind()
91 ,previous.Value()->LineNo, actDocAnchor->LineNo )
92 }
93
94 }
95 actDocAnchors.Clear();
96}
97
99 tagAttr= nullptr;
100 if (!line.ConsumeChar('<') )
102 if (!line.StartsWith(tagName))
104
105 line.ConsumeChars(tagName.Length());
106
107 // attributes following?
108 integer attrEnd= 1;
109 if ( line.CharAtStart() == ' ' ) {
110 // find next '>' which is not in a string.
111 bool inString= false;
112 for (; attrEnd<line.Length(); ++attrEnd) {
113 character c= line.CharAt<NC>(attrEnd);
114 if (c == '\"' && line.CharAt<NC>(attrEnd - 1) != '\\') {
115 inString= !inString;
116 continue;
117 }
118 if (!inString && c == '>' )
119 break;
120 }
121 if ( attrEnd == line.Length())
123 tagAttr= line.Substring(1, attrEnd-1);
124 line.ConsumeChars<NC>(attrEnd + 1 );
125 if (line.IsEmpty())
126 return line;
127 }
128 else {
129 if (line.CharAtStart() != '>')
131 line.ConsumeChar();
133 }
134
135 if (line.EndsWith(String64("</")._<NC>(tagName)._<NC>('>')) )
136 return line.Substring(0, line.Length()- (tagName.Length() + 3));
137
138 if (!line.EndsWith(">"))
140
141 line.ConsumeCharsFromEnd(1);
142 return line;
143
144}
145
146String Index::Parser::parseAttr(const String& attrName, bool isOptional) {
147 integer idxStart= 0;
148 for (;;) {
149 idxStart= tagAttr.IndexOf(attrName, idxStart);
150 if ( idxStart<0 ) {
151 if ( !isOptional )
152 Lox_Error("Unexpected Doxygen tag-file format: Missing attribute {!Q}\n"
153 " Line read: {}\n"
154 " @ {}:{}\n"
155 ,attrName
156 ,line
158 return nullptr;
159 }
160 if ( tagAttr.CharAt(idxStart + attrName.Length() ) == '='
161 && tagAttr.CharAt(idxStart + attrName.Length() + 1) == '\"' )
162 break;
163 idxStart+= attrName.Length() + 1;
164 }
165 idxStart+= attrName.Length() + 2;
166 bool escaped= false;
167 integer idxEnd= idxStart;
168 for (; idxEnd<tagAttr.Length(); ++idxEnd) {
169 character c= tagAttr.CharAt<NC>(idxEnd);
170 if (!escaped) {
171 if (c == '"')
172 break;
173 if ( c == '\\' )
174 escaped= true;
175 }
176 else
177 escaped= false;
178 }
179 if ( idxEnd == tagAttr.Length() )
182
183 return tagAttr.Substring( idxStart, idxEnd-idxStart);
184}
185
186/// todo: add parameter parent and if html file is the same, then re-use instead of allocation.
188 if (!line.ConsumeString( "<member kind=\"" ))
190 FilePath, lineNo, "member", line);
191 Target::Kinds kind;
192 if (! enumrecords::Parse(line, kind) )
194
195 // parse a refID: todo: I have no samples, yet for this. Not tested!
196 String128 refID;
197 line.ConsumeChar('"');
198 line.TrimStart();
199 if (line.ConsumeString("refid=\"")) {
200 refID = line.ConsumeToken('"');
201 }
202
203 key.Reset();
204
205 // pre-processor defines
206
207 if ( kind == Target::Macro ) {
208 int lNo= lineNo;
209 ALIB_DBG(String type=)
210 nextTag("type");
211 ALIB_ASSERT_ERROR(type.Equals("#define"), "DXL/TAGFILE", "Expected '#define' as type")
212 key << nextTag("name");
213 auto* member = ma().New<TGTMacro>(allocNextTag("anchorfile"), lNo);
214 member->Anchor = allocNextTag("anchor");
215 Substring argList = nextTag("arglist");
216 member->Args = Target::FunctionArguments::PARSE(ma, argList);
217 nextLine();
218 if (refID.IsNotEmpty())
219 member->RefID= String(ma, refID);
220 return member;
221 }
222
223 // typedef
224 if ( kind == Target::Typedef ) {
225 int lNo= lineNo;
226 String type = allocNextTag("type");
227 key << nextTag("name");
228
229 TGTTypedef* member= ma().New<TGTTypedef>(allocNextTag("anchorfile"), lNo);
230 member->Type = type;
231 member->Anchor= allocNextTag("anchor");
232 if ( nextTag("arglist").IsNotEmpty() )
234 nextLine();
235 if (refID.IsNotEmpty())
236 member->RefID= String(ma, refID);
237 return member;
238 }
239
240 // typedef or enumeration
241 if ( kind == Target::Enumeration ) {
242
243 int lNo= lineNo;
244 String type = allocNextTag("type");
245 key << nextTag("name");
246
247 TGTEnumeration* member= ma().New<TGTEnumeration>(allocNextTag("anchorfile"), lNo);
248 member->Type = type;
249 member->Anchor= allocNextTag("anchor");
250 if ( nextTag("arglist").IsNotEmpty() )
252 nextLine();
253 if (line.StartsWith("<enumvalue")) {
254 // the caller has to check whether line is "<enumvalue..." and add each value as its child.
255 return member;
256 }
257 if (refID.IsNotEmpty())
258 member->RefID= String(ma, refID);
259 return member;
260 }
261
262 // enum value
263 if ( kind == Target::EnumElement ) {
264 int lNo= lineNo;
265 key << nextTag("name");
266 TGTEnumValue* member= ma().New<TGTEnumValue>(allocNextTag("anchorfile"), lNo);
267 member->Anchor = allocNextTag("anchor");
268 if ( nextTag("arglist").IsNotEmpty() )
270
271 nextLine();
272 if (!line.StartsWith("</member")) {
273 if ( kind != Target::Enumeration )
275
276 // the caller has to check whether line is "<enumvalue..." and add each value as its child.
277 return member;
278 }
279
280 if (refID.IsNotEmpty())
281 member->RefID= String(ma, refID);
282 return member;
283 }
284
285 // variables
286 if ( kind == Target::Variable ) {
287 int lNo= lineNo;
288 String1K argList;
289 String type= allocNextTag("type");
290 key << nextTag("name");
291 TGTVariable* member= ma().New<TGTVariable>(allocNextTag("anchorfile"), lNo);
292 member->Type = type;
293 member->Anchor = allocNextTag("anchor");
294 member->Subscript= allocNextTag("arglist");
295
296 nextLine();
297 if (!line.Equals("</member>"))
299
300 // checks on variables
301 if ( argList.IsNotEmpty() && argList.CharAtStart()!='[' )
302 throw Exception(ALIB_CALLER_NULLED, Exceptions::UnexpectedXMLValue, FilePath,lineNo, "arglist starting with '['", argList);
303 if (!line.Equals("</member>"))
305
306 if (refID.IsNotEmpty())
307 member->RefID= String(ma, refID);
308 return member;
309 }
310
311 // functions
312 if ( kind == Target::Function ) {
313 int lNo= lineNo;
314 String type= allocNextTag("type");
315 key << nextTag("name");
316 TGTFunction* member= ma().New<TGTFunction>(allocNextTag("anchorfile"), lNo);
317 member->Type= type;
318 member->Anchor= allocNextTag("anchor");
319
320 Substring argList= nextTag("arglist");
321 if ( argList.IsNotEmpty() && !argList.Equals("()") ) {
322 if (argList.CharAtStart() != '(' )
324 "arglist starting with '('", argList);
325 member->Args= Target::FunctionArguments::PARSE(ma, argList);
326 if (member->Args == nullptr)
329 member->Args->Print(key);
330 if (argList.Trim().IsNotEmpty())
331 member->Qualifiers= String(ma, argList);
332 }
333
334 if ( member->Qualifiers.IsNotEmpty() )
335 key << ' ' << member->Qualifiers;
336
337 nextLine();
338 if (!line.Equals("</member>"))
340 "/member", line);
341 if (refID.IsNotEmpty())
342 member->RefID= String(ma, refID);
343 return member;
344 }
345
346 // warn if not a group-member (and not fetched above)
347 if (compoundType != Target::Group && verbosity >= Verbosity::Warning)
348 Lox_Warning("Unexpected member type {} in compound of type {}. Ignoring.",
349 kind, compoundType)
350
351 // read generic (group) members
353 if (refID.IsNotEmpty())
354 member->RefID= String(ma, refID);
355 for (;;) {
356 nextLine();
357 if (line.Equals("</member>"))
358 break;
359 if (line.StartsWith("<name")) key << nextTag("name");
360 else if (line.StartsWith("<anchorfile")) member->HTMLFile= allocNextTag("anchorfile");
361 else if (line.StartsWith("<anchor")) member->Anchor = allocTag("anchor");
362 }
363 if (key.IsNotEmpty())
364 return member;
365
366 Lox_Warning("Unnamed (generic) member of type {} in compound of type {}. Ignoring.",
367 member->Kind, compoundType)
368 return nullptr;
369}
370
371
373 Lox_SetDomain("DXL/TAGFILE", Scope::Filename )
374 Lox_Info("Reading tag-file: ", FilePath )
375 static TGTFilePathComponent filePathComponentTag(-1);
376
377 errno= 0;
378 std::ifstream ifstream;
379 ifstream.open( Path(FilePath).Terminate() );
380
382 if ( !ifstream.is_open() || errno ) {
383 app.cErr->Add(app.cli.ExitCodeDecls.Find(ExitCodes::TagFileNotFound).Mapped()->FormatString(),
384 FilePath, app.DoxyfilePath);
385 app.machine.SetExitCode(ExitCodes::TagFileNotFound);
386 throw Exception( ALIB_CALLER_NULLED, app::App::Exceptions::ControlledEarlyExit );
387 }
388
389 IStreamReader reader(1024,1024);
390 reader.SetStream( &ifstream );
391
393 Parser parser(*this, reader, FilePath);
395 // we want maximum speed and even spare the fast log calls in the loop.
396 Lox_GetVerbosity(parser.verbosity)
397
398 // skip 1st line
399 parser.nextLine();
400 ALIB_ASSERT_ERROR(parser.line.StartsWith("<?xml version='1.0'"), "DXL/TAGFILE", "Unknown file format")
401
402 // print doxygen version
403 parser.nextTag("tagfile");
404 Lox_Info("Doxygen version: ", parser.getAttr("doxygen_version") )
405 while( !reader.IsEOF() ) {
406 // read line
407 parser.nextLine();
408
409 if (parser.line.Equals("</tagfile>"))
410 break;
411
412 // not a compound?
413 if ( parser.tryTag("compound").IsNull() ) {
415 FilePath, parser.lineNo, "compound", parser.line);
416 }
417
418 //========== new compound ==========
419 Target::Kinds kind;
420 Substring kindStr= parser.getAttr("kind");
421 if (! enumrecords::Parse( kindStr, kind ) )
423 FilePath, parser.lineNo, parser.lineBuf );
424
425 //Lox_Warning("{} @ {}:{}", kind, filePath, lineNo)
426
427 //-------------------------- Compound struct/class/union ---------------------------------
428 if ( kind & Target::COMPOUND ) {
429 String1K compoundTagName = parser.nextTag("name");
430 String compoundHtmlFile= parser.allocNextTag("filename");
431
432 // local collection of template args given with tag <templarg>.
433 // Will be added on finalization to TGT
435 String* templateArgs= la().NewArray<String>(Target::MAX_TEMPLATE_ARGS);
436 int templateArgsSize= 0;
437
438
439 // cut off template specialization args"<..." from name.
440 Substring compoundNameWithoutTemplateParams= compoundTagName;
441 Substring templateSpecializationArgs= nullptr;
442 if ( compoundTagName.CharAtEnd<NC>() == '>') {
443 integer templateStart= compoundTagName.IndexOf('<');
444 ALIB_ASSERT(templateStart>0, "DXL/TAGFILE")
445 compoundNameWithoutTemplateParams= compoundTagName.Substring(0, templateStart);
446 compoundNameWithoutTemplateParams.TrimEnd();
447 templateSpecializationArgs = compoundTagName.Substring(templateStart);
448 templateSpecializationArgs .TrimStart();
449 }
450
451 // extract the path to the component by finding the last "::"
452 String compoundPathPortion= nullptr;
453 integer lastPathPos= compoundNameWithoutTemplateParams.LastIndexOf(':');
454 if ( lastPathPos>=0 ) {
455 ALIB_ASSERT_ERROR(compoundTagName.CharAt(lastPathPos -1) == ':', "DXL/TAGFILE",
456 "Single colon ':' found in compound name {!Q}", compoundTagName )
457 compoundPathPortion= compoundTagName.Substring(0, lastPathPos -1);
458 compoundNameWithoutTemplateParams= compoundNameWithoutTemplateParams.Substring(lastPathPos + 1);
459 }
460
461 // create the path to the new component.
462 Cursor pathCursor = Root();
463 if ( compoundPathPortion.IsNotEmpty() ) {
464 Tokenizer tok(compoundPathPortion, ':');
465 while (tok.HasNext()) {
466 String pathComponent= tok.Next();
467 Cursor component= pathCursor.CreateChild(pathComponent, nullptr);
468 // If it did not exist, we create an unknown component. A compound will
469 // hopefully replace this later. This may happen because "deeper" compounds
470 // can come first in doxygen tag-files.
471 if ( component.IsValid()) {
472 component.Value()= ma().New<TGTFilePathComponent>(parser.lineNo);
473 pathCursor= component;
474 }
475 else
476 pathCursor.GoToChild(tok.Actual);
477
478 // skip the next ':'
479 tok.Next();
480 }
481 }
482
483 // Now create the child itself. Template parameters have to be included in the key.
484 String compoundKey= compoundPathPortion.IsNotEmpty()
485 ? compoundTagName.Substring(compoundPathPortion.Length() + 2)
486 : compoundTagName;
487 Cursor compoundCursor= pathCursor.CreateChild(compoundKey, nullptr);
488 if ( compoundCursor.IsInvalid()) {
489 compoundCursor= pathCursor.Child(compoundKey);
490
491 if (!compoundCursor.Value()->IsA(Target::UNKNOWN_COMPOUND | Target::FILEPATH_COMPONENT))
493 compoundKey, compoundPathPortion, FilePath,
494 pathCursor.Child(compoundKey).Value()->LineNo, parser.lineNo);
495 }
496
497 // Add Namespace or Struct/Class/Union tag
498 TGTNamespace* tagNamespace= nullptr;
499 TGTRecord* tagRecord= nullptr;
500 if ( kind == Target::Namespace) {
501 compoundCursor.Value()=
502 tagNamespace= ma().New<TGTNamespace>(compoundHtmlFile, parser.lineNo);
503 ALIB_ASSERT(templateSpecializationArgs.IsNull(), "DXL/TAGFILE")
504 }
505 else {
506 compoundCursor.Value()=
507 tagRecord= ma().New<TGTRecord>(ma, kind, compoundHtmlFile, parser.lineNo);
508 // Todo:
509 // The name is internally allocated, hence the template args could be just stored.
510 // We could do the same on the XLink-side by allocating the string to parse.
511 // This would spare us the allocation.
512 // Also: both types, TemplateArguments and FunctionArguments should use a
513 // forward list.
514 // but it is ok as it is...
515 if ( templateSpecializationArgs.IsNotEmpty())
516 tagRecord->SpecializationArgs= Target::TemplateArguments::PARSE(ma, templateSpecializationArgs);
517 }
518
519
520 //----------------- read members ---------------
521 for(;;) {
522 parser.nextLine();
523 parser.addDocAnchors(compoundCursor);
524
525 if ( parser.line.StartsWith("<member kind=") ) {
526 String512 key;
527 TGTMember* member= parser.readMember(key, kind);
528 if (!member) continue;
529 //Dump(compoundCursor);
530 Cursor memberChild= compoundCursor.CreateChild(key, member);
531 if ( memberChild.IsInvalid() ) {
532 Target* existingValue= compoundCursor.Child(key).Value();
533 if ( member->IsA(Target::Function) ) {
534 // this is just strange: sometimes doxygen adds the very same
535 // member twice. We just ignore that.
536 // However, if just the anchor is different, then either DXL is
537 // wrong or a newer doxygen version does something different.
538 // Thus we throw.
539 if ( !existingValue->IsA(Target::Function)
540 && !Cast<TGTFunction,NC>(existingValue)->Anchor.Equals(member->Anchor)) {
541 Lox_Error("Doubly defined member function.\n"
542 "This happens with overloaded template functions that use keyword requires.\n"
543 "To fix this, dox only one function and include in the dox that\n"
544 "different versions for different requirements exist.\n"
545 " Function: {}::{}.\n"
546 " Tag-file first occurence: {}:{}.\n"
547 " Tag-file this occurence: {2}:{}.\n"
548 , compoundTagName, key, FilePath
549 , existingValue->LineNo
550 , parser.lineNo )
552 key, compoundTagName, FilePath,
553 existingValue->LineNo, member->LineNo);
554 }
555 }
556 else if ( member->IsA(Target::EnumElement)) {
557 // enum values of non-class enums appear doubly in the tag-file.
558 // We throw only in the case that the previous definition was
559 // different.
560 if ( !existingValue->IsA(Target::EnumElement)
561 || !existingValue->HTMLFile.Equals(member->HTMLFile)
562 || !Cast<TGTMember,NC>(existingValue)->Anchor.Equals(member->Anchor) )
564 key, compoundPathPortion, FilePath,
565 existingValue->LineNo,
566 member ->LineNo);
567
568 }
569 else
571 key, compoundPathPortion, FilePath,
572 existingValue->LineNo,
573 member ->LineNo);
574 }
575
576 // add the doc anchor(s) if present
577 parser.addDocAnchors(memberChild);
578
579 // if member type is enum, enum values might come now
580 while (parser.line.StartsWith("<enumvalue")) {
581 int lNo= parser.lineNo;
582 String256 elemName= parser.parseTag("enumvalue");
583 TGTEnumValue* enumElementTag= ma().New<TGTEnumValue>(
584 String(ma, parser.parseAttr("file")), lNo );
585 enumElementTag->Anchor = String(ma, parser.parseAttr("anchor"));
586 Cursor elementCursor= memberChild.CreateChild(elemName, enumElementTag);
587 if ( elementCursor.IsInvalid() ) {
589 elemName, compoundPathPortion, FilePath,
590 memberChild.Child(elemName).Value()->LineNo, parser.lineNo );
591 }
592 parser.nextLine();
593 }
594
595 // now we expect the end of the member
596 if (!parser.line.Equals("</member>"))
598 FilePath,parser.lineNo, "/member", parser.line);
599
600 continue;
601 }
602
603 // Tag <templarg> gives the template parameters of the type.
604 // Not that specializations have their template parameters encoded in their name!
605 if ( parser.line.StartsWith("<templarg" ) ){
606 templateArgs[templateArgsSize++]=
607 String(static_cast<MonoAllocator&>(la), parser.parseTag("templarg"));
608 // todo: ^^^^why do we need to cast down here?
609 continue;
610 }
611
612 // ignore simple "announcements" of inner types
613 if ( parser.line.StartsWith("<class" )
614 && parser.line.EndsWith( "</class>") ) continue;
615
616 if ( parser.line.Equals( "</compound>") ) break;
617
618 // ignored tags
619 if ( parser.line.StartsWith("<base" ) ) {
620 ALIB_ASSERT_ERROR(tagRecord, "DXL/TAGFILE",
621 "Base type information found for non-record type")
622 tagRecord->BaseTypes.push_back(parser.allocTag("base"));
623 continue;
624 }
625
626 if (kind == Target::Namespace) {
627 if ( parser.line.StartsWith("<namespace" ) ) continue;
628 if ( parser.line.StartsWith("<concept" ) ) continue;
629 }
630
632 FilePath,parser.lineNo, "/compound", parser.line);
633 }
634
635 // add collected template args
636 if (templateArgsSize) {
637 ALIB_ASSERT_ERROR(tagRecord, "DXL/TAGFILE",
638 "Template args found for non-record type")
639 tagRecord->TemplateArgs= ma().New<Target::TemplateArguments>();
640 tagRecord->TemplateArgs->Count= templateArgsSize;
641 tagRecord->TemplateArgs->Arguments= ma().NewArray<String>(templateArgsSize);
642 for (int i= 0; i<templateArgsSize; ++i)
643 tagRecord->TemplateArgs->Arguments[i]= String(ma, templateArgs[i]);
644 }
645 }
646
647 //------------------------------- Compound Dir --------------------------------------
648 else if ( kind == Target::Dir ){
649 String256 path;
650 Cursor dirCursor = Root();
651 String256 name = parser.nextTag("name");
652 path = parser.nextTag("path");
653 ReplaceToTreeSeparator(path, kind);
654 dirCursor.GoToCreatedPathIfNotExistent(path, &filePathComponentTag);
655
656 // create node with TDir : Tag
657 dirCursor= dirCursor.CreateChild(name, ma().New<TGTDir>( parser.allocNextTag("filename"),
658 parser.lineNo ));
659
660 for(;;) {
661 parser.nextLine();
662 parser.addDocAnchors(dirCursor);
663
664
665 // ------------------- global members -----------
666 if ( parser.line.StartsWith("<file>") ) continue;
667 if ( parser.line.StartsWith("<dir>") ) continue;
668 if ( parser.line.Equals( "</compound>") )
669 break;
670
671 // todo:don't throw, just warn
673 FilePath,parser.lineNo, "/compound", parser.line);
674 }
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() ) == system::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
770 //------------------------------- Compound Group --------------------------------------
771 else if (kind == Target::Group){
772 String128 name = parser.nextTag("name");
773 String title = parser.allocNextTag("title");
774
775 // create compound entry
776 Cursor groupCursor= Root().CreateChild(name, ma().New<TGTGroup>(
777 parser.allocNextTag("filename"), parser.lineNo, title));
778 for(;;) {
779 parser.nextLine();
780 parser.addDocAnchors(groupCursor);
781
782 // ------------------- global members -----------
783 if ( parser.line.StartsWith("<member kind=") ) {
784 String512 key;
785 TGTMember* member= parser.readMember(key, kind);
786 if (!member) continue;
787
788 Cursor memberChild= groupCursor.CreateChild(key, member);
789 if ( memberChild.IsInvalid() ) {
790 String256 path;
791 groupCursor.AssemblePath(path);
792 Cursor previous= groupCursor.Child(key);
793 Lox_Warning("Doubly defined member {!Q} in tag-file path {}:1\n"
794 " First definition @ {}:{}\n"
795 " Second definition @ {2}:{}"
796 ,key, path, FilePath
797 ,previous.Value()->LineNo, member->LineNo )
798 }
799
800 // add the doc anchor(s) if present
801 parser.addDocAnchors(memberChild);
802
803 continue;
804 }
805
806 if ( parser.line.Equals( "</compound>") )
807 break;
808
809 // we are not interested in the the other contents of file compounds.
810 // we just check if there is something unexpected
811 //if ( parser.line.StartsWith("<includes" ) ) continue;
812 //if ( parser.line.StartsWith("<concept" ) ) continue;
813 //if ( parser.line.StartsWith("<class" ) ) continue;
814 //if ( parser.line.StartsWith("<namespace" ) ) continue;
815
817 FilePath,parser.lineNo, "/compound", parser.line);
818 }
819 }
820
821 //------------------------------- Compound Page --------------------------------------
822 else if (kind == Target::Page){
823 String128 name = parser.nextTag("name");
824 String title = parser.allocNextTag("title");
825
826 // create compound entry
827 Cursor pageCursor= Root().CreateChild(name, ma().New<TGTPage>(
828 parser.allocNextTag("filename"), parser.lineNo, title));
829
830 for(;;) {
831 parser.nextLine(); // this reads anchors!
832 parser.addDocAnchors(pageCursor);
833
834 if ( parser.line.Equals( "</compound>") )
835 break;
836
838 FilePath,parser.lineNo, "/compound", parser.line);
839 }
840 }
841
842 //------------------------------- C++ 20 Concepts --------------------------------------
843 else if (kind == Target::Concept) {
844 String256 name = parser.nextTag("name");
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 concept {!Q}\n"
856 " First definition @ {}:{}\n"
857 " Second definition @ {2}:{}",
858 name, FilePath, previous.Value()->LineNo, parser.lineNo)
859 }
860
861 // create node with TGTConcept value
862 cursor.Value()= ma().New<TGTConcept>(htmlFile, parser.lineNo);
863 parser.nextLine();
864 if (!parser.line.Equals("</compound>"))
866 FilePath, parser.lineNo, "/compound", parser.line);
867 }
868
869 else
871 FilePath,parser.lineNo, kind);
872
873 }
874
875 StatCtdLines= parser.lineNo;
876 Lox_Verbose("Finished reading tag-file: {}, maximum line width: ", FilePath, parser.maxWidth )
877 ALIB_ASSERT_ERROR(parser.maxWidth < 1024, "DXL/TAGFILE", "Line buffer to small.")
878}
879
881 if ( branch.IsInvalid())
882 branch= Root();
884 AString actIndent;
885 Lox_SetDomain("DUMP", Scope::Method )
886 Lox_SetPrefix( std::reference_wrapper(actIndent) )
887
888 Lox_Info("Dumping tag-file: " )
890 stit.SetPathGeneration(lang::Switch::On);
891 stit.Initialize(branch.AsCursor(), lang::Inclusion::Exclude);
892 while (stit.IsValid()) {
894 // ReSharper disable once CppDFAUnreachableCode
895 Target& tag= *stit.Node().Value();
896 actIndent.Reset(String(spaces.Buffer(), stit.CurrentDepth()*4 ));
897 switch (tag.Kind()) {
899 Lox_Warning( "Unknown Compound: {}::{}\n"
900 " Documented in: {}\n"
901 " Tag-file location: {}:{}",
902 stit.Path(), stit.Node().Name(),
903 tag.HTMLFile, FilePath, tag.LineNo )
904 break;
905
908 } break;
909
910 case Target::File: {
911 auto& data= *Cast<TGTFile,NC>(stit.Node());
912 Lox_Info("File: ", data.HTMLFile)
913 } break;
914
915 case Target::Variable:
916 Lox_Info("Variable: ", stit.Node().Name())
917 break;
918 case Target::Function:
919 Lox_Info("Method: ", stit.Node().Name())
920 break;
921 case Target::Typedef:
922 Lox_Info("Typedef: ", stit.Node().Name())
923 break;
925 Lox_Info("Enum: ", stit.Node().Name())
926 break;
928 Lox_Info("Elem: ", stit.Node().Name())
929 break;
930 case Target::Page:
931 case Target::Group:
932 ALIB_ERROR("DXL/DUMP", "Page/Group should not be read today" )
933 break;
934
935 case Target::Namespace: {
936 const TGTNamespace* target= Cast<TGTNamespace,NC>(stit.Node());
937 Lox_Info("{}: ", target->Kind(), stit.Node().Name())
938 break;
939 }
940 case Target::Struct:
941 case Target::Class:
942 case Target::Union: {
943 const TGTRecord* target= Cast<TGTRecord,NC>(stit.Node());
944 Lox_Info("{}: {}<{}> : {}]", target->Kind(),
945 stit.Node().Name(), target)
946 break;
947 }
948
949 case Target::Concept:{
950 const TGTConcept* target= Cast<TGTConcept,NC>(stit.Node());
951 Lox_Info("{}: {}]", target->Kind(), stit.Node().Name())
952 break;
953 }
954
955 case Target::Macro:
956 default: {
957 const Target* target= stit.Node().Value();
958 Lox_Error("UNHANDLED KIND IN DUMP: ", target->Kind())
959 break;
960 }
962
963 stit.Next();
964 }
965}
966
967} //namespace [dxl]
968
#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
alib::system::PathString FilePath
The path to the doxygenTagFile.
Definition index.hpp:227
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:880
void ReplaceToTreeSeparator(alib::AString &buffer, Target::Kinds kind, alib::integer startPos=0)
Definition index.cpp:219
void ReplaceFromTreeSeparator(alib::AString &buffer, Target::Kinds kind, alib::integer startPos=0)
Definition index.cpp:226
void loadTagFile()
Loads the tag-file. This is called by Load.
Definition tagfile.cpp:372
int StatCtdLines
Statistics on the number of lines in the tag-file.
Definition index.hpp:381
Index(const alib::system::PathString &tagFilePath, const alib::String &baseURL, bool isMainTagFile)
Definition index.cpp:23
bool IsA(Kinds aKind) const
Definition target.hpp:240
Kinds Kind() const
Definition target.hpp:235
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:219
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:70
@ Struct
Denotes a struct.
Definition target.hpp:37
@ Function
Denotes a namespace- or member-function.
Definition target.hpp:46
@ Variable
Denotes a namespace- or member-variable.
Definition target.hpp:45
@ Union
Denotes a union.
Definition target.hpp:39
@ Class
Denotes a class.
Definition target.hpp:38
@ UNKNOWN_COMPOUND
Definition target.hpp:53
@ Typedef
Denotes a type definition.
Definition target.hpp:44
@ File
Denotes a source file.
Definition target.hpp:31
@ EnumElement
Denotes an enumeration element.
Definition target.hpp:48
@ Concept
Denotes a C++20 concept.
Definition target.hpp:40
@ Macro
Denotes a preprocessor definition.
Definition target.hpp:43
@ 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:47
@ FILEPATH_COMPONENT
A node of a file path (not a doxygen kind).
Definition target.hpp:52
@ 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:216
TApp & Get()
bool Parse(strings::TSubstring< TChar > &input, TEnum &result)
constexpr PathCharType DIRECTORY_SEPARATOR
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
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:104
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
alib::system::PathString FilePath
The path to the doxygenTagFile.
Definition index.hpp:272
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:187
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:64
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:98
alib::String parseAttr(const alib::String &attrName, bool isOptional=false)
Definition tagfile.cpp:146
alib::integer maxWidth
The maximum line width found.
Definition index.hpp:287
XLink target information for C++ concepts.
Definition target.hpp:327
XLink target information for C++ enum elements.
Definition target.hpp:411
XLink target information for C++ enums.
Definition target.hpp:431
alib::String Type
The underlying type of the enum.
Definition target.hpp:433
XLink target information for C++ namespace- and member-functions.
Definition target.hpp:459
FunctionArguments * Args
The list of arguments.
Definition target.hpp:464
alib::String Type
The return type of the function or method.
Definition target.hpp:461
alib::String Qualifiers
Additional qualifiers like const or nothrow.
Definition target.hpp:467
Target::Kinds Kind
The kind of the member, which was not fully read.
Definition target.hpp:386
XLink target information for C++ preprocessor definitions.
Definition target.hpp:399
alib::String Anchor
The HTML anchor.
Definition target.hpp:368
alib::String RefID
This ID is (sometimes) set if the member is read from a Doxygen group.
Definition target.hpp:371
XLink target information for C++ namespaces.
Definition target.hpp:335
XLink target information for C++ structs, classes and unions.
Definition target.hpp:343
TemplateArguments * SpecializationArgs
Definition target.hpp:349
TemplateArguments * TemplateArgs
Template arguments provided with tags <templarg>.
Definition target.hpp:345
alib::ListMA< alib::String > BaseTypes
The base type of the record.
Definition target.hpp:352
XLink target information for type definitions.
Definition target.hpp:419
alib::String Type
The type of the definition.
Definition target.hpp:421
XLink target information for C++ namespace- and member-variables.
Definition target.hpp:445
alib::String Subscript
The subscript of the variable.
Definition target.hpp:450
alib::String Type
The type of the variable.
Definition target.hpp:447
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:141
static TemplateArguments * PARSE(alib::MonoAllocator &ma, alib::Substring &parser)
Definition target.cpp:155
alib::String * Arguments
An array of length Count of strings.
Definition target.hpp:144