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_query());
115 writer.writeAttribute(qualifiedName: "href", value: linkForExampleFile(path: resolved_file.get_query()));
116 const QString title = exampleFileTitle(relative: static_cast<const ExampleNode *>(en), fileName: resolved_file.get_query());
117 writer.writeAttribute(qualifiedName: "title", value: title);
118 writer.writeAttribute(qualifiedName: "fulltitle", value: title);
119 writer.writeAttribute(qualifiedName: "subtitle", value: resolved_file.get_query());
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: std::move(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 std::ignore = 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{cnn->getMembers(type: NodeType::Namespace)};
182 generateAnnotatedList(writer, relative: node, nodeMap: namespaces);
183 writer.writeEndElement(); // section
184 }
185 if (cnn->hasClasses()) {
186 writer.writeStartElement(qualifiedName: "section");
187 writer.writeStartElement(qualifiedName: "heading");
188 writer.writeAttribute(qualifiedName: "level", value: "1");
189 writer.writeCharacters(text: "Classes");
190 writer.writeEndElement(); // heading
191 NodeMap classes{cnn->getMembers(predicate: [](const Node *n){ return n->isClassNode(); })};
192 generateAnnotatedList(writer, relative: node, nodeMap: classes);
193 writer.writeEndElement(); // section
194 }
195 writer.writeEndElement(); // generatedlist
196 }
197
198 m_inLink = m_inSectionHeading = m_hasQuotingInformation = false;
199
200 const Atom *atom = node->doc().body().firstAtom();
201 while (atom)
202 atom = addAtomElements(writer, atom, relative: node, marker: marker_);
203
204 QList<Text> alsoList = node->doc().alsoList();
205 supplementAlsoList(node, alsoList);
206
207 if (!alsoList.isEmpty()) {
208 writer.writeStartElement(qualifiedName: "see-also");
209 for (const auto &item : alsoList) {
210 const auto *atom = item.firstAtom();
211 while (atom)
212 atom = addAtomElements(writer, atom, relative: node, marker: marker_);
213 }
214 writer.writeEndElement(); // see-also
215 }
216
217 if (node->isExample()) {
218 m_supplement = true;
219 generateRequiredLinks(node, marker: marker_);
220 m_supplement = false;
221 } else if (node->isGroup()) {
222 auto *cn = static_cast<CollectionNode *>(node);
223 if (!cn->noAutoList())
224 generateAnnotatedList(writer, relative: node, nodeList: cn->members());
225 }
226
227 writer.writeEndElement(); // description
228}
229
230void WebXMLGenerator::generateDocumentation(Node *node)
231{
232 // Don't generate nodes that are already processed, or if they're not supposed to
233 // generate output, ie. external, index or images nodes.
234 if (!node->url().isNull() || node->isExternalPage() || node->isIndexNode())
235 return;
236
237 if (node->isInternal() && !m_showInternal)
238 return;
239
240 if (node->parent()) {
241 if (node->isNamespace() || node->isClassNode() || node->isHeader())
242 generateCppReferencePage(aggregate: static_cast<Aggregate *>(node), nullptr);
243 else if (node->isCollectionNode()) {
244 if (node->wasSeen()) {
245 // see remarks in base class impl.
246 m_qdb->mergeCollections(c: static_cast<CollectionNode *>(node));
247 generatePageNode(pn: static_cast<PageNode *>(node), nullptr);
248 }
249 } else if (node->isTextPageNode())
250 generatePageNode(pn: static_cast<PageNode *>(node), nullptr);
251 // else if TODO: anything else?
252 }
253
254 if (node->isAggregate()) {
255 auto *aggregate = static_cast<Aggregate *>(node);
256 for (auto c : aggregate->childNodes()) {
257 if ((c->isAggregate() || c->isTextPageNode() || c->isCollectionNode())
258 && !c->isPrivate())
259 generateDocumentation(node: c);
260 }
261 }
262}
263
264const Atom *WebXMLGenerator::addAtomElements(QXmlStreamWriter &writer, const Atom *atom,
265 const Node *relative, CodeMarker *marker)
266{
267 bool keepQuoting = false;
268
269 if (!atom)
270 return nullptr;
271
272 switch (atom->type()) {
273 case Atom::AnnotatedList: {
274 const CollectionNode *cn = m_qdb->getCollectionNode(name: atom->string(), type: NodeType::Group);
275 if (cn)
276 generateAnnotatedList(writer, relative, nodeList: cn->members());
277 } break;
278 case Atom::AutoLink: {
279 const Node *node{nullptr};
280 QString link{};
281
282 if (!m_inLink && !m_inSectionHeading) {
283 link = getAutoLink(atom, relative, node: &node, Genus::API);
284
285 if (!link.isEmpty() && node && node->isDeprecated()
286 && relative->parent() != node && !relative->isDeprecated()) {
287 link.clear();
288 }
289 }
290
291 startLink(writer, atom, node, link);
292
293 writer.writeCharacters(text: atom->string());
294
295 if (m_inLink) {
296 writer.writeEndElement(); // link
297 m_inLink = false;
298 }
299
300 break;
301 }
302 case Atom::BaseName:
303 break;
304 case Atom::BriefLeft:
305
306 writer.writeStartElement(qualifiedName: "brief");
307 switch (relative->nodeType()) {
308 case NodeType::Property:
309 writer.writeCharacters(text: "This property");
310 break;
311 case NodeType::Variable:
312 writer.writeCharacters(text: "This variable");
313 break;
314 default:
315 break;
316 }
317 if (relative->isProperty() || relative->isVariable()) {
318 QString str;
319 const Atom *a = atom->next();
320 while (a != nullptr && a->type() != Atom::BriefRight) {
321 if (a->type() == Atom::String || a->type() == Atom::AutoLink)
322 str += a->string();
323 a = a->next();
324 }
325 str[0] = str[0].toLower();
326 if (str.endsWith(c: '.'))
327 str.chop(n: 1);
328
329 const QList<QStringView> words = QStringView{str}.split(sep: ' ');
330 if (!words.isEmpty()) {
331 QStringView first(words.at(i: 0));
332 if (!(first == u"contains" || first == u"specifies" || first == u"describes"
333 || first == u"defines" || first == u"holds" || first == u"determines"))
334 writer.writeCharacters(text: " holds ");
335 else
336 writer.writeCharacters(text: " ");
337 }
338 }
339 break;
340
341 case Atom::BriefRight:
342 if (relative->isProperty() || relative->isVariable())
343 writer.writeCharacters(text: ".");
344
345 writer.writeEndElement(); // brief
346 break;
347
348 case Atom::C:
349 writer.writeStartElement(qualifiedName: "teletype");
350 if (m_inLink)
351 writer.writeAttribute(qualifiedName: "type", value: "normal");
352 else
353 writer.writeAttribute(qualifiedName: "type", value: "highlighted");
354
355 writer.writeCharacters(text: plainCode(markedCode: atom->string()));
356 writer.writeEndElement(); // teletype
357 break;
358
359 case Atom::Code:
360 if (!m_hasQuotingInformation)
361 writer.writeTextElement(
362 qualifiedName: "code", text: trimmedTrailing(string: plainCode(markedCode: atom->string()), prefix: QString(), suffix: QString()));
363 else
364 keepQuoting = true;
365 break;
366
367 case Atom::CodeBad:
368 writer.writeTextElement(qualifiedName: "badcode",
369 text: trimmedTrailing(string: plainCode(markedCode: atom->string()), prefix: QString(), suffix: QString()));
370 break;
371
372 case Atom::CodeQuoteArgument:
373 if (m_quoting) {
374 if (quoteCommand == "dots") {
375 writer.writeAttribute(qualifiedName: "indent", value: atom->string());
376 writer.writeCharacters(text: "...");
377 } else {
378 writer.writeCharacters(text: atom->string());
379 }
380 writer.writeEndElement(); // code
381 keepQuoting = true;
382 }
383 break;
384
385 case Atom::CodeQuoteCommand:
386 if (m_quoting) {
387 quoteCommand = atom->string();
388 writer.writeStartElement(qualifiedName: quoteCommand);
389 }
390 break;
391
392 case Atom::ExampleFileLink: {
393 if (!m_inLink) {
394 QString link = linkForExampleFile(path: atom->string());
395 if (!link.isEmpty())
396 startLink(writer, atom, node: relative, link);
397 }
398 } break;
399
400 case Atom::ExampleImageLink: {
401 if (!m_inLink) {
402 QString link = atom->string();
403 if (!link.isEmpty())
404 startLink(writer, atom, node: nullptr, link: "images/used-in-examples/" + link);
405 }
406 } break;
407
408 case Atom::FootnoteLeft:
409 writer.writeStartElement(qualifiedName: "footnote");
410 break;
411
412 case Atom::FootnoteRight:
413 writer.writeEndElement(); // footnote
414 break;
415
416 case Atom::FormatEndif:
417 writer.writeEndElement(); // raw
418 break;
419 case Atom::FormatIf:
420 writer.writeStartElement(qualifiedName: "raw");
421 writer.writeAttribute(qualifiedName: "format", value: atom->string());
422 break;
423 case Atom::FormattingLeft: {
424 if (atom->string() == ATOM_FORMATTING_BOLD)
425 writer.writeStartElement(qualifiedName: "bold");
426 else if (atom->string() == ATOM_FORMATTING_ITALIC)
427 writer.writeStartElement(qualifiedName: "italic");
428 else if (atom->string() == ATOM_FORMATTING_UNDERLINE)
429 writer.writeStartElement(qualifiedName: "underline");
430 else if (atom->string() == ATOM_FORMATTING_SUBSCRIPT)
431 writer.writeStartElement(qualifiedName: "subscript");
432 else if (atom->string() == ATOM_FORMATTING_SUPERSCRIPT)
433 writer.writeStartElement(qualifiedName: "superscript");
434 else if (atom->string() == ATOM_FORMATTING_TELETYPE || atom->string() == ATOM_FORMATTING_NOTRANSLATE)
435 writer.writeStartElement(qualifiedName: "teletype");
436 else if (atom->string() == ATOM_FORMATTING_PARAMETER)
437 writer.writeStartElement(qualifiedName: "argument");
438 else if (atom->string() == ATOM_FORMATTING_INDEX)
439 writer.writeStartElement(qualifiedName: "index");
440 } break;
441
442 case Atom::FormattingRight: {
443 if (atom->string() == ATOM_FORMATTING_BOLD)
444 writer.writeEndElement();
445 else if (atom->string() == ATOM_FORMATTING_ITALIC)
446 writer.writeEndElement();
447 else if (atom->string() == ATOM_FORMATTING_UNDERLINE)
448 writer.writeEndElement();
449 else if (atom->string() == ATOM_FORMATTING_SUBSCRIPT)
450 writer.writeEndElement();
451 else if (atom->string() == ATOM_FORMATTING_SUPERSCRIPT)
452 writer.writeEndElement();
453 else if (atom->string() == ATOM_FORMATTING_TELETYPE || atom->string() == ATOM_FORMATTING_NOTRANSLATE)
454 writer.writeEndElement();
455 else if (atom->string() == ATOM_FORMATTING_PARAMETER)
456 writer.writeEndElement();
457 else if (atom->string() == ATOM_FORMATTING_INDEX)
458 writer.writeEndElement();
459 else if (atom->string() == ATOM_FORMATTING_TRADEMARK && appendTrademark(atom))
460 writer.writeCharacters(text: QChar(0x2122)); // 'TM' symbol
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 NodeType::Enum:
787 writer.writeAttribute(qualifiedName: "enum", value: fullName);
788 break;
789 case NodeType::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 NodeType::Page:
799 writer.writeAttribute(qualifiedName: "page", value: fullName);
800 break;
801 case NodeType::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/src/qdoc/webxmlgenerator.cpp