1// Copyright (C) 2021 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3
4#include "webxmlgenerator.h"
5
6#include "aggregate.h"
7#include "collectionnode.h"
8#include "config.h"
9#include "helpprojectwriter.h"
10#include "node.h"
11#include "propertynode.h"
12#include "qdocdatabase.h"
13#include "quoter.h"
14#include "utilities.h"
15
16#include <QtCore/qxmlstream.h>
17
18QT_BEGIN_NAMESPACE
19
20using namespace Qt::StringLiterals;
21
22static CodeMarker *marker_ = nullptr;
23
24WebXMLGenerator::WebXMLGenerator(FileResolver& file_resolver) : HtmlGenerator(file_resolver) {}
25
26void WebXMLGenerator::initializeGenerator()
27{
28 HtmlGenerator::initializeGenerator();
29}
30
31void WebXMLGenerator::terminateGenerator()
32{
33 HtmlGenerator::terminateGenerator();
34}
35
36QString WebXMLGenerator::format()
37{
38 return "WebXML";
39}
40
41QString WebXMLGenerator::fileExtension() const
42{
43 // As this is meant to be an intermediate format,
44 // use .html for internal references. The name of
45 // the output file is set separately in
46 // beginSubPage() calls.
47 return "html";
48}
49
50/*!
51 Most of the output is generated by QDocIndexFiles and the append() callback.
52 Some pages produce supplementary output while being generated, and that's
53 handled here.
54*/
55qsizetype WebXMLGenerator::generateAtom(const Atom *atom, const Node *relative, CodeMarker *marker)
56{
57 if (m_supplement && currentWriter)
58 addAtomElements(writer&: *currentWriter.data(), atom, relative, marker);
59 return 0;
60}
61
62void WebXMLGenerator::generateCppReferencePage(Aggregate *aggregate, CodeMarker * /* marker */)
63{
64 QByteArray data;
65 QXmlStreamWriter writer(&data);
66 writer.setAutoFormatting(true);
67 beginSubPage(node: aggregate, fileName: Generator::fileName(node: aggregate, extension: "webxml"));
68 writer.writeStartDocument();
69 writer.writeStartElement(qualifiedName: "WebXML");
70 writer.writeStartElement(qualifiedName: "document");
71
72 generateIndexSections(writer, node: aggregate);
73
74 writer.writeEndElement(); // document
75 writer.writeEndElement(); // WebXML
76 writer.writeEndDocument();
77
78 out() << data;
79 endSubPage();
80}
81
82void WebXMLGenerator::generatePageNode(PageNode *pn, CodeMarker * /* marker */)
83{
84 QByteArray data;
85 currentWriter.reset(other: new QXmlStreamWriter(&data));
86 currentWriter->setAutoFormatting(true);
87 beginSubPage(node: pn, fileName: Generator::fileName(node: pn, extension: "webxml"));
88 currentWriter->writeStartDocument();
89 currentWriter->writeStartElement(qualifiedName: "WebXML");
90 currentWriter->writeStartElement(qualifiedName: "document");
91
92 generateIndexSections(writer&: *currentWriter.data(), node: pn);
93
94 currentWriter->writeEndElement(); // document
95 currentWriter->writeEndElement(); // WebXML
96 currentWriter->writeEndDocument();
97
98 out() << data;
99 endSubPage();
100}
101
102void WebXMLGenerator::generateExampleFilePage(const Node *en, ResolvedFile resolved_file, CodeMarker* /* marker */)
103{
104 // TODO: [generator-insufficient-structural-abstraction]
105
106 QByteArray data;
107 QXmlStreamWriter writer(&data);
108 writer.setAutoFormatting(true);
109 beginSubPage(node: en, fileName: linkForExampleFile(path: resolved_file.get_query(), fileExt: "webxml"));
110 writer.writeStartDocument();
111 writer.writeStartElement(qualifiedName: "WebXML");
112 writer.writeStartElement(qualifiedName: "document");
113 writer.writeStartElement(qualifiedName: "page");
114 writer.writeAttribute(qualifiedName: "name", value: resolved_file.get_path());
115 writer.writeAttribute(qualifiedName: "href", value: linkForExampleFile(path: resolved_file.get_path()));
116 QString title = exampleFileTitle(relative: static_cast<const ExampleNode *>(en), fileName: resolved_file.get_path());
117 writer.writeAttribute(qualifiedName: "title", value: title);
118 writer.writeAttribute(qualifiedName: "fulltitle", value: title);
119 writer.writeAttribute(qualifiedName: "subtitle", value: resolved_file.get_path());
120 writer.writeStartElement(qualifiedName: "description");
121
122 if (Config::instance().get(CONFIG_LOCATIONINFO).asBool()) {
123 writer.writeAttribute(qualifiedName: "path", value: resolved_file.get_path());
124 writer.writeAttribute(qualifiedName: "line", value: "0");
125 writer.writeAttribute(qualifiedName: "column", value: "0");
126 }
127
128 Quoter quoter;
129 Doc::quoteFromFile(location: en->doc().location(), quoter, resolved_file);
130 QString code = quoter.quoteTo(docLocation: en->location(), command: QString(), pattern: QString());
131 writer.writeTextElement(qualifiedName: "code", text: trimmedTrailing(string: code, prefix: QString(), suffix: QString()));
132
133 writer.writeEndElement(); // description
134 writer.writeEndElement(); // page
135 writer.writeEndElement(); // document
136 writer.writeEndElement(); // WebXML
137 writer.writeEndDocument();
138
139 out() << data;
140 endSubPage();
141}
142
143void WebXMLGenerator::generateIndexSections(QXmlStreamWriter &writer, Node *node)
144{
145 marker_ = CodeMarker::markerForFileName(fileName: node->location().filePath());
146 auto qdocIndexFiles = QDocIndexFiles::qdocIndexFiles();
147 if (qdocIndexFiles) {
148 qdocIndexFiles->generateIndexSections(writer, node, post: this);
149 // generateIndexSections does nothing for groups, so handle them explicitly
150 if (node->isGroup())
151 qdocIndexFiles->generateIndexSection(writer, node, post: this);
152 }
153}
154
155// Handles callbacks from QDocIndexFiles to add documentation to node
156void WebXMLGenerator::append(QXmlStreamWriter &writer, Node *node)
157{
158 Q_ASSERT(marker_);
159
160 writer.writeStartElement(qualifiedName: "description");
161 if (Config::instance().get(CONFIG_LOCATIONINFO).asBool()) {
162 writer.writeAttribute(qualifiedName: "path", value: node->doc().location().filePath());
163 writer.writeAttribute(qualifiedName: "line", value: QString::number(node->doc().location().lineNo()));
164 writer.writeAttribute(qualifiedName: "column", value: QString::number(node->doc().location().columnNo()));
165 }
166
167 if (node->isTextPageNode())
168 generateRelations(writer, node);
169
170 if (node->isModule()) {
171 writer.writeStartElement(qualifiedName: "generatedlist");
172 writer.writeAttribute(qualifiedName: "contents", value: "classesbymodule");
173 auto *cnn = static_cast<CollectionNode *>(node);
174
175 if (cnn->hasNamespaces()) {
176 writer.writeStartElement(qualifiedName: "section");
177 writer.writeStartElement(qualifiedName: "heading");
178 writer.writeAttribute(qualifiedName: "level", value: "1");
179 writer.writeCharacters(text: "Namespaces");
180 writer.writeEndElement(); // heading
181 NodeMap namespaces;
182 cnn->getMemberNamespaces(out&: namespaces);
183 generateAnnotatedList(writer, relative: node, nodeMap: namespaces);
184 writer.writeEndElement(); // section
185 }
186 if (cnn->hasClasses()) {
187 writer.writeStartElement(qualifiedName: "section");
188 writer.writeStartElement(qualifiedName: "heading");
189 writer.writeAttribute(qualifiedName: "level", value: "1");
190 writer.writeCharacters(text: "Classes");
191 writer.writeEndElement(); // heading
192 NodeMap classes;
193 cnn->getMemberClasses(out&: classes);
194 generateAnnotatedList(writer, relative: node, nodeMap: classes);
195 writer.writeEndElement(); // section
196 }
197 writer.writeEndElement(); // generatedlist
198 }
199
200 m_inLink = m_inSectionHeading = m_hasQuotingInformation = false;
201
202 const Atom *atom = node->doc().body().firstAtom();
203 while (atom)
204 atom = addAtomElements(writer, atom, relative: node, marker: marker_);
205
206 QList<Text> alsoList = node->doc().alsoList();
207 supplementAlsoList(node, alsoList);
208
209 if (!alsoList.isEmpty()) {
210 writer.writeStartElement(qualifiedName: "see-also");
211 for (const auto &item : alsoList) {
212 const auto *atom = item.firstAtom();
213 while (atom)
214 atom = addAtomElements(writer, atom, relative: node, marker: marker_);
215 }
216 writer.writeEndElement(); // see-also
217 }
218
219 if (node->isExample()) {
220 m_supplement = true;
221 generateRequiredLinks(node, marker: marker_);
222 m_supplement = false;
223 } else if (node->isGroup()) {
224 auto *cn = static_cast<CollectionNode *>(node);
225 if (!cn->noAutoList())
226 generateAnnotatedList(writer, relative: node, nodeList: cn->members());
227 }
228
229 writer.writeEndElement(); // description
230}
231
232void WebXMLGenerator::generateDocumentation(Node *node)
233{
234 // Don't generate nodes that are already processed, or if they're not supposed to
235 // generate output, ie. external, index or images nodes.
236 if (!node->url().isNull() || node->isExternalPage() || node->isIndexNode())
237 return;
238
239 if (node->isInternal() && !m_showInternal)
240 return;
241
242 if (node->parent()) {
243 if (node->isNamespace() || node->isClassNode() || node->isHeader())
244 generateCppReferencePage(aggregate: static_cast<Aggregate *>(node), nullptr);
245 else if (node->isCollectionNode()) {
246 if (node->wasSeen()) {
247 // see remarks in base class impl.
248 m_qdb->mergeCollections(c: static_cast<CollectionNode *>(node));
249 generatePageNode(pn: static_cast<PageNode *>(node), nullptr);
250 }
251 } else if (node->isTextPageNode())
252 generatePageNode(pn: static_cast<PageNode *>(node), nullptr);
253 // else if TODO: anything else?
254 }
255
256 if (node->isAggregate()) {
257 auto *aggregate = static_cast<Aggregate *>(node);
258 for (auto c : aggregate->childNodes()) {
259 if ((c->isAggregate() || c->isTextPageNode() || c->isCollectionNode())
260 && !c->isPrivate())
261 generateDocumentation(node: c);
262 }
263 }
264}
265
266const Atom *WebXMLGenerator::addAtomElements(QXmlStreamWriter &writer, const Atom *atom,
267 const Node *relative, CodeMarker *marker)
268{
269 bool keepQuoting = false;
270
271 if (!atom)
272 return nullptr;
273
274 switch (atom->type()) {
275 case Atom::AnnotatedList: {
276 const CollectionNode *cn = m_qdb->getCollectionNode(name: atom->string(), type: Node::Group);
277 if (cn)
278 generateAnnotatedList(writer, relative, nodeList: cn->members());
279 } break;
280 case Atom::AutoLink: {
281 const Node *node{nullptr};
282 QString link{};
283
284 if (!m_inLink && !m_inSectionHeading) {
285 link = getAutoLink(atom, relative, node: &node, Node::API);
286
287 if (!link.isEmpty() && node && node->isDeprecated()
288 && relative->parent() != node && !relative->isDeprecated()) {
289 link.clear();
290 }
291 }
292
293 startLink(writer, atom, node, link);
294
295 writer.writeCharacters(text: atom->string());
296
297 if (m_inLink) {
298 writer.writeEndElement(); // link
299 m_inLink = false;
300 }
301
302 break;
303 }
304 case Atom::BaseName:
305 break;
306 case Atom::BriefLeft:
307
308 writer.writeStartElement(qualifiedName: "brief");
309 switch (relative->nodeType()) {
310 case Node::Property:
311 writer.writeCharacters(text: "This property");
312 break;
313 case Node::Variable:
314 writer.writeCharacters(text: "This variable");
315 break;
316 default:
317 break;
318 }
319 if (relative->isProperty() || relative->isVariable()) {
320 QString str;
321 const Atom *a = atom->next();
322 while (a != nullptr && a->type() != Atom::BriefRight) {
323 if (a->type() == Atom::String || a->type() == Atom::AutoLink)
324 str += a->string();
325 a = a->next();
326 }
327 str[0] = str[0].toLower();
328 if (str.endsWith(c: '.'))
329 str.chop(n: 1);
330
331 const QList<QStringView> words = QStringView{str}.split(sep: ' ');
332 if (!words.isEmpty()) {
333 QStringView first(words.at(i: 0));
334 if (!(first == u"contains" || first == u"specifies" || first == u"describes"
335 || first == u"defines" || first == u"holds" || first == u"determines"))
336 writer.writeCharacters(text: " holds ");
337 else
338 writer.writeCharacters(text: " ");
339 }
340 }
341 break;
342
343 case Atom::BriefRight:
344 if (relative->isProperty() || relative->isVariable())
345 writer.writeCharacters(text: ".");
346
347 writer.writeEndElement(); // brief
348 break;
349
350 case Atom::C:
351 writer.writeStartElement(qualifiedName: "teletype");
352 if (m_inLink)
353 writer.writeAttribute(qualifiedName: "type", value: "normal");
354 else
355 writer.writeAttribute(qualifiedName: "type", value: "highlighted");
356
357 writer.writeCharacters(text: plainCode(markedCode: atom->string()));
358 writer.writeEndElement(); // teletype
359 break;
360
361 case Atom::Code:
362 if (!m_hasQuotingInformation)
363 writer.writeTextElement(
364 qualifiedName: "code", text: trimmedTrailing(string: plainCode(markedCode: atom->string()), prefix: QString(), suffix: QString()));
365 else
366 keepQuoting = true;
367 break;
368
369 case Atom::CodeBad:
370 writer.writeTextElement(qualifiedName: "badcode",
371 text: trimmedTrailing(string: plainCode(markedCode: atom->string()), prefix: QString(), suffix: QString()));
372 break;
373
374 case Atom::CodeQuoteArgument:
375 if (m_quoting) {
376 if (quoteCommand == "dots") {
377 writer.writeAttribute(qualifiedName: "indent", value: atom->string());
378 writer.writeCharacters(text: "...");
379 } else {
380 writer.writeCharacters(text: atom->string());
381 }
382 writer.writeEndElement(); // code
383 keepQuoting = true;
384 }
385 break;
386
387 case Atom::CodeQuoteCommand:
388 if (m_quoting) {
389 quoteCommand = atom->string();
390 writer.writeStartElement(qualifiedName: quoteCommand);
391 }
392 break;
393
394 case Atom::ExampleFileLink: {
395 if (!m_inLink) {
396 QString link = linkForExampleFile(path: atom->string());
397 if (!link.isEmpty())
398 startLink(writer, atom, node: relative, link);
399 }
400 } break;
401
402 case Atom::ExampleImageLink: {
403 if (!m_inLink) {
404 QString link = atom->string();
405 if (!link.isEmpty())
406 startLink(writer, atom, node: nullptr, link: "images/used-in-examples/" + link);
407 }
408 } break;
409
410 case Atom::FootnoteLeft:
411 writer.writeStartElement(qualifiedName: "footnote");
412 break;
413
414 case Atom::FootnoteRight:
415 writer.writeEndElement(); // footnote
416 break;
417
418 case Atom::FormatEndif:
419 writer.writeEndElement(); // raw
420 break;
421 case Atom::FormatIf:
422 writer.writeStartElement(qualifiedName: "raw");
423 writer.writeAttribute(qualifiedName: "format", value: atom->string());
424 break;
425 case Atom::FormattingLeft: {
426 if (atom->string() == ATOM_FORMATTING_BOLD)
427 writer.writeStartElement(qualifiedName: "bold");
428 else if (atom->string() == ATOM_FORMATTING_ITALIC)
429 writer.writeStartElement(qualifiedName: "italic");
430 else if (atom->string() == ATOM_FORMATTING_UNDERLINE)
431 writer.writeStartElement(qualifiedName: "underline");
432 else if (atom->string() == ATOM_FORMATTING_SUBSCRIPT)
433 writer.writeStartElement(qualifiedName: "subscript");
434 else if (atom->string() == ATOM_FORMATTING_SUPERSCRIPT)
435 writer.writeStartElement(qualifiedName: "superscript");
436 else if (atom->string() == ATOM_FORMATTING_TELETYPE)
437 writer.writeStartElement(qualifiedName: "teletype");
438 else if (atom->string() == ATOM_FORMATTING_PARAMETER)
439 writer.writeStartElement(qualifiedName: "argument");
440 else if (atom->string() == ATOM_FORMATTING_INDEX)
441 writer.writeStartElement(qualifiedName: "index");
442 } break;
443
444 case Atom::FormattingRight: {
445 if (atom->string() == ATOM_FORMATTING_BOLD)
446 writer.writeEndElement();
447 else if (atom->string() == ATOM_FORMATTING_ITALIC)
448 writer.writeEndElement();
449 else if (atom->string() == ATOM_FORMATTING_UNDERLINE)
450 writer.writeEndElement();
451 else if (atom->string() == ATOM_FORMATTING_SUBSCRIPT)
452 writer.writeEndElement();
453 else if (atom->string() == ATOM_FORMATTING_SUPERSCRIPT)
454 writer.writeEndElement();
455 else if (atom->string() == ATOM_FORMATTING_TELETYPE)
456 writer.writeEndElement();
457 else if (atom->string() == ATOM_FORMATTING_PARAMETER)
458 writer.writeEndElement();
459 else if (atom->string() == ATOM_FORMATTING_INDEX)
460 writer.writeEndElement();
461 }
462 if (m_inLink) {
463 writer.writeEndElement(); // link
464 m_inLink = false;
465 }
466 break;
467
468 case Atom::GeneratedList:
469 writer.writeStartElement(qualifiedName: "generatedlist");
470 writer.writeAttribute(qualifiedName: "contents", value: atom->string());
471 writer.writeEndElement();
472 break;
473
474 // TODO: The other generators treat inlineimage and image
475 // simultaneously as the diffirences aren't big. It should be
476 // possible to do the same for webxmlgenerator instead of
477 // repeating the code.
478
479 // TODO: [generator-insufficient-structural-abstraction]
480 case Atom::Image: {
481 auto maybe_resolved_file{file_resolver.resolve(filename: atom->string())};
482 if (!maybe_resolved_file) {
483 // TODO: [uncentralized-admonition][failed-resolve-file]
484 relative->location().warning(QStringLiteral("Missing image: %1").arg(a: atom->string()));
485 } else {
486 ResolvedFile file{*maybe_resolved_file};
487 QString file_name{QFileInfo{file.get_path()}.fileName()};
488
489 // TODO: [uncentralized-output-directory-structure]
490 Config::copyFile(location: relative->doc().location(), sourceFilePath: file.get_path(), userFriendlySourceFilePath: file_name, targetDirPath: outputDir() + QLatin1String("/images"));
491
492 writer.writeStartElement(qualifiedName: "image");
493 // TODO: [uncentralized-output-directory-structure]
494 writer.writeAttribute(qualifiedName: "href", value: "images/" + file_name);
495 writer.writeEndElement();
496 // TODO: [uncentralized-output-directory-structure]
497 setImageFileName(relative, fileName: "images/" + file_name);
498 }
499 break;
500 }
501 // TODO: [generator-insufficient-structural-abstraction]
502 case Atom::InlineImage: {
503 auto maybe_resolved_file{file_resolver.resolve(filename: atom->string())};
504 if (!maybe_resolved_file) {
505 // TODO: [uncentralized-admonition][failed-resolve-file]
506 relative->location().warning(QStringLiteral("Missing image: %1").arg(a: atom->string()));
507 } else {
508 ResolvedFile file{*maybe_resolved_file};
509 QString file_name{QFileInfo{file.get_path()}.fileName()};
510
511 // TODO: [uncentralized-output-directory-structure]
512 Config::copyFile(location: relative->doc().location(), sourceFilePath: file.get_path(), userFriendlySourceFilePath: file_name, targetDirPath: outputDir() + QLatin1String("/images"));
513
514 writer.writeStartElement(qualifiedName: "inlineimage");
515 // TODO: [uncentralized-output-directory-structure]
516 writer.writeAttribute(qualifiedName: "href", value: "images/" + file_name);
517 writer.writeEndElement();
518 // TODO: [uncentralized-output-directory-structure]
519 setImageFileName(relative, fileName: "images/" + file_name);
520 }
521 break;
522 }
523 case Atom::ImageText:
524 break;
525
526 case Atom::ImportantLeft:
527 writer.writeStartElement(qualifiedName: "para");
528 writer.writeTextElement(qualifiedName: "bold", text: "Important:");
529 writer.writeCharacters(text: " ");
530 break;
531
532 case Atom::LegaleseLeft:
533 writer.writeStartElement(qualifiedName: "legalese");
534 break;
535
536 case Atom::LegaleseRight:
537 writer.writeEndElement(); // legalese
538 break;
539
540 case Atom::Link:
541 case Atom::LinkNode:
542 if (!m_inLink) {
543 const Node *node = nullptr;
544 QString link = getLink(atom, relative, node: &node);
545 if (!link.isEmpty())
546 startLink(writer, atom, node, link);
547 }
548 break;
549
550 case Atom::ListLeft:
551 writer.writeStartElement(qualifiedName: "list");
552
553 if (atom->string() == ATOM_LIST_BULLET)
554 writer.writeAttribute(qualifiedName: "type", value: "bullet");
555 else if (atom->string() == ATOM_LIST_TAG)
556 writer.writeAttribute(qualifiedName: "type", value: "definition");
557 else if (atom->string() == ATOM_LIST_VALUE) {
558 if (relative->isEnumType())
559 writer.writeAttribute(qualifiedName: "type", value: "enum");
560 else
561 writer.writeAttribute(qualifiedName: "type", value: "definition");
562 } else {
563 writer.writeAttribute(qualifiedName: "type", value: "ordered");
564 if (atom->string() == ATOM_LIST_UPPERALPHA)
565 writer.writeAttribute(qualifiedName: "start", value: "A");
566 else if (atom->string() == ATOM_LIST_LOWERALPHA)
567 writer.writeAttribute(qualifiedName: "start", value: "a");
568 else if (atom->string() == ATOM_LIST_UPPERROMAN)
569 writer.writeAttribute(qualifiedName: "start", value: "I");
570 else if (atom->string() == ATOM_LIST_LOWERROMAN)
571 writer.writeAttribute(qualifiedName: "start", value: "i");
572 else // (atom->string() == ATOM_LIST_NUMERIC)
573 writer.writeAttribute(qualifiedName: "start", value: "1");
574 }
575 break;
576
577 case Atom::ListItemNumber:
578 break;
579 case Atom::ListTagLeft: {
580 writer.writeStartElement(qualifiedName: "definition");
581
582 writer.writeTextElement(
583 qualifiedName: "term", text: plainCode(markedCode: marker->markedUpEnumValue(atom->next()->string(), relative)));
584 } break;
585
586 case Atom::ListTagRight:
587 writer.writeEndElement(); // definition
588 break;
589
590 case Atom::ListItemLeft:
591 writer.writeStartElement(qualifiedName: "item");
592 break;
593
594 case Atom::ListItemRight:
595 writer.writeEndElement(); // item
596 break;
597
598 case Atom::ListRight:
599 writer.writeEndElement(); // list
600 break;
601
602 case Atom::NoteLeft:
603 writer.writeStartElement(qualifiedName: "para");
604 writer.writeTextElement(qualifiedName: "bold", text: "Note:");
605 writer.writeCharacters(text: " ");
606 break;
607
608 // End admonition elements
609 case Atom::ImportantRight:
610 case Atom::NoteRight:
611 case Atom::WarningRight:
612 writer.writeEndElement(); // para
613 break;
614
615 case Atom::Nop:
616 break;
617
618 case Atom::CaptionLeft:
619 case Atom::ParaLeft:
620 writer.writeStartElement(qualifiedName: "para");
621 break;
622
623 case Atom::CaptionRight:
624 case Atom::ParaRight:
625 writer.writeEndElement(); // para
626 break;
627
628 case Atom::QuotationLeft:
629 writer.writeStartElement(qualifiedName: "quote");
630 break;
631
632 case Atom::QuotationRight:
633 writer.writeEndElement(); // quote
634 break;
635
636 case Atom::RawString:
637 writer.writeCharacters(text: atom->string());
638 break;
639
640 case Atom::SectionLeft:
641 writer.writeStartElement(qualifiedName: "section");
642 writer.writeAttribute(qualifiedName: "id",
643 value: Utilities::asAsciiPrintable(name: Text::sectionHeading(sectionBegin: atom).toString()));
644 break;
645
646 case Atom::SectionRight:
647 writer.writeEndElement(); // section
648 break;
649
650 case Atom::SectionHeadingLeft: {
651 writer.writeStartElement(qualifiedName: "heading");
652 int unit = atom->string().toInt(); // + hOffset(relative)
653 writer.writeAttribute(qualifiedName: "level", value: QString::number(unit));
654 m_inSectionHeading = true;
655 } break;
656
657 case Atom::SectionHeadingRight:
658 writer.writeEndElement(); // heading
659 m_inSectionHeading = false;
660 break;
661
662 case Atom::SidebarLeft:
663 case Atom::SidebarRight:
664 break;
665
666 case Atom::SnippetCommand:
667 if (m_quoting) {
668 writer.writeStartElement(qualifiedName: atom->string());
669 }
670 break;
671
672 case Atom::SnippetIdentifier:
673 if (m_quoting) {
674 writer.writeAttribute(qualifiedName: "identifier", value: atom->string());
675 writer.writeEndElement();
676 keepQuoting = true;
677 }
678 break;
679
680 case Atom::SnippetLocation:
681 if (m_quoting) {
682 const QString &location = atom->string();
683 writer.writeAttribute(qualifiedName: "location", value: location);
684 auto maybe_resolved_file{file_resolver.resolve(filename: location)};
685 // const QString resolved = Doc::resolveFile(Location(), location);
686 if (maybe_resolved_file)
687 writer.writeAttribute(qualifiedName: "path", value: (*maybe_resolved_file).get_path());
688 else {
689 // TODO: [uncetnralized-admonition][failed-resolve-file]
690 QString details = std::transform_reduce(
691 first: file_resolver.get_search_directories().cbegin(),
692 last: file_resolver.get_search_directories().cend(),
693 init: u"Searched directories:"_s,
694 binary_op: std::plus(),
695 unary_op: [](const DirectoryPath &directory_path) -> QString { return u' ' + directory_path.value(); }
696 );
697
698 relative->location().warning(message: u"Cannot find file to quote from: %1"_s.arg(a: location), details);
699 }
700 }
701 break;
702
703 case Atom::String:
704 writer.writeCharacters(text: atom->string());
705 break;
706 case Atom::TableLeft:
707 writer.writeStartElement(qualifiedName: "table");
708 if (atom->string().contains(s: "%"))
709 writer.writeAttribute(qualifiedName: "width", value: atom->string());
710 break;
711
712 case Atom::TableRight:
713 writer.writeEndElement(); // table
714 break;
715
716 case Atom::TableHeaderLeft:
717 writer.writeStartElement(qualifiedName: "header");
718 break;
719
720 case Atom::TableHeaderRight:
721 writer.writeEndElement(); // header
722 break;
723
724 case Atom::TableRowLeft:
725 writer.writeStartElement(qualifiedName: "row");
726 break;
727
728 case Atom::TableRowRight:
729 writer.writeEndElement(); // row
730 break;
731
732 case Atom::TableItemLeft: {
733 writer.writeStartElement(qualifiedName: "item");
734 QStringList spans = atom->string().split(sep: ",");
735 if (spans.size() == 2) {
736 if (spans.at(i: 0) != "1")
737 writer.writeAttribute(qualifiedName: "colspan", value: spans.at(i: 0).trimmed());
738 if (spans.at(i: 1) != "1")
739 writer.writeAttribute(qualifiedName: "rowspan", value: spans.at(i: 1).trimmed());
740 }
741 } break;
742 case Atom::TableItemRight:
743 writer.writeEndElement(); // item
744 break;
745
746 case Atom::Target:
747 writer.writeStartElement(qualifiedName: "target");
748 writer.writeAttribute(qualifiedName: "name", value: Utilities::asAsciiPrintable(name: atom->string()));
749 writer.writeEndElement();
750 break;
751
752 case Atom::WarningLeft:
753 writer.writeStartElement(qualifiedName: "para");
754 writer.writeTextElement(qualifiedName: "bold", text: "Warning:");
755 writer.writeCharacters(text: " ");
756 break;
757
758 case Atom::UnhandledFormat:
759 case Atom::UnknownCommand:
760 writer.writeCharacters(text: atom->typeString());
761 break;
762 default:
763 break;
764 }
765
766 m_hasQuotingInformation = keepQuoting;
767 return atom->next();
768}
769
770void WebXMLGenerator::startLink(QXmlStreamWriter &writer, const Atom *atom, const Node *node,
771 const QString &link)
772{
773 QString fullName = link;
774 if (node)
775 fullName = node->fullName();
776 if (!fullName.isEmpty() && !link.isEmpty()) {
777 writer.writeStartElement(qualifiedName: "link");
778 if (atom && !atom->string().isEmpty())
779 writer.writeAttribute(qualifiedName: "raw", value: atom->string());
780 else
781 writer.writeAttribute(qualifiedName: "raw", value: fullName);
782 writer.writeAttribute(qualifiedName: "href", value: link);
783 writer.writeAttribute(qualifiedName: "type", value: targetType(node));
784 if (node) {
785 switch (node->nodeType()) {
786 case Node::Enum:
787 writer.writeAttribute(qualifiedName: "enum", value: fullName);
788 break;
789 case Node::Example: {
790 const auto *en = static_cast<const ExampleNode *>(node);
791 const QString fileTitle = atom ? exampleFileTitle(relative: en, fileName: atom->string()) : QString();
792 if (!fileTitle.isEmpty()) {
793 writer.writeAttribute(qualifiedName: "page", value: fileTitle);
794 break;
795 }
796 }
797 Q_FALLTHROUGH();
798 case Node::Page:
799 writer.writeAttribute(qualifiedName: "page", value: fullName);
800 break;
801 case Node::Property: {
802 const auto *propertyNode = static_cast<const PropertyNode *>(node);
803 if (!propertyNode->getters().empty())
804 writer.writeAttribute(qualifiedName: "getter", value: propertyNode->getters().at(i: 0)->fullName());
805 } break;
806 default:
807 break;
808 }
809 }
810 m_inLink = true;
811 }
812}
813
814void WebXMLGenerator::endLink(QXmlStreamWriter &writer)
815{
816 if (m_inLink) {
817 writer.writeEndElement(); // link
818 m_inLink = false;
819 }
820}
821
822void WebXMLGenerator::generateRelations(QXmlStreamWriter &writer, const Node *node)
823{
824 if (node && !node->links().empty()) {
825 std::pair<QString, QString> anchorPair;
826 const Node *linkNode;
827
828 for (auto it = node->links().cbegin(); it != node->links().cend(); ++it) {
829
830 linkNode = m_qdb->findNodeForTarget(target: it.value().first, relative: node);
831
832 if (!linkNode)
833 linkNode = node;
834
835 if (linkNode == node)
836 anchorPair = it.value();
837 else
838 anchorPair = anchorForNode(node: linkNode);
839
840 writer.writeStartElement(qualifiedName: "relation");
841 writer.writeAttribute(qualifiedName: "href", value: anchorPair.first);
842 writer.writeAttribute(qualifiedName: "type", value: targetType(node: linkNode));
843
844 switch (it.key()) {
845 case Node::StartLink:
846 writer.writeAttribute(qualifiedName: "meta", value: "start");
847 break;
848 case Node::NextLink:
849 writer.writeAttribute(qualifiedName: "meta", value: "next");
850 break;
851 case Node::PreviousLink:
852 writer.writeAttribute(qualifiedName: "meta", value: "previous");
853 break;
854 case Node::ContentsLink:
855 writer.writeAttribute(qualifiedName: "meta", value: "contents");
856 break;
857 default:
858 writer.writeAttribute(qualifiedName: "meta", value: "");
859 }
860 writer.writeAttribute(qualifiedName: "description", value: anchorPair.second);
861 writer.writeEndElement(); // link
862 }
863 }
864}
865
866void WebXMLGenerator::generateAnnotatedList(QXmlStreamWriter &writer, const Node *relative,
867 const NodeMap &nodeMap)
868{
869 generateAnnotatedList(writer, relative, nodeList: nodeMap.values());
870}
871
872void WebXMLGenerator::generateAnnotatedList(QXmlStreamWriter &writer, const Node *relative,
873 const NodeList &nodeList)
874{
875 writer.writeStartElement(qualifiedName: "table");
876 writer.writeAttribute(qualifiedName: "width", value: "100%");
877
878 for (const auto *node : nodeList) {
879 writer.writeStartElement(qualifiedName: "row");
880 writer.writeStartElement(qualifiedName: "item");
881 writer.writeStartElement(qualifiedName: "para");
882 const QString link = linkForNode(node, relative);
883 startLink(writer, atom: node->doc().body().firstAtom(), node, link);
884 endLink(writer);
885 writer.writeEndElement(); // para
886 writer.writeEndElement(); // item
887
888 writer.writeStartElement(qualifiedName: "item");
889 writer.writeStartElement(qualifiedName: "para");
890 writer.writeCharacters(text: node->doc().briefText().toString());
891 writer.writeEndElement(); // para
892 writer.writeEndElement(); // item
893 writer.writeEndElement(); // row
894 }
895 writer.writeEndElement(); // table
896}
897
898QString WebXMLGenerator::fileBase(const Node *node) const
899{
900 return Generator::fileBase(node);
901}
902
903QT_END_NAMESPACE
904

source code of qttools/src/qdoc/qdoc/webxmlgenerator.cpp