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