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