1 /****************************************************************************
2 **
3 ** Copyright (C) 2019 Thibaut Cuvelier
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 <cctype>
30 #include <qlist.h>
31 #include <qiterator.h>
32 #include <qtextcodec.h>
33 #include <quuid.h>
34 #include <qurl.h>
35 #include <qmap.h>
36 #include <QtCore/qversionnumber.h>
37 
38 #include "codemarker.h"
39 #include "config.h"
40 #include "generator.h"
41 #include "docbookgenerator.h"
42 #include "node.h"
43 #include "quoter.h"
44 #include "qdocdatabase.h"
45 #include "separator.h"
46 
47 QT_BEGIN_NAMESPACE
48 
49 static const char dbNamespace[] = "http://docbook.org/ns/docbook";
50 static const char xlinkNamespace[] = "http://www.w3.org/1999/xlink";
51 
newLine()52 inline void DocBookGenerator::newLine()
53 {
54     writer->writeCharacters("\n");
55 }
56 
startSectionBegin()57 void DocBookGenerator::startSectionBegin()
58 {
59     writer->writeStartElement(dbNamespace, "section");
60     newLine();
61     writer->writeStartElement(dbNamespace, "title");
62 }
63 
startSectionBegin(const QString & id)64 void DocBookGenerator::startSectionBegin(const QString &id)
65 {
66     writer->writeStartElement(dbNamespace, "section");
67     writer->writeAttribute("xml:id", id);
68     newLine();
69     writer->writeStartElement(dbNamespace, "title");
70 }
71 
startSectionEnd()72 void DocBookGenerator::startSectionEnd()
73 {
74     writer->writeEndElement(); // title
75     newLine();
76 }
77 
startSection(const QString & id,const QString & title)78 void DocBookGenerator::startSection(const QString &id, const QString &title)
79 {
80     startSectionBegin(id);
81     writer->writeCharacters(title);
82     startSectionEnd();
83 }
84 
endSection()85 void DocBookGenerator::endSection()
86 {
87     writer->writeEndElement(); // section
88     newLine();
89 }
90 
writeAnchor(const QString & id)91 void DocBookGenerator::writeAnchor(const QString &id)
92 {
93     writer->writeEmptyElement(dbNamespace, "anchor");
94     writer->writeAttribute("xml:id", id);
95     newLine();
96 }
97 
98 /*!
99   Initializes the DocBook output generator's data structures
100   from the configuration (Config).
101  */
initializeGenerator()102 void DocBookGenerator::initializeGenerator()
103 {
104     // Excerpts from HtmlGenerator::initializeGenerator.
105     Generator::initializeGenerator();
106     config = &Config::instance();
107 
108     project = config->getString(CONFIG_PROJECT);
109 
110     projectDescription = config->getString(CONFIG_DESCRIPTION);
111     if (projectDescription.isEmpty() && !project.isEmpty())
112         projectDescription = project + QLatin1String(" Reference Documentation");
113 
114     naturalLanguage = config->getString(CONFIG_NATURALLANGUAGE);
115     if (naturalLanguage.isEmpty())
116         naturalLanguage = QLatin1String("en");
117 
118     buildversion = config->getString(CONFIG_BUILDVERSION);
119 }
120 
format()121 QString DocBookGenerator::format()
122 {
123     return QStringLiteral("DocBook");
124 }
125 
126 /*!
127   Returns "xml" for this subclass of Generator.
128  */
fileExtension() const129 QString DocBookGenerator::fileExtension() const
130 {
131     return QStringLiteral("xml");
132 }
133 
134 /*!
135   Generate the documentation for \a relative. i.e. \a relative
136   is the node that represents the entity where a qdoc comment
137   was found, and \a text represents the qdoc comment.
138  */
generateText(const Text & text,const Node * relative,CodeMarker * marker)139 bool DocBookGenerator::generateText(const Text &text, const Node *relative, CodeMarker *marker)
140 {
141     Q_UNUSED(marker);
142     // From Generator::generateText.
143     if (!text.firstAtom())
144         return false;
145 
146     int numAtoms = 0;
147     initializeTextOutput();
148     generateAtomList(text.firstAtom(), relative, true, numAtoms);
149     closeTextSections();
150     return true;
151 }
152 
153 /*!
154   Generate the text for \a atom relatively to \a relative.
155   \a generate indicates if output to \a writer is expected.
156   The number of generated atoms is returned in the argument
157   \a numAtoms. The returned value is the first atom that was not
158   generated.
159  */
generateAtomList(const Atom * atom,const Node * relative,bool generate,int & numAtoms)160 const Atom *DocBookGenerator::generateAtomList(const Atom *atom, const Node *relative,
161                                                bool generate, int &numAtoms)
162 {
163     Q_ASSERT(writer);
164     // From Generator::generateAtomList.
165     while (atom) {
166         switch (atom->type()) {
167         case Atom::FormatIf: {
168             int numAtoms0 = numAtoms;
169             atom = generateAtomList(atom->next(), relative, generate, numAtoms);
170             if (!atom)
171                 return nullptr;
172 
173             if (atom->type() == Atom::FormatElse) {
174                 ++numAtoms;
175                 atom = generateAtomList(atom->next(), relative, false, numAtoms);
176                 if (!atom)
177                     return nullptr;
178             }
179 
180             if (atom->type() == Atom::FormatEndif) {
181                 if (generate && numAtoms0 == numAtoms) {
182                     relative->location().warning(
183                             tr("Output format %1 not handled %2").arg(format()).arg(outFileName()));
184                     Atom unhandledFormatAtom(Atom::UnhandledFormat, format());
185                     generateAtomList(&unhandledFormatAtom, relative, generate, numAtoms);
186                 }
187                 atom = atom->next();
188             }
189         } break;
190         case Atom::FormatElse:
191         case Atom::FormatEndif:
192             return atom;
193         default:
194             int n = 1;
195             if (generate) {
196                 n += generateAtom(atom, relative);
197                 numAtoms += n;
198             }
199             while (n-- > 0)
200                 atom = atom->next();
201         }
202     }
203     return nullptr;
204 }
205 
206 /*!
207   Generate DocBook from an instance of Atom.
208  */
generateAtom(const Atom * atom,const Node * relative,CodeMarker * marker)209 int DocBookGenerator::generateAtom(const Atom *atom, const Node *relative, CodeMarker *marker)
210 {
211     Q_ASSERT(writer);
212     Q_UNUSED(marker);
213     // From HtmlGenerator::generateAtom, without warning generation.
214     int idx = 0;
215     int skipAhead = 0;
216     static bool inPara = false;
217 
218     switch (atom->type()) {
219     case Atom::AutoLink:
220     case Atom::NavAutoLink:
221         if (!inLink && !inContents_ && !inSectionHeading_) {
222             const Node *node = nullptr;
223             QString link = getAutoLink(atom, relative, &node);
224             if (!link.isEmpty() && node && node->status() == Node::Obsolete
225                 && relative->parent() != node && !relative->isObsolete()) {
226                 link.clear();
227             }
228             if (link.isEmpty()) {
229                 writer->writeCharacters(atom->string());
230             } else {
231                 beginLink(link, node, relative);
232                 generateLink(atom);
233                 endLink();
234             }
235         } else {
236             writer->writeCharacters(atom->string());
237         }
238         break;
239     case Atom::BaseName:
240         break;
241     case Atom::BriefLeft:
242         if (!hasBrief(relative)) {
243             skipAhead = skipAtoms(atom, Atom::BriefRight);
244             break;
245         }
246         writer->writeStartElement(dbNamespace, "para");
247         rewritePropertyBrief(atom, relative);
248         break;
249     case Atom::BriefRight:
250         if (hasBrief(relative)) {
251             writer->writeEndElement(); // para
252             newLine();
253         }
254         break;
255     case Atom::C:
256         // This may at one time have been used to mark up C++ code but it is
257         // now widely used to write teletype text. As a result, text marked
258         // with the \c command is not passed to a code marker.
259         writer->writeTextElement(dbNamespace, "code", plainCode(atom->string()));
260         break;
261     case Atom::CaptionLeft:
262         writer->writeStartElement(dbNamespace, "title");
263         break;
264     case Atom::CaptionRight:
265         endLink();
266         writer->writeEndElement(); // title
267         newLine();
268         break;
269     case Atom::Qml:
270         writer->writeStartElement(dbNamespace, "programlisting");
271         writer->writeAttribute("language", "qml");
272         writer->writeCharacters(atom->string());
273         writer->writeEndElement(); // programlisting
274         newLine();
275         break;
276     case Atom::JavaScript:
277         writer->writeStartElement(dbNamespace, "programlisting");
278         writer->writeAttribute("language", "js");
279         writer->writeCharacters(atom->string());
280         writer->writeEndElement(); // programlisting
281         newLine();
282         break;
283     case Atom::CodeNew:
284         writer->writeTextElement(dbNamespace, "para", "you can rewrite it as");
285         newLine();
286         writer->writeStartElement(dbNamespace, "programlisting");
287         writer->writeAttribute("language", "cpp");
288         writer->writeAttribute("role", "new");
289         writer->writeCharacters(atom->string());
290         writer->writeEndElement(); // programlisting
291         newLine();
292         break;
293     case Atom::Code:
294         writer->writeStartElement(dbNamespace, "programlisting");
295         writer->writeAttribute("language", "cpp");
296         writer->writeCharacters(atom->string());
297         writer->writeEndElement(); // programlisting
298         newLine();
299         break;
300     case Atom::CodeOld:
301         writer->writeTextElement(dbNamespace, "para", "For example, if you have code like");
302         newLine();
303         Q_FALLTHROUGH();
304     case Atom::CodeBad:
305         writer->writeStartElement(dbNamespace, "programlisting");
306         writer->writeAttribute("language", "cpp");
307         writer->writeAttribute("role", "bad");
308         writer->writeCharacters(atom->string());
309         writer->writeEndElement(); // programlisting
310         newLine();
311         break;
312     case Atom::DivLeft:
313     case Atom::DivRight:
314         break;
315     case Atom::FootnoteLeft:
316         writer->writeStartElement(dbNamespace, "footnote");
317         newLine();
318         writer->writeStartElement(dbNamespace, "para");
319         break;
320     case Atom::FootnoteRight:
321         writer->writeEndElement(); // para
322         newLine();
323         writer->writeEndElement(); // footnote
324         break;
325     case Atom::FormatElse:
326     case Atom::FormatEndif:
327     case Atom::FormatIf:
328         break;
329     case Atom::FormattingLeft:
330         if (atom->string() == ATOM_FORMATTING_BOLD) {
331             writer->writeStartElement(dbNamespace, "emphasis");
332             writer->writeAttribute("role", "bold");
333         } else if (atom->string() == ATOM_FORMATTING_ITALIC) {
334             writer->writeStartElement(dbNamespace, "emphasis");
335         } else if (atom->string() == ATOM_FORMATTING_UNDERLINE) {
336             writer->writeStartElement(dbNamespace, "emphasis");
337             writer->writeAttribute("role", "underline");
338         } else if (atom->string() == ATOM_FORMATTING_SUBSCRIPT) {
339             writer->writeStartElement(dbNamespace, "sub");
340         } else if (atom->string() == ATOM_FORMATTING_SUPERSCRIPT) {
341             writer->writeStartElement(dbNamespace, "sup");
342         } else if (atom->string() == ATOM_FORMATTING_TELETYPE
343                    || atom->string() == ATOM_FORMATTING_PARAMETER) {
344             writer->writeStartElement(dbNamespace, "code");
345 
346             if (atom->string() == ATOM_FORMATTING_PARAMETER)
347                 writer->writeAttribute("role", "parameter");
348         }
349         break;
350     case Atom::FormattingRight:
351         if (atom->string() == ATOM_FORMATTING_BOLD || atom->string() == ATOM_FORMATTING_ITALIC
352             || atom->string() == ATOM_FORMATTING_UNDERLINE
353             || atom->string() == ATOM_FORMATTING_SUBSCRIPT
354             || atom->string() == ATOM_FORMATTING_SUPERSCRIPT
355             || atom->string() == ATOM_FORMATTING_TELETYPE
356             || atom->string() == ATOM_FORMATTING_PARAMETER) {
357             writer->writeEndElement();
358         }
359         if (atom->string() == ATOM_FORMATTING_LINK)
360             endLink();
361         break;
362     case Atom::AnnotatedList:
363         if (const CollectionNode *cn = qdb_->getCollectionNode(atom->string(), Node::Group))
364             generateList(cn, atom->string());
365         break;
366     case Atom::GeneratedList:
367         if (atom->string() == QLatin1String("annotatedclasses")
368             || atom->string() == QLatin1String("attributions")
369             || atom->string() == QLatin1String("namespaces")) {
370             const NodeMultiMap things = atom->string() == QLatin1String("annotatedclasses")
371                     ? qdb_->getCppClasses()
372                     : atom->string() == QLatin1String("attributions") ? qdb_->getAttributions()
373                                                                       : qdb_->getNamespaces();
374             generateAnnotatedList(relative, things, atom->string());
375         } else if (atom->string() == QLatin1String("annotatedexamples")
376                    || atom->string() == QLatin1String("annotatedattributions")) {
377             const NodeMultiMap things = atom->string() == QLatin1String("annotatedexamples")
378                     ? qdb_->getAttributions()
379                     : qdb_->getExamples();
380             generateAnnotatedLists(relative, things, atom->string());
381         } else if (atom->string() == QLatin1String("classes")
382                    || atom->string() == QLatin1String("qmlbasictypes")
383                    || atom->string() == QLatin1String("qmltypes")) {
384             const NodeMultiMap things = atom->string() == QLatin1String("classes")
385                     ? qdb_->getCppClasses()
386                     : atom->string() == QLatin1String("qmlbasictypes") ? qdb_->getQmlBasicTypes()
387                                                                        : qdb_->getQmlTypes();
388             generateCompactList(Generic, relative, things, QString(), atom->string());
389         } else if (atom->string().contains("classes ")) {
390             QString rootName = atom->string().mid(atom->string().indexOf("classes") + 7).trimmed();
391             generateCompactList(Generic, relative, qdb_->getCppClasses(), rootName, atom->string());
392         } else if ((idx = atom->string().indexOf(QStringLiteral("bymodule"))) != -1) {
393             QString moduleName = atom->string().mid(idx + 8).trimmed();
394             Node::NodeType type = typeFromString(atom);
395             QDocDatabase *qdb = QDocDatabase::qdocDB();
396             if (const CollectionNode *cn = qdb->getCollectionNode(moduleName, type)) {
397                 if (type == Node::Module) {
398                     NodeMap m;
399                     cn->getMemberClasses(m);
400                     if (!m.isEmpty())
401                         generateAnnotatedList(relative, m, atom->string());
402                 } else {
403                     generateAnnotatedList(relative, cn->members(), atom->string());
404                 }
405             }
406         } else if (atom->string().startsWith("examplefiles")
407                    || atom->string().startsWith("exampleimages")) {
408             if (relative->isExample())
409                 qDebug() << "GENERATE FILE LIST CALLED" << relative->name() << atom->string();
410         } else if (atom->string() == QLatin1String("classhierarchy")) {
411             generateClassHierarchy(relative, qdb_->getCppClasses());
412         } else if (atom->string().startsWith("obsolete")) {
413             ListType type = atom->string().endsWith("members") ? Obsolete : Generic;
414             QString prefix = atom->string().contains("cpp") ? QStringLiteral("Q") : QString();
415             const NodeMultiMap &things = atom->string() == QLatin1String("obsoleteclasses")
416                     ? qdb_->getObsoleteClasses()
417                     : atom->string() == QLatin1String("obsoleteqmltypes")
418                             ? qdb_->getObsoleteQmlTypes()
419                             : atom->string() == QLatin1String("obsoletecppmembers")
420                                     ? qdb_->getClassesWithObsoleteMembers()
421                                     : qdb_->getQmlTypesWithObsoleteMembers();
422             generateCompactList(type, relative, things, prefix, atom->string());
423         } else if (atom->string() == QLatin1String("functionindex")) {
424             generateFunctionIndex(relative);
425         } else if (atom->string() == QLatin1String("legalese")) {
426             generateLegaleseList(relative);
427         } else if (atom->string() == QLatin1String("overviews")
428                    || atom->string() == QLatin1String("cpp-modules")
429                    || atom->string() == QLatin1String("qml-modules")
430                    || atom->string() == QLatin1String("related")) {
431             generateList(relative, atom->string());
432         }
433         break;
434     case Atom::SinceList:
435         // Table of contents, should automatically be generated by the DocBook processor.
436         break;
437     case Atom::LineBreak:
438     case Atom::BR:
439     case Atom::HR:
440         // Not supported in DocBook.
441         break;
442     case Atom::Image: // mediaobject
443     case Atom::InlineImage: { // inlinemediaobject
444         QString tag = atom->type() == Atom::Image ? "mediaobject" : "inlinemediaobject";
445         writer->writeStartElement(dbNamespace, tag);
446         newLine();
447 
448         QString fileName = imageFileName(relative, atom->string());
449         if (fileName.isEmpty()) {
450             writer->writeStartElement(dbNamespace, "textobject");
451             newLine();
452             writer->writeStartElement(dbNamespace, "para");
453             writer->writeTextElement(dbNamespace, "emphasis",
454                                      "[Missing image " + atom->string() + "]");
455             writer->writeEndElement(); // para
456             newLine();
457             writer->writeEndElement(); // textobject
458             newLine();
459         } else {
460             if (atom->next() && !atom->next()->string().isEmpty())
461                 writer->writeTextElement(dbNamespace, "alt", atom->next()->string());
462 
463             writer->writeStartElement(dbNamespace, "imageobject");
464             newLine();
465             writer->writeEmptyElement(dbNamespace, "imagedata");
466             writer->writeAttribute("fileref", fileName);
467             newLine();
468             writer->writeEndElement(); // imageobject
469             newLine();
470 
471             setImageFileName(relative, fileName);
472         }
473 
474         writer->writeEndElement(); // [inline]mediaobject
475         if (atom->type() == Atom::Image)
476             newLine();
477     } break;
478     case Atom::ImageText:
479         break;
480     case Atom::ImportantLeft:
481     case Atom::NoteLeft: {
482         QString tag = atom->type() == Atom::ImportantLeft ? "important" : "note";
483         writer->writeStartElement(dbNamespace, tag);
484         newLine();
485         writer->writeStartElement(dbNamespace, "para");
486     } break;
487     case Atom::ImportantRight:
488     case Atom::NoteRight:
489         writer->writeEndElement(); // para
490         newLine();
491         writer->writeEndElement(); // note/important
492         newLine();
493         break;
494     case Atom::LegaleseLeft:
495     case Atom::LegaleseRight:
496         break;
497     case Atom::Link:
498     case Atom::NavLink: {
499         const Node *node = nullptr;
500         QString link = getLink(atom, relative, &node);
501         beginLink(link, node, relative); // Ended at Atom::FormattingRight
502         skipAhead = 1;
503     } break;
504     case Atom::LinkNode: {
505         const Node *node = CodeMarker::nodeForString(atom->string());
506         beginLink(linkForNode(node, relative), node, relative);
507         skipAhead = 1;
508     } break;
509     case Atom::ListLeft:
510         if (inPara) {
511             writer->writeEndElement(); // para
512             newLine();
513             inPara = false;
514         }
515         if (atom->string() == ATOM_LIST_BULLET) {
516             writer->writeStartElement(dbNamespace, "itemizedlist");
517             newLine();
518         } else if (atom->string() == ATOM_LIST_TAG) {
519             writer->writeStartElement(dbNamespace, "variablelist");
520             newLine();
521         } else if (atom->string() == ATOM_LIST_VALUE) {
522             writer->writeStartElement(dbNamespace, "informaltable");
523             newLine();
524             writer->writeStartElement(dbNamespace, "thead");
525             newLine();
526             writer->writeStartElement(dbNamespace, "tr");
527             newLine();
528             writer->writeTextElement(dbNamespace, "th", "Constant");
529             newLine();
530 
531             threeColumnEnumValueTable_ = isThreeColumnEnumValueTable(atom);
532             if (threeColumnEnumValueTable_ && relative->nodeType() == Node::Enum) {
533                 // If not in \enum topic, skip the value column
534                 writer->writeTextElement(dbNamespace, "th", "Value");
535                 newLine();
536             }
537 
538             writer->writeTextElement(dbNamespace, "th", "Description");
539             newLine();
540 
541             writer->writeEndElement(); // tr
542             newLine();
543             writer->writeEndElement(); // thead
544             newLine();
545         } else {
546             writer->writeStartElement(dbNamespace, "orderedlist");
547 
548             if (atom->next() != nullptr && atom->next()->string().toInt() > 1)
549                 writer->writeAttribute("startingnumber", atom->next()->string());
550 
551             if (atom->string() == ATOM_LIST_UPPERALPHA)
552                 writer->writeAttribute("numeration", "upperalpha");
553             else if (atom->string() == ATOM_LIST_LOWERALPHA)
554                 writer->writeAttribute("numeration", "loweralpha");
555             else if (atom->string() == ATOM_LIST_UPPERROMAN)
556                 writer->writeAttribute("numeration", "upperroman");
557             else if (atom->string() == ATOM_LIST_LOWERROMAN)
558                 writer->writeAttribute("numeration", "lowerroman");
559             else // (atom->string() == ATOM_LIST_NUMERIC)
560                 writer->writeAttribute("numeration", "arabic");
561 
562             newLine();
563         }
564         break;
565     case Atom::ListItemNumber:
566         break;
567     case Atom::ListTagLeft:
568         if (atom->string() == ATOM_LIST_TAG) {
569             writer->writeStartElement(dbNamespace, "varlistentry");
570             newLine();
571             writer->writeStartElement(dbNamespace, "item");
572         } else { // (atom->string() == ATOM_LIST_VALUE)
573             QPair<QString, int> pair = getAtomListValue(atom);
574             skipAhead = pair.second;
575 
576             writer->writeStartElement(dbNamespace, "tr");
577             newLine();
578             writer->writeStartElement(dbNamespace, "td");
579             newLine();
580             writer->writeStartElement(dbNamespace, "para");
581             generateEnumValue(pair.first, relative);
582             writer->writeEndElement(); // para
583             newLine();
584             writer->writeEndElement(); // td
585             newLine();
586 
587             if (relative->nodeType() == Node::Enum) {
588                 const auto enume = static_cast<const EnumNode *>(relative);
589                 QString itemValue = enume->itemValue(atom->next()->string());
590 
591                 writer->writeStartElement(dbNamespace, "td");
592                 if (itemValue.isEmpty())
593                     writer->writeCharacters("?");
594                 else
595                     writer->writeTextElement(dbNamespace, "code", itemValue);
596                 writer->writeEndElement(); // td
597                 newLine();
598             }
599         }
600         break;
601     case Atom::SinceTagRight:
602     case Atom::ListTagRight:
603         if (atom->string() == ATOM_LIST_TAG) {
604             writer->writeEndElement(); // item
605             newLine();
606         }
607         break;
608     case Atom::ListItemLeft:
609         inListItemLineOpen = false;
610         if (atom->string() == ATOM_LIST_TAG) {
611             writer->writeStartElement(dbNamespace, "listitem");
612             newLine();
613             writer->writeStartElement(dbNamespace, "para");
614         } else if (atom->string() == ATOM_LIST_VALUE) {
615             if (threeColumnEnumValueTable_) {
616                 if (matchAhead(atom, Atom::ListItemRight)) {
617                     writer->writeEmptyElement(dbNamespace, "td");
618                     newLine();
619                     inListItemLineOpen = false;
620                 } else {
621                     writer->writeStartElement(dbNamespace, "td");
622                     newLine();
623                     inListItemLineOpen = true;
624                 }
625             }
626         } else {
627             writer->writeStartElement(dbNamespace, "listitem");
628             newLine();
629         }
630         // Don't skip a paragraph, DocBook requires them within list items.
631         break;
632     case Atom::ListItemRight:
633         if (atom->string() == ATOM_LIST_TAG) {
634             writer->writeEndElement(); // para
635             newLine();
636             writer->writeEndElement(); // listitem
637             newLine();
638             writer->writeEndElement(); // varlistentry
639             newLine();
640         } else if (atom->string() == ATOM_LIST_VALUE) {
641             if (inListItemLineOpen) {
642                 writer->writeEndElement(); // td
643                 newLine();
644                 inListItemLineOpen = false;
645             }
646             writer->writeEndElement(); // tr
647             newLine();
648         } else {
649             writer->writeEndElement(); // listitem
650             newLine();
651         }
652         break;
653     case Atom::ListRight:
654         // Depending on atom->string(), closing a different item:
655         // - ATOM_LIST_BULLET: itemizedlist
656         // - ATOM_LIST_TAG: variablelist
657         // - ATOM_LIST_VALUE: informaltable
658         // - ATOM_LIST_NUMERIC: orderedlist
659         writer->writeEndElement();
660         newLine();
661         break;
662     case Atom::Nop:
663         break;
664     case Atom::ParaLeft:
665         writer->writeStartElement(dbNamespace, "para");
666         inPara = true;
667         break;
668     case Atom::ParaRight:
669         endLink();
670         if (inPara) {
671             writer->writeEndElement(); // para
672             newLine();
673             inPara = false;
674         }
675         break;
676     case Atom::QuotationLeft:
677         writer->writeStartElement(dbNamespace, "blockquote");
678         inPara = true;
679         break;
680     case Atom::QuotationRight:
681         writer->writeEndElement(); // blockquote
682         newLine();
683         break;
684     case Atom::RawString:
685         writer->writeCharacters(atom->string());
686         break;
687     case Atom::SectionLeft:
688         currentSectionLevel = atom->string().toInt() + hOffset(relative);
689         // Level 1 is dealt with at the header level (info tag).
690         if (currentSectionLevel > 1) {
691             // Unfortunately, SectionRight corresponds to the end of any section,
692             // i.e. going to a new section, even deeper.
693             while (!sectionLevels.empty() && sectionLevels.top() >= currentSectionLevel) {
694                 sectionLevels.pop();
695                 writer->writeEndElement(); // section
696                 newLine();
697             }
698 
699             sectionLevels.push(currentSectionLevel);
700 
701             writer->writeStartElement(dbNamespace, "section");
702             writer->writeAttribute("xml:id",
703                                    Doc::canonicalTitle(Text::sectionHeading(atom).toString()));
704             newLine();
705             // Unlike startSectionBegin, don't start a title here.
706         }
707         break;
708     case Atom::SectionRight:
709         // All the logic about closing sections is done in the SectionLeft case
710         // and generateFooter() for the end of the page.
711         break;
712     case Atom::SectionHeadingLeft:
713         // Level 1 is dealt with at the header level (info tag).
714         if (currentSectionLevel > 1) {
715             writer->writeStartElement(dbNamespace, "title");
716             inSectionHeading_ = true;
717         }
718         break;
719     case Atom::SectionHeadingRight:
720         // Level 1 is dealt with at the header level (info tag).
721         if (currentSectionLevel > 1) {
722             writer->writeEndElement(); // title
723             newLine();
724             inSectionHeading_ = false;
725         }
726         break;
727     case Atom::SidebarLeft:
728         writer->writeStartElement(dbNamespace, "sidebar");
729         break;
730     case Atom::SidebarRight:
731         writer->writeEndElement(); // sidebar
732         newLine();
733         break;
734     case Atom::String:
735         if (inLink && !inContents_ && !inSectionHeading_)
736             generateLink(atom);
737         else
738             writer->writeCharacters(atom->string());
739         break;
740     case Atom::TableLeft: {
741         QPair<QString, QString> pair = getTableWidthAttr(atom);
742         QString attr = pair.second;
743         QString width = pair.first;
744 
745         if (inPara) {
746             writer->writeEndElement(); // para or blockquote
747             newLine();
748             inPara = false;
749         }
750 
751         writer->writeStartElement(dbNamespace, "informaltable");
752         writer->writeAttribute("style", attr);
753         if (!width.isEmpty())
754             writer->writeAttribute("width", width);
755         newLine();
756         numTableRows_ = 0;
757     } break;
758     case Atom::TableRight:
759         writer->writeEndElement(); // table
760         newLine();
761         break;
762     case Atom::TableHeaderLeft:
763         writer->writeStartElement(dbNamespace, "thead");
764         newLine();
765         writer->writeStartElement(dbNamespace, "tr");
766         newLine();
767         inTableHeader_ = true;
768         break;
769     case Atom::TableHeaderRight:
770         writer->writeEndElement(); // tr
771         newLine();
772         if (matchAhead(atom, Atom::TableHeaderLeft)) {
773             skipAhead = 1;
774             writer->writeStartElement(dbNamespace, "tr");
775             newLine();
776         } else {
777             writer->writeEndElement(); // thead
778             newLine();
779             inTableHeader_ = false;
780         }
781         break;
782     case Atom::TableRowLeft:
783         writer->writeStartElement(dbNamespace, "tr");
784         if (atom->string().isEmpty()) {
785             writer->writeAttribute("valign", "top");
786         } else {
787             // Basic parsing of attributes, should be enough. The input string (atom->string())
788             // looks like:
789             //      arg1="val1" arg2="val2"
790             QStringList args = atom->string().split("\"", Qt::SkipEmptyParts);
791             //      arg1=, val1, arg2=, val2,
792             //      \-- 1st --/  \-- 2nd --/  \-- remainder
793             if (args.size() % 2) {
794                 // Problem...
795                 relative->doc().location().warning(
796                         tr("Error when parsing attributes for the table: got \"%1\"")
797                                 .arg(atom->string()));
798             }
799             for (int i = 0; i + 1 < args.size(); i += 2)
800                 writer->writeAttribute(args.at(i).chopped(1), args.at(i + 1));
801         }
802         newLine();
803         break;
804     case Atom::TableRowRight:
805         writer->writeEndElement(); // tr
806         newLine();
807         break;
808     case Atom::TableItemLeft:
809         writer->writeStartElement(dbNamespace, inTableHeader_ ? "th" : "td");
810 
811         for (int i = 0; i < atom->count(); ++i) {
812             const QString &p = atom->string(i);
813             if (p.contains('=')) {
814                 QStringList lp = p.split(QLatin1Char('='));
815                 writer->writeAttribute(lp.at(0), lp.at(1));
816             } else {
817                 QStringList spans = p.split(QLatin1Char(','));
818                 if (spans.size() == 2) {
819                     if (spans.at(0) != "1")
820                         writer->writeAttribute("colspan", spans.at(0));
821                     if (spans.at(1) != "1")
822                         writer->writeAttribute("rowspan", spans.at(1));
823                 }
824             }
825         }
826         newLine();
827         // No skipahead, as opposed to HTML: in DocBook, the text must be wrapped in paragraphs.
828         break;
829     case Atom::TableItemRight:
830         writer->writeEndElement(); // th if inTableHeader_, otherwise td
831         newLine();
832         break;
833     case Atom::TableOfContents:
834         break;
835     case Atom::Keyword:
836         break;
837     case Atom::Target:
838         writeAnchor(Doc::canonicalTitle(atom->string()));
839         break;
840     case Atom::UnhandledFormat:
841         writer->writeStartElement(dbNamespace, "emphasis");
842         writer->writeAttribute("role", "bold");
843         writer->writeCharacters("&lt;Missing DocBook&gt;");
844         writer->writeEndElement(); // emphasis
845         break;
846     case Atom::UnknownCommand:
847         writer->writeStartElement(dbNamespace, "emphasis");
848         writer->writeAttribute("role", "bold");
849         writer->writeCharacters("&lt;Unknown command&gt;");
850         writer->writeStartElement(dbNamespace, "code");
851         writer->writeCharacters(atom->string());
852         writer->writeEndElement(); // code
853         writer->writeEndElement(); // emphasis
854         break;
855     case Atom::QmlText:
856     case Atom::EndQmlText:
857         // don't do anything with these. They are just tags.
858         break;
859     case Atom::CodeQuoteArgument:
860     case Atom::CodeQuoteCommand:
861     case Atom::SnippetCommand:
862     case Atom::SnippetIdentifier:
863     case Atom::SnippetLocation:
864         // no output (ignore)
865         break;
866     default:
867         unknownAtom(atom);
868     }
869     return skipAhead;
870 }
871 
generateClassHierarchy(const Node * relative,NodeMap & classMap)872 void DocBookGenerator::generateClassHierarchy(const Node *relative, NodeMap &classMap)
873 {
874     // From HtmlGenerator::generateClassHierarchy.
875     if (classMap.isEmpty())
876         return;
877 
878     NodeMap topLevel;
879     NodeMap::Iterator c = classMap.begin();
880     while (c != classMap.end()) {
881         auto *classe = static_cast<ClassNode *>(*c);
882         if (classe->baseClasses().isEmpty())
883             topLevel.insert(classe->name(), classe);
884         ++c;
885     }
886 
887     QStack<NodeMap> stack;
888     stack.push(topLevel);
889 
890     writer->writeStartElement(dbNamespace, "itemizedlist");
891     newLine();
892     while (!stack.isEmpty()) {
893         if (stack.top().isEmpty()) {
894             stack.pop();
895             writer->writeEndElement(); // listitem
896             newLine();
897             writer->writeEndElement(); // itemizedlist
898             newLine();
899         } else {
900             ClassNode *child = static_cast<ClassNode *>(*stack.top().begin());
901             writer->writeStartElement(dbNamespace, "listitem");
902             newLine();
903             writer->writeStartElement(dbNamespace, "para");
904             generateFullName(child, relative);
905             writer->writeEndElement(); // para
906             newLine();
907             // Don't close the listitem now, as DocBook requires sublists to reside in items.
908             stack.top().erase(stack.top().begin());
909 
910             NodeMap newTop;
911             for (const RelatedClass &d : child->derivedClasses()) {
912                 if (d.node_ && !d.isPrivate() && !d.node_->isInternal() && d.node_->hasDoc())
913                     newTop.insert(d.node_->name(), d.node_);
914             }
915             if (!newTop.isEmpty()) {
916                 stack.push(newTop);
917                 writer->writeStartElement(dbNamespace, "itemizedlist");
918                 newLine();
919             }
920         }
921     }
922 }
923 
generateLink(const Atom * atom)924 void DocBookGenerator::generateLink(const Atom *atom)
925 {
926     // From HtmlGenerator::generateLink.
927     QRegExp funcLeftParen("\\S(\\()");
928     if (funcLeftParen.indexIn(atom->string()) != -1) {
929         // hack for C++: move () outside of link
930         int k = funcLeftParen.pos(1);
931         writer->writeCharacters(atom->string().left(k));
932         writer->writeEndElement(); // link
933         inLink = false;
934         writer->writeCharacters(atom->string().mid(k));
935     } else {
936         writer->writeCharacters(atom->string());
937     }
938 }
939 
940 /*!
941   This version of the function is called when the \a link is known
942   to be correct.
943  */
beginLink(const QString & link,const Node * node,const Node * relative)944 void DocBookGenerator::beginLink(const QString &link, const Node *node, const Node *relative)
945 {
946     // From HtmlGenerator::beginLink.
947     writer->writeStartElement(dbNamespace, "link");
948     writer->writeAttribute(xlinkNamespace, "href", link);
949     if (node && !(relative && node->status() == relative->status())
950         && node->status() == Node::Obsolete)
951         writer->writeAttribute("role", "obsolete");
952     inLink = true;
953 }
954 
endLink()955 void DocBookGenerator::endLink()
956 {
957     // From HtmlGenerator::endLink.
958     if (inLink)
959         writer->writeEndElement(); // link
960     inLink = false;
961 }
962 
generateList(const Node * relative,const QString & selector)963 void DocBookGenerator::generateList(const Node *relative, const QString &selector)
964 {
965     // From HtmlGenerator::generateList, without warnings, changing prototype.
966     CNMap cnm;
967     Node::NodeType type = Node::NoType;
968     if (selector == QLatin1String("overviews"))
969         type = Node::Group;
970     else if (selector == QLatin1String("cpp-modules"))
971         type = Node::Module;
972     else if (selector == QLatin1String("qml-modules"))
973         type = Node::QmlModule;
974     else if (selector == QLatin1String("js-modules"))
975         type = Node::JsModule;
976 
977     if (type != Node::NoType) {
978         NodeList nodeList;
979         qdb_->mergeCollections(type, cnm, relative);
980         const QList<CollectionNode *> collectionList = cnm.values();
981         nodeList.reserve(collectionList.size());
982         for (auto *collectionNode : collectionList)
983             nodeList.append(collectionNode);
984         generateAnnotatedList(relative, nodeList, selector);
985     } else {
986         /*
987           \generatelist {selector} is only allowed in a
988           comment where the topic is \group, \module,
989           \qmlmodule, or \jsmodule
990         */
991         Node *n = const_cast<Node *>(relative);
992         auto *cn = static_cast<CollectionNode *>(n);
993         qdb_->mergeCollections(cn);
994         generateAnnotatedList(cn, cn->members(), selector);
995     }
996 }
997 
998 /*!
999   Output an annotated list of the nodes in \a nodeMap.
1000   A two-column table is output.
1001  */
generateAnnotatedList(const Node * relative,const NodeMultiMap & nmm,const QString & selector)1002 void DocBookGenerator::generateAnnotatedList(const Node *relative, const NodeMultiMap &nmm,
1003                                              const QString &selector)
1004 {
1005     // From HtmlGenerator::generateAnnotatedList
1006     if (nmm.isEmpty())
1007         return;
1008     generateAnnotatedList(relative, nmm.values(), selector);
1009 }
1010 
generateAnnotatedList(const Node * relative,const NodeList & nodeList,const QString & selector)1011 void DocBookGenerator::generateAnnotatedList(const Node *relative, const NodeList &nodeList,
1012                                              const QString &selector)
1013 {
1014     // From WebXMLGenerator::generateAnnotatedList.
1015     writer->writeStartElement(dbNamespace, "variablelist");
1016     writer->writeAttribute("role", selector);
1017     newLine();
1018 
1019     for (auto node : nodeList) {
1020         writer->writeStartElement(dbNamespace, "varlistentry");
1021         newLine();
1022         writer->writeStartElement(dbNamespace, "term");
1023         generateFullName(node, relative);
1024         writer->writeEndElement(); // term
1025         newLine();
1026 
1027         writer->writeStartElement(dbNamespace, "listitem");
1028         newLine();
1029         writer->writeStartElement(dbNamespace, "para");
1030         writer->writeCharacters(node->doc().briefText().toString());
1031         writer->writeEndElement(); // para
1032         newLine();
1033         writer->writeEndElement(); // listitem
1034         newLine();
1035         writer->writeEndElement(); // varlistentry
1036         newLine();
1037     }
1038     writer->writeEndElement(); // variablelist
1039     newLine();
1040 }
1041 
1042 /*!
1043   Outputs a series of annotated lists from the nodes in \a nmm,
1044   divided into sections based by the key names in the multimap.
1045  */
generateAnnotatedLists(const Node * relative,const NodeMultiMap & nmm,const QString & selector)1046 void DocBookGenerator::generateAnnotatedLists(const Node *relative, const NodeMultiMap &nmm,
1047                                               const QString &selector)
1048 {
1049     // From HtmlGenerator::generateAnnotatedLists.
1050     for (const QString &name : nmm.uniqueKeys()) {
1051         if (!name.isEmpty())
1052             startSection(registerRef(name.toLower()), name);
1053         generateAnnotatedList(relative, nmm.values(name), selector);
1054         if (!name.isEmpty())
1055             endSection();
1056     }
1057 }
1058 
1059 /*!
1060   This function finds the common prefix of the names of all
1061   the classes in the class map \a nmm and then generates a
1062   compact list of the class names alphabetized on the part
1063   of the name not including the common prefix. You can tell
1064   the function to use \a comonPrefix as the common prefix,
1065   but normally you let it figure it out itself by looking at
1066   the name of the first and last classes in the class map
1067   \a nmm.
1068  */
generateCompactList(ListType listType,const Node * relative,const NodeMultiMap & nmm,const QString & commonPrefix,const QString & selector)1069 void DocBookGenerator::generateCompactList(ListType listType, const Node *relative,
1070                                            const NodeMultiMap &nmm, const QString &commonPrefix,
1071                                            const QString &selector)
1072 {
1073     // From HtmlGenerator::generateCompactList. No more "includeAlphabet", this should be handled by
1074     // the DocBook toolchain afterwards.
1075     // TODO: In DocBook, probably no need for this method: this is purely presentational, i.e. to be
1076     // fully handled by the DocBook toolchain.
1077     if (nmm.isEmpty())
1078         return;
1079 
1080     const int NumParagraphs = 37; // '0' to '9', 'A' to 'Z', '_'
1081     int commonPrefixLen = commonPrefix.length();
1082 
1083     /*
1084       Divide the data into 37 paragraphs: 0, ..., 9, A, ..., Z,
1085       underscore (_). QAccel will fall in paragraph 10 (A) and
1086       QXtWidget in paragraph 33 (X). This is the only place where we
1087       assume that NumParagraphs is 37. Each paragraph is a NodeMultiMap.
1088     */
1089     NodeMultiMap paragraph[NumParagraphs + 1];
1090     QString paragraphName[NumParagraphs + 1];
1091     QSet<char> usedParagraphNames;
1092 
1093     NodeMultiMap::ConstIterator c = nmm.constBegin();
1094     while (c != nmm.constEnd()) {
1095         QStringList pieces = c.key().split("::");
1096         QString key;
1097         int idx = commonPrefixLen;
1098         if (idx > 0 && !pieces.last().startsWith(commonPrefix, Qt::CaseInsensitive))
1099             idx = 0;
1100         key = pieces.last().mid(idx).toLower();
1101 
1102         int paragraphNr = NumParagraphs - 1;
1103 
1104         if (key[0].digitValue() != -1)
1105             paragraphNr = key[0].digitValue();
1106         else if (key[0] >= QLatin1Char('a') && key[0] <= QLatin1Char('z'))
1107             paragraphNr = 10 + key[0].unicode() - 'a';
1108 
1109         paragraphName[paragraphNr] = key[0].toUpper();
1110         usedParagraphNames.insert(key[0].toLower().cell());
1111         paragraph[paragraphNr].insert(c.key(), c.value());
1112         ++c;
1113     }
1114 
1115     /*
1116       Each paragraph j has a size: paragraph[j].count(). In the
1117       discussion, we will assume paragraphs 0 to 5 will have sizes
1118       3, 1, 4, 1, 5, 9.
1119 
1120       We now want to compute the paragraph offset. Paragraphs 0 to 6
1121       start at offsets 0, 3, 4, 8, 9, 14, 23.
1122     */
1123     int paragraphOffset[NumParagraphs + 1]; // 37 + 1
1124     paragraphOffset[0] = 0;
1125     for (int i = 0; i < NumParagraphs; i++) // i = 0..36
1126         paragraphOffset[i + 1] = paragraphOffset[i] + paragraph[i].count();
1127 
1128     // No table of contents in DocBook.
1129 
1130     // Actual output.
1131     numTableRows_ = 0;
1132 
1133     int curParNr = 0;
1134     int curParOffset = 0;
1135     QString previousName;
1136     bool multipleOccurrences = false;
1137 
1138     for (int i = 0; i < nmm.count(); i++) {
1139         while ((curParNr < NumParagraphs) && (curParOffset == paragraph[curParNr].count())) {
1140             ++curParNr;
1141             curParOffset = 0;
1142         }
1143 
1144         /*
1145           Starting a new paragraph means starting a new variablelist.
1146         */
1147         if (curParOffset == 0) {
1148             if (i > 0) {
1149                 writer->writeEndElement(); // variablelist
1150                 newLine();
1151             }
1152 
1153             writer->writeStartElement(dbNamespace, "variablelist");
1154             writer->writeAttribute("role", selector);
1155             newLine();
1156             writer->writeStartElement(dbNamespace, "varlistentry");
1157             newLine();
1158 
1159             writer->writeStartElement(dbNamespace, "term");
1160             writer->writeStartElement(dbNamespace, "emphasis");
1161             writer->writeAttribute("role", "bold");
1162             writer->writeCharacters(paragraphName[curParNr]);
1163             writer->writeEndElement(); // emphasis
1164             writer->writeEndElement(); // term
1165             newLine();
1166         }
1167 
1168         /*
1169           Output a listitem for the current offset in the current paragraph.
1170          */
1171         writer->writeStartElement(dbNamespace, "listitem");
1172         newLine();
1173         writer->writeStartElement(dbNamespace, "para");
1174         if ((curParNr < NumParagraphs) && !paragraphName[curParNr].isEmpty()) {
1175             NodeMultiMap::Iterator it;
1176             NodeMultiMap::Iterator next;
1177             it = paragraph[curParNr].begin();
1178             for (int j = 0; j < curParOffset; j++)
1179                 ++it;
1180 
1181             if (listType == Generic) {
1182                 generateFullName(it.value(), relative);
1183                 writer->writeStartElement(dbNamespace, "link");
1184                 writer->writeAttribute(xlinkNamespace, "href", fullDocumentLocation(*it));
1185                 writer->writeAttribute("type", targetType(it.value()));
1186             } else if (listType == Obsolete) {
1187                 QString fn = fileName(it.value(), fileExtension());
1188                 QString link;
1189                 if (useOutputSubdirs())
1190                     link = QString("../" + it.value()->outputSubdirectory() + QLatin1Char('/'));
1191                 link += fn;
1192 
1193                 writer->writeStartElement(dbNamespace, "link");
1194                 writer->writeAttribute(xlinkNamespace, "href", link);
1195                 writer->writeAttribute("type", targetType(it.value()));
1196             }
1197 
1198             QStringList pieces;
1199             if (it.value()->isQmlType() || it.value()->isJsType()) {
1200                 QString name = it.value()->name();
1201                 next = it;
1202                 ++next;
1203                 if (name != previousName)
1204                     multipleOccurrences = false;
1205                 if ((next != paragraph[curParNr].end()) && (name == next.value()->name())) {
1206                     multipleOccurrences = true;
1207                     previousName = name;
1208                 }
1209                 if (multipleOccurrences)
1210                     name += ": " + it.value()->tree()->camelCaseModuleName();
1211                 pieces << name;
1212             } else
1213                 pieces = it.value()->fullName(relative).split("::");
1214 
1215             writer->writeCharacters(pieces.last());
1216             writer->writeEndElement(); // link
1217 
1218             if (pieces.size() > 1) {
1219                 writer->writeCharacters(" (");
1220                 generateFullName(it.value()->parent(), relative);
1221                 writer->writeCharacters(")");
1222             }
1223         }
1224         writer->writeEndElement(); // para
1225         newLine();
1226         writer->writeEndElement(); // listitem
1227         newLine();
1228         writer->writeEndElement(); // varlistentry
1229         newLine();
1230         curParOffset++;
1231     }
1232     if (nmm.count() > 0) {
1233         writer->writeEndElement(); // variablelist
1234     }
1235 }
1236 
generateFunctionIndex(const Node * relative)1237 void DocBookGenerator::generateFunctionIndex(const Node *relative)
1238 {
1239     // From HtmlGenerator::generateFunctionIndex.
1240     writer->writeStartElement(dbNamespace, "simplelist");
1241     writer->writeAttribute("role", "functionIndex");
1242     newLine();
1243     for (int i = 0; i < 26; i++) {
1244         QChar ch('a' + i);
1245         writer->writeStartElement(dbNamespace, "member");
1246         writer->writeAttribute(xlinkNamespace, "href", QString("#") + ch);
1247         writer->writeCharacters(ch.toUpper());
1248         writer->writeEndElement(); // member
1249         newLine();
1250     }
1251     writer->writeEndElement(); // simplelist
1252     newLine();
1253 
1254     char nextLetter = 'a';
1255     char currentLetter;
1256 
1257     writer->writeStartElement(dbNamespace, "itemizedlist");
1258     newLine();
1259 
1260     NodeMapMap &funcIndex = qdb_->getFunctionIndex();
1261     QMap<QString, NodeMap>::ConstIterator f = funcIndex.constBegin();
1262     while (f != funcIndex.constEnd()) {
1263         writer->writeStartElement(dbNamespace, "listitem");
1264         newLine();
1265         writer->writeStartElement(dbNamespace, "para");
1266         writer->writeCharacters(f.key() + ": ");
1267 
1268         currentLetter = f.key()[0].unicode();
1269         while (islower(currentLetter) && currentLetter >= nextLetter) {
1270             writeAnchor(QString(nextLetter));
1271             nextLetter++;
1272         }
1273 
1274         NodeMap::ConstIterator s = (*f).constBegin();
1275         while (s != (*f).constEnd()) {
1276             writer->writeCharacters(" ");
1277             generateFullName((*s)->parent(), relative);
1278             ++s;
1279         }
1280 
1281         writer->writeEndElement(); // para
1282         newLine();
1283         writer->writeEndElement(); // listitem
1284         newLine();
1285         ++f;
1286     }
1287     writer->writeEndElement(); // itemizedlist
1288     newLine();
1289 }
1290 
generateLegaleseList(const Node * relative)1291 void DocBookGenerator::generateLegaleseList(const Node *relative)
1292 {
1293     // From HtmlGenerator::generateLegaleseList.
1294     TextToNodeMap &legaleseTexts = qdb_->getLegaleseTexts();
1295     QMap<Text, const Node *>::ConstIterator it = legaleseTexts.constBegin();
1296     while (it != legaleseTexts.constEnd()) {
1297         Text text = it.key();
1298         generateText(text, relative);
1299         writer->writeStartElement(dbNamespace, "itemizedlist");
1300         newLine();
1301         do {
1302             writer->writeStartElement(dbNamespace, "listitem");
1303             newLine();
1304             writer->writeStartElement(dbNamespace, "para");
1305             generateFullName(it.value(), relative);
1306             writer->writeEndElement(); // para
1307             newLine();
1308             writer->writeEndElement(); // listitem
1309             newLine();
1310             ++it;
1311         } while (it != legaleseTexts.constEnd() && it.key() == text);
1312         writer->writeEndElement(); // itemizedlist
1313         newLine();
1314     }
1315 }
1316 
generateBrief(const Node * node)1317 void DocBookGenerator::generateBrief(const Node *node)
1318 {
1319     // From HtmlGenerator::generateBrief. Also see generateHeader, which is specifically dealing
1320     // with the DocBook header (and thus wraps the brief in an abstract).
1321     Text brief = node->doc().briefText();
1322 
1323     if (!brief.isEmpty()) {
1324         if (!brief.lastAtom()->string().endsWith('.'))
1325             brief << Atom(Atom::String, ".");
1326 
1327         writer->writeStartElement(dbNamespace, "para");
1328         generateText(brief, node);
1329         writer->writeEndElement(); // para
1330         newLine();
1331     }
1332 }
1333 
generateSince(const Node * node)1334 bool DocBookGenerator::generateSince(const Node *node)
1335 {
1336     // From Generator::generateSince.
1337     if (!node->since().isEmpty()) {
1338         writer->writeStartElement(dbNamespace, "para");
1339         writer->writeCharacters("This " + typeString(node) + " was introduced");
1340         if (node->nodeType() == Node::Enum)
1341             writer->writeCharacters(" or modified");
1342         writer->writeCharacters(" in " + formatSince(node) + ".");
1343         writer->writeEndElement(); // para
1344         newLine();
1345 
1346         return true;
1347     }
1348 
1349     return false;
1350 }
1351 
generateHeader(const QString & title,const QString & subTitle,const Node * node)1352 void DocBookGenerator::generateHeader(const QString &title, const QString &subTitle,
1353                                       const Node *node)
1354 {
1355     // From HtmlGenerator::generateHeader.
1356     refMap.clear();
1357 
1358     // Output the DocBook header.
1359     writer->writeStartElement(dbNamespace, "info");
1360     newLine();
1361     writer->writeTextElement(dbNamespace, "title", title);
1362     newLine();
1363 
1364     if (!subTitle.isEmpty()) {
1365         writer->writeTextElement(dbNamespace, "subtitle", subTitle);
1366         newLine();
1367     }
1368 
1369     if (!project.isEmpty()) {
1370         writer->writeTextElement(dbNamespace, "productname", project);
1371         newLine();
1372     }
1373 
1374     if (!buildversion.isEmpty()) {
1375         writer->writeTextElement(dbNamespace, "edition", buildversion);
1376         newLine();
1377     }
1378 
1379     if (!projectDescription.isEmpty()) {
1380         writer->writeTextElement(dbNamespace, "titleabbrev", projectDescription);
1381         newLine();
1382     }
1383 
1384     // Deal with links.
1385     // Adapted from HtmlGenerator::generateHeader (output part: no need to update a navigationLinks
1386     // or useSeparator field, as this content is only output in the info tag, not in the main
1387     // content).
1388     if (node && !node->links().empty()) {
1389         QPair<QString, QString> linkPair;
1390         QPair<QString, QString> anchorPair;
1391         const Node *linkNode;
1392 
1393         if (node->links().contains(Node::PreviousLink)) {
1394             linkPair = node->links()[Node::PreviousLink];
1395             linkNode = qdb_->findNodeForTarget(linkPair.first, node);
1396             if (!linkNode || linkNode == node)
1397                 anchorPair = linkPair;
1398             else
1399                 anchorPair = anchorForNode(linkNode);
1400 
1401             writer->writeStartElement(dbNamespace, "extendedlink");
1402             writer->writeEmptyElement(dbNamespace, "link");
1403             writer->writeAttribute(xlinkNamespace, "to", anchorPair.first);
1404             writer->writeAttribute(xlinkNamespace, "title", "prev");
1405             if (linkPair.first == linkPair.second && !anchorPair.second.isEmpty())
1406                 writer->writeAttribute(xlinkNamespace, "label", anchorPair.second);
1407             else
1408                 writer->writeAttribute(xlinkNamespace, "label", linkPair.second);
1409             writer->writeEndElement(); // extendedlink
1410         }
1411         if (node->links().contains(Node::NextLink)) {
1412             linkPair = node->links()[Node::NextLink];
1413             linkNode = qdb_->findNodeForTarget(linkPair.first, node);
1414             if (!linkNode || linkNode == node)
1415                 anchorPair = linkPair;
1416             else
1417                 anchorPair = anchorForNode(linkNode);
1418 
1419             writer->writeStartElement(dbNamespace, "extendedlink");
1420             writer->writeEmptyElement(dbNamespace, "link");
1421             writer->writeAttribute(xlinkNamespace, "to", anchorPair.first);
1422             writer->writeAttribute(xlinkNamespace, "title", "prev");
1423             if (linkPair.first == linkPair.second && !anchorPair.second.isEmpty())
1424                 writer->writeAttribute(xlinkNamespace, "label", anchorPair.second);
1425             else
1426                 writer->writeAttribute(xlinkNamespace, "label", linkPair.second);
1427             writer->writeEndElement(); // extendedlink
1428         }
1429         if (node->links().contains(Node::StartLink)) {
1430             linkPair = node->links()[Node::StartLink];
1431             linkNode = qdb_->findNodeForTarget(linkPair.first, node);
1432             if (!linkNode || linkNode == node)
1433                 anchorPair = linkPair;
1434             else
1435                 anchorPair = anchorForNode(linkNode);
1436 
1437             writer->writeStartElement(dbNamespace, "extendedlink");
1438             writer->writeEmptyElement(dbNamespace, "link");
1439             writer->writeAttribute(xlinkNamespace, "to", anchorPair.first);
1440             writer->writeAttribute(xlinkNamespace, "title", "start");
1441             if (linkPair.first == linkPair.second && !anchorPair.second.isEmpty())
1442                 writer->writeAttribute(xlinkNamespace, "label", anchorPair.second);
1443             else
1444                 writer->writeAttribute(xlinkNamespace, "label", linkPair.second);
1445             writer->writeEndElement(); // extendedlink
1446         }
1447     }
1448 
1449     // Deal with the abstract (what qdoc calls brief).
1450     if (node) {
1451         // Adapted from HtmlGenerator::generateBrief, without extraction marks. The parameter
1452         // addLink is always false. Factoring this function out is not as easy as in HtmlGenerator:
1453         // abstracts only happen in the header (info tag), slightly different tags must be used at
1454         // other places. Also includes code from HtmlGenerator::generateCppReferencePage to handle
1455         // the name spaces.
1456         writer->writeStartElement(dbNamespace, "abstract");
1457         newLine();
1458 
1459         bool generatedSomething = false;
1460 
1461         Text brief;
1462         const NamespaceNode *ns = node->isAggregate()
1463                 ? static_cast<const NamespaceNode *>(static_cast<const Aggregate *>(node))
1464                 : nullptr;
1465         if (node->isAggregate() && ns && !ns->hasDoc() && ns->docNode()) {
1466             NamespaceNode *NS = ns->docNode();
1467             brief << "The " << ns->name()
1468                   << " namespace includes the following elements from module "
1469                   << ns->tree()->camelCaseModuleName() << ". The full namespace is "
1470                   << "documented in module " << NS->tree()->camelCaseModuleName()
1471                   << Atom(Atom::LinkNode, fullDocumentLocation(NS))
1472                   << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
1473                   << Atom(Atom::String, " here.")
1474                   << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
1475         } else {
1476             brief = node->doc().briefText();
1477         }
1478 
1479         if (!brief.isEmpty()) {
1480             if (!brief.lastAtom()->string().endsWith('.'))
1481                 brief << Atom(Atom::String, ".");
1482 
1483             writer->writeStartElement(dbNamespace, "para");
1484             generateText(brief, node);
1485             writer->writeEndElement(); // para
1486             newLine();
1487 
1488             generatedSomething = true;
1489         }
1490 
1491         // Generate other paragraphs that should go into the abstract.
1492         generatedSomething |= generateStatus(node);
1493         generatedSomething |= generateSince(node);
1494         generatedSomething |= generateThreadSafeness(node);
1495 
1496         // An abstract cannot be empty, hence use the project description.
1497         if (!generatedSomething)
1498             writer->writeTextElement(dbNamespace, "para", projectDescription + ".");
1499 
1500         writer->writeEndElement(); // abstract
1501         newLine();
1502     }
1503 
1504     // End of the DocBook header.
1505     writer->writeEndElement(); // info
1506     newLine();
1507 }
1508 
closeTextSections()1509 void DocBookGenerator::closeTextSections()
1510 {
1511     while (!sectionLevels.isEmpty()) {
1512         sectionLevels.pop();
1513         endSection();
1514     }
1515 }
1516 
generateFooter()1517 void DocBookGenerator::generateFooter()
1518 {
1519     closeTextSections();
1520     writer->writeEndElement(); // article
1521 }
1522 
generateSimpleLink(const QString & href,const QString & text)1523 void DocBookGenerator::generateSimpleLink(const QString &href, const QString &text)
1524 {
1525     writer->writeStartElement(dbNamespace, "link");
1526     writer->writeAttribute(xlinkNamespace, "href", href);
1527     writer->writeCharacters(text);
1528     writer->writeEndElement(); // link
1529 }
1530 
generateObsoleteMembers(const Sections & sections)1531 void DocBookGenerator::generateObsoleteMembers(const Sections &sections)
1532 {
1533     // From HtmlGenerator::generateObsoleteMembersFile.
1534     SectionPtrVector summary_spv; // Summaries are ignored in DocBook (table of contents).
1535     SectionPtrVector details_spv;
1536     if (!sections.hasObsoleteMembers(&summary_spv, &details_spv))
1537         return;
1538 
1539     Aggregate *aggregate = sections.aggregate();
1540     QString link;
1541     if (useOutputSubdirs() && !Generator::outputSubdir().isEmpty())
1542         link = QString("../" + Generator::outputSubdir() + QLatin1Char('/'));
1543     link += fileName(aggregate, fileExtension());
1544     aggregate->setObsoleteLink(link);
1545 
1546     startSection("obsolete", "Obsolete Members for " + aggregate->name());
1547 
1548     writer->writeStartElement(dbNamespace, "para");
1549     writer->writeStartElement(dbNamespace, "emphasis");
1550     writer->writeAttribute("role", "bold");
1551     writer->writeCharacters("The following members of class ");
1552     generateSimpleLink(linkForNode(aggregate, nullptr), aggregate->name());
1553     writer->writeCharacters(" are obsolete.");
1554     writer->writeEndElement(); // emphasis bold
1555     writer->writeCharacters(" They are provided to keep old source code working. "
1556                             "We strongly advise against using them in new code.");
1557     writer->writeEndElement(); // para
1558     newLine();
1559 
1560     for (int i = 0; i < details_spv.size(); ++i) {
1561         QString title = details_spv.at(i)->title();
1562         QString ref = registerRef(title.toLower());
1563         startSection(ref, title);
1564 
1565         const NodeVector &members = details_spv.at(i)->obsoleteMembers();
1566         NodeVector::ConstIterator m = members.constBegin();
1567         while (m != members.constEnd()) {
1568             if ((*m)->access() != Node::Private)
1569                 generateDetailedMember(*m, aggregate);
1570             ++m;
1571         }
1572 
1573         endSection();
1574     }
1575 
1576     endSection();
1577 }
1578 
1579 /*!
1580   Generates a separate file where obsolete members of the QML
1581   type \a qcn are listed. The \a marker is used to generate
1582   the section lists, which are then traversed and output here.
1583 
1584   Note that this function currently only handles correctly the
1585   case where \a status is \c {Section::Obsolete}.
1586  */
generateObsoleteQmlMembers(const Sections & sections)1587 void DocBookGenerator::generateObsoleteQmlMembers(const Sections &sections)
1588 {
1589     // From HtmlGenerator::generateObsoleteQmlMembersFile.
1590     SectionPtrVector summary_spv; // Summaries are not useful in DocBook.
1591     SectionPtrVector details_spv;
1592     if (!sections.hasObsoleteMembers(&summary_spv, &details_spv))
1593         return;
1594 
1595     Aggregate *aggregate = sections.aggregate();
1596     QString title = "Obsolete Members for " + aggregate->name();
1597     QString fn = fileName(aggregate, fileExtension());
1598     QString link;
1599     if (useOutputSubdirs() && !Generator::outputSubdir().isEmpty())
1600         link = QString("../" + Generator::outputSubdir() + QLatin1Char('/'));
1601     link += fn;
1602     aggregate->setObsoleteLink(link);
1603 
1604     startSection("obsolete", "Obsolete Members for " + aggregate->name());
1605 
1606     writer->writeStartElement(dbNamespace, "para");
1607     writer->writeStartElement(dbNamespace, "emphasis");
1608     writer->writeAttribute("role", "bold");
1609     writer->writeCharacters("The following members of QML type ");
1610     generateSimpleLink(linkForNode(aggregate, nullptr), aggregate->name());
1611     writer->writeCharacters(" are obsolete.");
1612     writer->writeEndElement(); // emphasis bold
1613     writer->writeCharacters("They are provided to keep old source code working. "
1614                             "We strongly advise against using them in new code.");
1615     writer->writeEndElement(); // para
1616     newLine();
1617 
1618     for (auto i : details_spv) {
1619         QString ref = registerRef(i->title().toLower());
1620         startSection(ref, i->title());
1621 
1622         NodeVector::ConstIterator m = i->members().constBegin();
1623         while (m != i->members().constEnd()) {
1624             generateDetailedQmlMember(*m, aggregate);
1625             ++m;
1626         }
1627 
1628         endSection();
1629     }
1630 
1631     endSection();
1632 }
1633 
nodeToSynopsisTag(const Node * node)1634 static QString nodeToSynopsisTag(const Node *node)
1635 {
1636     // Order from Node::nodeTypeString.
1637     if (node->isClass() || node->isQmlType() || node->isQmlBasicType())
1638         return QStringLiteral("classsynopsis");
1639     if (node->isNamespace())
1640         return QStringLiteral("namespacesynopsis");
1641     if (node->isPageNode()) {
1642         node->doc().location().warning("Unexpected document node in nodeToSynopsisTag");
1643         return QString();
1644     }
1645     if (node->isEnumType())
1646         return QStringLiteral("enumsynopsis");
1647     if (node->isTypedef())
1648         return QStringLiteral("typedefsynopsis");
1649     if (node->isFunction()) {
1650         // Signals are also encoded as functions (including QML/JS ones).
1651         const auto fn = static_cast<const FunctionNode *>(node);
1652         if (fn->isCtor() || fn->isCCtor() || fn->isMCtor())
1653             return QStringLiteral("constructorsynopsis");
1654         if (fn->isDtor())
1655             return QStringLiteral("destructorsynopsis");
1656         return QStringLiteral("methodsynopsis");
1657     }
1658     if (node->isProperty() || node->isVariable() || node->isQmlProperty())
1659         return QStringLiteral("fieldsynopsis");
1660 
1661     node->doc().location().warning(QString("Unknown node tag %1").arg(node->nodeTypeString()));
1662     return QStringLiteral("synopsis");
1663 }
1664 
generateStartRequisite(const QString & description)1665 void DocBookGenerator::generateStartRequisite(const QString &description)
1666 {
1667     writer->writeStartElement(dbNamespace, "varlistentry");
1668     newLine();
1669     writer->writeTextElement(dbNamespace, "term", description);
1670     newLine();
1671     writer->writeStartElement(dbNamespace, "listitem");
1672     newLine();
1673     writer->writeStartElement(dbNamespace, "para");
1674 }
1675 
generateEndRequisite()1676 void DocBookGenerator::generateEndRequisite()
1677 {
1678     writer->writeEndElement(); // para
1679     newLine();
1680     writer->writeEndElement(); // listitem
1681     newLine();
1682     writer->writeEndElement(); // varlistentry
1683     newLine();
1684 }
1685 
generateRequisite(const QString & description,const QString & value)1686 void DocBookGenerator::generateRequisite(const QString &description, const QString &value)
1687 {
1688     generateStartRequisite(description);
1689     writer->writeCharacters(value);
1690     generateEndRequisite();
1691 }
1692 
generateSortedNames(const ClassNode * cn,const QVector<RelatedClass> & rc)1693 void DocBookGenerator::generateSortedNames(const ClassNode *cn, const QVector<RelatedClass> &rc)
1694 {
1695     // From Generator::appendSortedNames.
1696     QMap<QString, ClassNode *> classMap;
1697     QVector<RelatedClass>::ConstIterator r = rc.constBegin();
1698     while (r != rc.constEnd()) {
1699         ClassNode *rcn = (*r).node_;
1700         if (rcn && rcn->access() == Node::Public && rcn->status() != Node::Internal
1701             && !rcn->doc().isEmpty()) {
1702             classMap[rcn->plainFullName(cn).toLower()] = rcn;
1703         }
1704         ++r;
1705     }
1706 
1707     QStringList classNames = classMap.keys();
1708     classNames.sort();
1709 
1710     int index = 0;
1711     for (const QString &className : classNames) {
1712         generateFullName(classMap.value(className), cn);
1713         writer->writeCharacters(comma(index++, classNames.count()));
1714     }
1715 }
1716 
generateSortedQmlNames(const Node * base,const NodeList & subs)1717 void DocBookGenerator::generateSortedQmlNames(const Node *base, const NodeList &subs)
1718 {
1719     // From Generator::appendSortedQmlNames.
1720     QMap<QString, Node *> classMap;
1721     int index = 0;
1722 
1723     for (auto sub : subs)
1724         if (!base->isQtQuickNode() || !sub->isQtQuickNode()
1725             || (base->logicalModuleName() == sub->logicalModuleName()))
1726             classMap[sub->plainFullName(base).toLower()] = sub;
1727 
1728     QStringList names = classMap.keys();
1729     names.sort();
1730 
1731     for (const QString &name : names) {
1732         generateFullName(classMap.value(name), base);
1733         writer->writeCharacters(comma(index++, names.count()));
1734     }
1735 }
1736 
1737 /*!
1738   Lists the required imports and includes.
1739 */
generateRequisites(const Aggregate * aggregate)1740 void DocBookGenerator::generateRequisites(const Aggregate *aggregate)
1741 {
1742     // Adapted from HtmlGenerator::generateRequisites, but simplified: no need to store all the
1743     // elements, they can be produced one by one.
1744     writer->writeStartElement(dbNamespace, "variablelist");
1745     newLine();
1746 
1747     // Includes.
1748     if (!aggregate->includeFiles().isEmpty()) {
1749         for (const QString &include : aggregate->includeFiles())
1750             generateRequisite("Header", include);
1751     }
1752 
1753     // Since and project.
1754     if (!aggregate->since().isEmpty())
1755         generateRequisite("Since", formatSince(aggregate));
1756 
1757     if (aggregate->isClassNode() || aggregate->isNamespace()) {
1758         // QT variable.
1759         if (!aggregate->physicalModuleName().isEmpty()) {
1760             const CollectionNode *cn =
1761                     qdb_->getCollectionNode(aggregate->physicalModuleName(), Node::Module);
1762             if (cn && !cn->qtVariable().isEmpty()) {
1763                 generateRequisite("qmake", "QT += " + cn->qtVariable());
1764             }
1765         }
1766     }
1767 
1768     if (aggregate->nodeType() == Node::Class) {
1769         // Instantiated by.
1770         auto *classe = const_cast<ClassNode *>(static_cast<const ClassNode *>(aggregate));
1771         if (classe->qmlElement() != nullptr && classe->status() != Node::Internal) {
1772             generateStartRequisite("Inherited By");
1773             generateSortedNames(classe, classe->derivedClasses());
1774             generateEndRequisite();
1775             generateRequisite("Instantiated By", fullDocumentLocation(classe->qmlElement()));
1776         }
1777 
1778         // Inherits.
1779         QVector<RelatedClass>::ConstIterator r;
1780         if (!classe->baseClasses().isEmpty()) {
1781             generateStartRequisite("Inherits");
1782 
1783             r = classe->baseClasses().constBegin();
1784             int index = 0;
1785             while (r != classe->baseClasses().constEnd()) {
1786                 if ((*r).node_) {
1787                     generateFullName((*r).node_, classe);
1788 
1789                     if ((*r).access_ == Node::Protected)
1790                         writer->writeCharacters(" (protected)");
1791                     else if ((*r).access_ == Node::Private)
1792                         writer->writeCharacters(" (private)");
1793                     writer->writeCharacters(comma(index++, classe->baseClasses().count()));
1794                 }
1795                 ++r;
1796             }
1797 
1798             generateEndRequisite();
1799         }
1800 
1801         // Inherited by.
1802         if (!classe->derivedClasses().isEmpty()) {
1803             generateStartRequisite("Inherited By");
1804             generateSortedNames(classe, classe->derivedClasses());
1805             generateEndRequisite();
1806         }
1807     }
1808 
1809     writer->writeEndElement(); // variablelist
1810     newLine();
1811 }
1812 
1813 /*!
1814   Lists the required imports and includes.
1815 */
generateQmlRequisites(const QmlTypeNode * qcn)1816 void DocBookGenerator::generateQmlRequisites(const QmlTypeNode *qcn)
1817 {
1818     // From HtmlGenerator::generateQmlRequisites, but simplified: no need to store all the elements,
1819     // they can be produced one by one.
1820     if (!qcn)
1821         return;
1822 
1823     writer->writeStartElement(dbNamespace, "variablelist");
1824     newLine();
1825 
1826     // Module name and version (i.e. import).
1827     QString logicalModuleVersion;
1828     const CollectionNode *collection = qcn->logicalModule();
1829 
1830     // skip import statement for \internal collections
1831     if (!collection || !collection->isInternal() || showInternal_) {
1832         logicalModuleVersion =
1833                 collection ? collection->logicalModuleVersion() : qcn->logicalModuleVersion();
1834 
1835         generateRequisite("Import Statement",
1836                           "import " + qcn->logicalModuleName() + QLatin1Char(' ')
1837                                   + logicalModuleVersion);
1838     }
1839 
1840     // Since and project.
1841     if (!qcn->since().isEmpty())
1842         generateRequisite("Since:", formatSince(qcn));
1843 
1844     // Inherited by.
1845     NodeList subs;
1846     QmlTypeNode::subclasses(qcn, subs);
1847     if (!subs.isEmpty()) {
1848         generateStartRequisite("Inherited By:");
1849         generateSortedQmlNames(qcn, subs);
1850         generateEndRequisite();
1851     }
1852 
1853     // Inherits.
1854     QmlTypeNode *base = qcn->qmlBaseNode();
1855     while (base && base->isInternal()) {
1856         base = base->qmlBaseNode();
1857     }
1858     if (base) {
1859         const Node *otherNode = nullptr;
1860         Atom a = Atom(Atom::LinkNode, CodeMarker::stringForNode(base));
1861         QString link = getAutoLink(&a, qcn, &otherNode);
1862 
1863         generateStartRequisite("Inherits:");
1864         generateSimpleLink(link, base->name());
1865         generateEndRequisite();
1866     }
1867 
1868     // Instantiates.
1869     ClassNode *cn = (const_cast<QmlTypeNode *>(qcn))->classNode();
1870     if (cn && (cn->status() != Node::Internal)) {
1871         const Node *otherNode = nullptr;
1872         Atom a = Atom(Atom::LinkNode, CodeMarker::stringForNode(qcn));
1873         QString link = getAutoLink(&a, cn, &otherNode);
1874 
1875         generateStartRequisite("Instantiates:");
1876         generateSimpleLink(fullDocumentLocation(cn), cn->name());
1877         generateEndRequisite();
1878     }
1879 
1880     writer->writeEndElement(); // variablelist
1881     newLine();
1882 }
1883 
generateStatus(const Node * node)1884 bool DocBookGenerator::generateStatus(const Node *node)
1885 {
1886     // From Generator::generateStatus.
1887     switch (node->status()) {
1888     case Node::Active:
1889         // Do nothing.
1890         return false;
1891     case Node::Preliminary:
1892         writer->writeStartElement(dbNamespace, "para");
1893         writer->writeStartElement(dbNamespace, "emphasis");
1894         writer->writeAttribute("role", "bold");
1895         writer->writeCharacters("This " + typeString(node)
1896                                 + " is under development and is subject to change.");
1897         writer->writeEndElement(); // emphasis
1898         writer->writeEndElement(); // para
1899         newLine();
1900         return true;
1901     case Node::Deprecated:
1902         writer->writeStartElement(dbNamespace, "para");
1903         if (node->isAggregate()) {
1904             writer->writeStartElement(dbNamespace, "emphasis");
1905             writer->writeAttribute("role", "bold");
1906         }
1907         writer->writeCharacters("This " + typeString(node) + " is deprecated.");
1908         if (node->isAggregate())
1909             writer->writeEndElement(); // emphasis
1910         writer->writeEndElement(); // para
1911         newLine();
1912         return true;
1913     case Node::Obsolete:
1914         writer->writeStartElement(dbNamespace, "para");
1915         if (node->isAggregate()) {
1916             writer->writeStartElement(dbNamespace, "emphasis");
1917             writer->writeAttribute("role", "bold");
1918         }
1919         writer->writeCharacters("This " + typeString(node) + " is obsolete.");
1920         if (node->isAggregate())
1921             writer->writeEndElement(); // emphasis
1922         writer->writeCharacters(" It is provided to keep old source code working. "
1923                                 "We strongly advise against using it in new code.");
1924         writer->writeEndElement(); // para
1925         newLine();
1926         return true;
1927     case Node::Internal:
1928     default:
1929         return false;
1930     }
1931 }
1932 
1933 /*!
1934   Generate a list of function signatures. The function nodes
1935   are in \a nodes.
1936  */
generateSignatureList(const NodeList & nodes)1937 void DocBookGenerator::generateSignatureList(const NodeList &nodes)
1938 {
1939     // From Generator::signatureList and Generator::appendSignature.
1940     writer->writeStartElement(dbNamespace, "itemizedlist");
1941     newLine();
1942 
1943     NodeList::ConstIterator n = nodes.constBegin();
1944     while (n != nodes.constEnd()) {
1945         writer->writeStartElement(dbNamespace, "listitem");
1946         newLine();
1947         writer->writeStartElement(dbNamespace, "para");
1948 
1949         generateSimpleLink(currentGenerator()->fullDocumentLocation(*n),
1950                            (*n)->signature(false, true));
1951 
1952         writer->writeEndElement(); // para
1953         newLine();
1954         writer->writeEndElement(); // itemizedlist
1955         newLine();
1956         ++n;
1957     }
1958 
1959     writer->writeEndElement(); // itemizedlist
1960     newLine();
1961 }
1962 
1963 /*!
1964   Generates text that explains how threadsafe and/or reentrant
1965   \a node is.
1966  */
generateThreadSafeness(const Node * node)1967 bool DocBookGenerator::generateThreadSafeness(const Node *node)
1968 {
1969     // From Generator::generateThreadSafeness
1970     Node::ThreadSafeness ts = node->threadSafeness();
1971 
1972     const Node *reentrantNode;
1973     Atom reentrantAtom = Atom(Atom::Link, "reentrant");
1974     QString linkReentrant = getAutoLink(&reentrantAtom, node, &reentrantNode);
1975     const Node *threadSafeNode;
1976     Atom threadSafeAtom = Atom(Atom::Link, "thread-safe");
1977     QString linkThreadSafe = getAutoLink(&threadSafeAtom, node, &threadSafeNode);
1978 
1979     if (ts == Node::NonReentrant) {
1980         writer->writeStartElement(dbNamespace, "warning");
1981         newLine();
1982         writer->writeStartElement(dbNamespace, "para");
1983         writer->writeCharacters("This " + typeString(node) + " is not ");
1984         generateSimpleLink(linkReentrant, "reentrant");
1985         writer->writeCharacters(".");
1986         writer->writeEndElement(); // para
1987         newLine();
1988         writer->writeEndElement(); // warning
1989 
1990         return true;
1991     }
1992     if (ts == Node::Reentrant || ts == Node::ThreadSafe) {
1993         writer->writeStartElement(dbNamespace, "note");
1994         newLine();
1995         writer->writeStartElement(dbNamespace, "para");
1996 
1997         if (node->isAggregate()) {
1998             writer->writeCharacters("All functions in this " + typeString(node) + " are ");
1999             if (ts == Node::ThreadSafe)
2000                 generateSimpleLink(linkThreadSafe, "thread-safe");
2001             else
2002                 generateSimpleLink(linkReentrant, "reentrant");
2003 
2004             NodeList reentrant;
2005             NodeList threadsafe;
2006             NodeList nonreentrant;
2007             bool exceptions = hasExceptions(node, reentrant, threadsafe, nonreentrant);
2008             if (!exceptions || (ts == Node::Reentrant && !threadsafe.isEmpty())) {
2009                 writer->writeCharacters(".");
2010                 writer->writeEndElement(); // para
2011                 newLine();
2012             } else {
2013                 writer->writeCharacters(" with the following exceptions:");
2014                 writer->writeEndElement(); // para
2015                 newLine();
2016                 writer->writeStartElement(dbNamespace, "para");
2017 
2018                 if (ts == Node::Reentrant) {
2019                     if (!nonreentrant.isEmpty()) {
2020                         writer->writeCharacters("These functions are not ");
2021                         generateSimpleLink(linkReentrant, "reentrant");
2022                         writer->writeCharacters(":");
2023                         writer->writeEndElement(); // para
2024                         newLine();
2025                         generateSignatureList(nonreentrant);
2026                     }
2027                     if (!threadsafe.isEmpty()) {
2028                         writer->writeCharacters("These functions are also ");
2029                         generateSimpleLink(linkThreadSafe, "thread-safe");
2030                         writer->writeCharacters(":");
2031                         writer->writeEndElement(); // para
2032                         newLine();
2033                         generateSignatureList(threadsafe);
2034                     }
2035                 } else { // thread-safe
2036                     if (!reentrant.isEmpty()) {
2037                         writer->writeCharacters("These functions are only ");
2038                         generateSimpleLink(linkReentrant, "reentrant");
2039                         writer->writeCharacters(":");
2040                         writer->writeEndElement(); // para
2041                         newLine();
2042                         generateSignatureList(reentrant);
2043                     }
2044                     if (!nonreentrant.isEmpty()) {
2045                         writer->writeCharacters("These functions are not ");
2046                         generateSimpleLink(linkReentrant, "reentrant");
2047                         writer->writeCharacters(":");
2048                         writer->writeEndElement(); // para
2049                         newLine();
2050                         generateSignatureList(nonreentrant);
2051                     }
2052                 }
2053             }
2054         } else {
2055             writer->writeCharacters("This " + typeString(node) + " is ");
2056             if (ts == Node::ThreadSafe)
2057                 generateSimpleLink(linkThreadSafe, "thread-safe");
2058             else
2059                 generateSimpleLink(linkReentrant, "reentrant");
2060             writer->writeCharacters(".");
2061             writer->writeEndElement(); // para
2062             newLine();
2063         }
2064         writer->writeEndElement(); // note
2065 
2066         return true;
2067     }
2068 
2069     return false;
2070 }
2071 
2072 /*!
2073   Generate the body of the documentation from the qdoc comment
2074   found with the entity represented by the \a node.
2075  */
generateBody(const Node * node)2076 void DocBookGenerator::generateBody(const Node *node)
2077 {
2078     // From Generator::generateBody, without warnings.
2079     const FunctionNode *fn = node->isFunction() ? static_cast<const FunctionNode *>(node) : nullptr;
2080 
2081     if (!node->hasDoc() && !node->hasSharedDoc()) {
2082         /*
2083           Test for special function, like a destructor or copy constructor,
2084           that has no documentation.
2085         */
2086         if (fn) {
2087             QString t;
2088             if (fn->isDtor()) {
2089                 t = "Destroys the instance of " + fn->parent()->name() + ".";
2090                 if (fn->isVirtual())
2091                     t += " The destructor is virtual.";
2092             } else if (fn->isCtor()) {
2093                 t = "Default constructs an instance of " + fn->parent()->name() + ".";
2094             } else if (fn->isCCtor()) {
2095                 t = "Copy constructor.";
2096             } else if (fn->isMCtor()) {
2097                 t = "Move-copy constructor.";
2098             } else if (fn->isCAssign()) {
2099                 t = "Copy-assignment constructor.";
2100             } else if (fn->isMAssign()) {
2101                 t = "Move-assignment constructor.";
2102             }
2103 
2104             if (!t.isEmpty())
2105                 writer->writeTextElement(dbNamespace, "para", t);
2106         }
2107     } else if (!node->isSharingComment()) {
2108         // Reimplements clause and type alias info precede body text
2109         if (fn && !fn->overridesThis().isEmpty())
2110             generateReimplementsClause(fn);
2111         else if (node->isTypeAlias())
2112             generateAddendum(node, TypeAlias, nullptr, false);
2113 
2114         if (!generateText(node->doc().body(), node)) {
2115             if (node->isMarkedReimp())
2116                 return;
2117         }
2118 
2119         if (fn) {
2120             if (fn->isQmlSignal())
2121                 generateAddendum(node, QmlSignalHandler);
2122             if (fn->isPrivateSignal())
2123                 generateAddendum(node, PrivateSignal);
2124             if (fn->isInvokable())
2125                 generateAddendum(node, Invokable);
2126             if (fn->hasAssociatedProperties())
2127                 generateAddendum(node, AssociatedProperties);
2128         }
2129 
2130         // Warning generation skipped with respect to Generator::generateBody.
2131     }
2132 
2133     generateRequiredLinks(node);
2134 }
2135 
2136 /*!
2137   Generates either a link to the project folder for example \a node, or a list
2138   of links files/images if 'url.examples config' variable is not defined.
2139 
2140   Does nothing for non-example nodes.
2141 */
generateRequiredLinks(const Node * node)2142 void DocBookGenerator::generateRequiredLinks(const Node *node)
2143 {
2144     // From Generator::generateRequiredLinks.
2145     if (!node->isExample())
2146         return;
2147 
2148     const auto en = static_cast<const ExampleNode *>(node);
2149     QString exampleUrl = Config::instance().getString(CONFIG_URL + Config::dot + CONFIG_EXAMPLES);
2150 
2151     if (exampleUrl.isEmpty()) {
2152         if (!en->noAutoList()) {
2153             generateFileList(en, false); // files
2154             generateFileList(en, true); // images
2155         }
2156     } else {
2157         generateLinkToExample(en, exampleUrl);
2158     }
2159 }
2160 
2161 /*!
2162   The path to the example replaces a placeholder '\1' character if
2163   one is found in the \a baseUrl string.  If no such placeholder is found,
2164   the path is appended to \a baseUrl, after a '/' character if \a baseUrl did
2165   not already end in one.
2166 */
generateLinkToExample(const ExampleNode * en,const QString & baseUrl)2167 void DocBookGenerator::generateLinkToExample(const ExampleNode *en, const QString &baseUrl)
2168 {
2169     // From Generator::generateLinkToExample.
2170     QString exampleUrl(baseUrl);
2171     QString link;
2172 #ifndef QT_BOOTSTRAPPED
2173     link = QUrl(exampleUrl).host();
2174 #endif
2175     if (!link.isEmpty())
2176         link.prepend(" @ ");
2177     link.prepend("Example project");
2178 
2179     const QLatin1Char separator('/');
2180     const QLatin1Char placeholder('\1');
2181     if (!exampleUrl.contains(placeholder)) {
2182         if (!exampleUrl.endsWith(separator))
2183             exampleUrl += separator;
2184         exampleUrl += placeholder;
2185     }
2186 
2187     // Construct a path to the example; <install path>/<example name>
2188     QStringList path = QStringList()
2189             << Config::instance().getString(CONFIG_EXAMPLESINSTALLPATH) << en->name();
2190     path.removeAll({});
2191 
2192     writer->writeStartElement(dbNamespace, "para");
2193     writer->writeStartElement(dbNamespace, "link");
2194     writer->writeAttribute(xlinkNamespace, "href",
2195                            exampleUrl.replace(placeholder, path.join(separator)));
2196     writer->writeCharacters(link);
2197     writer->writeEndElement(); // link
2198     writer->writeEndElement(); // para
2199     newLine();
2200 }
2201 
2202 /*!
2203   This function is called when the documentation for an example is
2204   being formatted. It outputs a list of files for the example, which
2205   can be the example's source files or the list of images used by the
2206   example. The images are copied into a subtree of
2207   \c{...doc/html/images/used-in-examples/...}
2208 */
generateFileList(const ExampleNode * en,bool images)2209 void DocBookGenerator::generateFileList(const ExampleNode *en, bool images)
2210 {
2211     // From Generator::generateFileList
2212     QString tag;
2213     QStringList paths;
2214     if (images) {
2215         paths = en->images();
2216         tag = "Images:";
2217     } else { // files
2218         paths = en->files();
2219         tag = "Files:";
2220     }
2221     std::sort(paths.begin(), paths.end(), Generator::comparePaths);
2222 
2223     if (paths.isEmpty())
2224         return;
2225 
2226     writer->writeStartElement(dbNamespace, "para");
2227     writer->writeCharacters(tag);
2228     writer->writeEndElement(); // para
2229     newLine();
2230 
2231     writer->writeStartElement(dbNamespace, "itemizedlist");
2232 
2233     for (const auto &file : qAsConst(paths)) {
2234         if (images) {
2235             if (!file.isEmpty())
2236                 addImageToCopy(en, file);
2237         } else {
2238             generateExampleFilePage(en, file);
2239         }
2240 
2241         writer->writeStartElement(dbNamespace, "listitem");
2242         newLine();
2243         writer->writeStartElement(dbNamespace, "para");
2244         generateSimpleLink(file, file);
2245         writer->writeEndElement(); // para
2246         writer->writeEndElement(); // listitem
2247         newLine();
2248     }
2249 
2250     writer->writeEndElement(); // itemizedlist
2251     newLine();
2252 }
2253 
2254 /*!
2255   Generate a file with the contents of a C++ or QML source file.
2256  */
generateExampleFilePage(const Node * node,const QString & file,CodeMarker * marker)2257 void DocBookGenerator::generateExampleFilePage(const Node *node, const QString &file,
2258                                                CodeMarker *marker)
2259 {
2260     Q_UNUSED(marker);
2261     // From HtmlGenerator::generateExampleFilePage.
2262     if (!node->isExample())
2263         return;
2264 
2265     const auto en = static_cast<const ExampleNode *>(node);
2266 
2267     // Store current (active) writer
2268     QXmlStreamWriter *currentWriter = writer;
2269     writer = startDocument(en, file);
2270     generateHeader(en->fullTitle(), en->subtitle(), en);
2271 
2272     Text text;
2273     Quoter quoter;
2274     Doc::quoteFromFile(en->doc().location(), quoter, file);
2275     QString code = quoter.quoteTo(en->location(), QString(), QString());
2276     CodeMarker *codeMarker = CodeMarker::markerForFileName(file);
2277     text << Atom(codeMarker->atomType(), code);
2278     Atom a(codeMarker->atomType(), code);
2279     generateText(text, en);
2280 
2281     endDocument();
2282     // Restore writer
2283     writer = currentWriter;
2284 }
2285 
generateReimplementsClause(const FunctionNode * fn)2286 void DocBookGenerator::generateReimplementsClause(const FunctionNode *fn)
2287 {
2288     // From Generator::generateReimplementsClause, without warning generation.
2289     if (!fn->overridesThis().isEmpty()) {
2290         if (fn->parent()->isClassNode()) {
2291             auto cn = static_cast<ClassNode *>(fn->parent());
2292             const FunctionNode *overrides = cn->findOverriddenFunction(fn);
2293             if (overrides && !overrides->isPrivate() && !overrides->parent()->isPrivate()) {
2294                 if (overrides->hasDoc()) {
2295                     writer->writeStartElement(dbNamespace, "para");
2296                     writer->writeCharacters("Reimplements: ");
2297                     QString fullName =
2298                             overrides->parent()->name() + "::" + overrides->signature(false, true);
2299                     generateFullName(overrides->parent(), fullName, overrides);
2300                     writer->writeCharacters(".");
2301                     return;
2302                 }
2303             }
2304             const PropertyNode *sameName = cn->findOverriddenProperty(fn);
2305             if (sameName && sameName->hasDoc()) {
2306                 writer->writeStartElement(dbNamespace, "para");
2307                 writer->writeCharacters("Reimplements an access function for property: ");
2308                 QString fullName = sameName->parent()->name() + "::" + sameName->name();
2309                 generateFullName(sameName->parent(), fullName, overrides);
2310                 writer->writeCharacters(".");
2311                 return;
2312             }
2313         }
2314     }
2315 }
2316 
generateAlsoList(const Node * node,CodeMarker * marker)2317 void DocBookGenerator::generateAlsoList(const Node *node, CodeMarker *marker)
2318 {
2319     Q_UNUSED(marker);
2320     // From Generator::generateAlsoList.
2321     QVector<Text> alsoList = node->doc().alsoList();
2322     supplementAlsoList(node, alsoList);
2323 
2324     if (!alsoList.isEmpty()) {
2325         writer->writeStartElement(dbNamespace, "para");
2326         writer->writeStartElement(dbNamespace, "emphasis");
2327         writer->writeCharacters("See also ");
2328         writer->writeEndElement(); // emphasis
2329         newLine();
2330 
2331         writer->writeStartElement(dbNamespace, "simplelist");
2332         writer->writeAttribute("type", "vert");
2333         writer->writeAttribute("role", "see-also");
2334         for (const Text &text : alsoList) {
2335             writer->writeStartElement(dbNamespace, "member");
2336             generateText(text, node);
2337             writer->writeEndElement(); // member
2338             newLine();
2339         }
2340         writer->writeEndElement(); // simplelist
2341         newLine();
2342 
2343         writer->writeEndElement(); // para
2344     }
2345 }
2346 
2347 /*!
2348   Generate a list of maintainers in the output
2349  */
generateMaintainerList(const Aggregate * node,CodeMarker * marker)2350 void DocBookGenerator::generateMaintainerList(const Aggregate *node, CodeMarker *marker)
2351 {
2352     Q_UNUSED(marker);
2353     // From Generator::generateMaintainerList.
2354     QStringList sl = getMetadataElements(node, "maintainer");
2355 
2356     if (!sl.isEmpty()) {
2357         writer->writeStartElement(dbNamespace, "para");
2358         writer->writeStartElement(dbNamespace, "emphasis");
2359         writer->writeCharacters("Maintained by: ");
2360         writer->writeEndElement(); // emphasis
2361         newLine();
2362 
2363         writer->writeStartElement(dbNamespace, "simplelist");
2364         writer->writeAttribute("type", "vert");
2365         writer->writeAttribute("role", "maintainer");
2366         for (int i = 0; i < sl.size(); ++i) {
2367             writer->writeStartElement(dbNamespace, "member");
2368             writer->writeCharacters(sl.at(i));
2369             writer->writeEndElement(); // member
2370             newLine();
2371         }
2372         writer->writeEndElement(); // simplelist
2373         newLine();
2374 
2375         writer->writeEndElement(); // para
2376     }
2377 }
2378 
2379 /*!
2380   Open a new file to write XML contents, including the DocBook
2381   opening tag.
2382  */
startGenericDocument(const Node * node,const QString & fileName)2383 QXmlStreamWriter *DocBookGenerator::startGenericDocument(const Node *node, const QString &fileName)
2384 {
2385     QFile *outFile = openSubPageFile(node, fileName);
2386     writer = new QXmlStreamWriter(outFile);
2387     writer->setAutoFormatting(false); // We need a precise handling of line feeds.
2388 
2389     writer->writeStartDocument();
2390     newLine();
2391     writer->writeNamespace(dbNamespace, "db");
2392     writer->writeNamespace(xlinkNamespace, "xlink");
2393     writer->writeStartElement(dbNamespace, "article");
2394     writer->writeAttribute("version", "5.2");
2395     if (!naturalLanguage.isEmpty())
2396         writer->writeAttribute("xml:lang", naturalLanguage);
2397     newLine();
2398 
2399     // Empty the section stack for the new document.
2400     sectionLevels.resize(0);
2401 
2402     return writer;
2403 }
2404 
startDocument(const Node * node)2405 QXmlStreamWriter *DocBookGenerator::startDocument(const Node *node)
2406 {
2407     QString fileName = Generator::fileName(node, fileExtension());
2408     return startGenericDocument(node, fileName);
2409 }
2410 
startDocument(const ExampleNode * en,const QString & file)2411 QXmlStreamWriter *DocBookGenerator::startDocument(const ExampleNode *en, const QString &file)
2412 {
2413     QString fileName = linkForExampleFile(file, en);
2414     return startGenericDocument(en, fileName);
2415 }
2416 
endDocument()2417 void DocBookGenerator::endDocument()
2418 {
2419     writer->writeEndElement(); // article
2420     writer->writeEndDocument();
2421     writer->device()->close();
2422     delete writer;
2423     writer = nullptr;
2424 }
2425 
2426 /*!
2427   Generate a reference page for the C++ class, namespace, or
2428   header file documented in \a node.
2429  */
generateCppReferencePage(Node * node)2430 void DocBookGenerator::generateCppReferencePage(Node *node)
2431 {
2432     // Based on HtmlGenerator::generateCppReferencePage.
2433     Q_ASSERT(node->isAggregate());
2434     const auto aggregate = static_cast<const Aggregate *>(node);
2435 
2436     QString title;
2437     QString rawTitle;
2438     QString fullTitle;
2439     if (aggregate->isNamespace()) {
2440         rawTitle = aggregate->plainName();
2441         fullTitle = aggregate->plainFullName();
2442         title = rawTitle + " Namespace";
2443     } else if (aggregate->isClass()) {
2444         rawTitle = aggregate->plainName();
2445         QString templateDecl = node->templateDecl();
2446         if (!templateDecl.isEmpty())
2447             fullTitle = QString("%1 %2 ").arg(templateDecl, aggregate->typeWord(false));
2448         fullTitle += aggregate->plainFullName();
2449         title = rawTitle + QLatin1Char(' ') + aggregate->typeWord(true);
2450     } else if (aggregate->isHeader()) {
2451         title = fullTitle = rawTitle = aggregate->fullTitle();
2452     }
2453 
2454     QString subtitleText;
2455     if (rawTitle != fullTitle)
2456         subtitleText = fullTitle;
2457 
2458     // Start producing the DocBook file.
2459     writer = startDocument(node);
2460 
2461     // Info container.
2462     generateHeader(title, subtitleText, aggregate);
2463 
2464     generateRequisites(aggregate);
2465     generateStatus(aggregate);
2466 
2467     // Element synopsis.
2468     generateDocBookSynopsis(node);
2469 
2470     // Actual content.
2471     if (!aggregate->doc().isEmpty()) {
2472         startSection(registerRef("details"), "Detailed Description");
2473 
2474         generateBody(aggregate);
2475         generateAlsoList(aggregate);
2476         generateMaintainerList(aggregate);
2477 
2478         endSection();
2479     }
2480 
2481     Sections sections(const_cast<Aggregate *>(aggregate));
2482     auto *sectionVector =
2483             (aggregate->isNamespace() || aggregate->isHeader()) ?
2484                     &sections.stdDetailsSections() :
2485                     &sections.stdCppClassDetailsSections();
2486     SectionVector::ConstIterator section = sectionVector->constBegin();
2487     while (section != sectionVector->constEnd()) {
2488         bool headerGenerated = false;
2489         NodeVector::ConstIterator member = section->members().constBegin();
2490         while (member != section->members().constEnd()) {
2491             if ((*member)->access() == Node::Private) { // ### check necessary?
2492                 ++member;
2493                 continue;
2494             }
2495 
2496             if (!headerGenerated) {
2497                 // Equivalent to h2
2498                 startSection(registerRef(section->title().toLower()), section->title());
2499                 headerGenerated = true;
2500             }
2501 
2502             if ((*member)->nodeType() != Node::Class) {
2503                 // This function starts its own section.
2504                 generateDetailedMember(*member, aggregate);
2505             } else {
2506                 startSectionBegin();
2507                 writer->writeCharacters("class ");
2508                 generateFullName(*member, aggregate);
2509                 startSectionEnd();
2510                 generateBrief(*member);
2511                 endSection();
2512             }
2513 
2514             ++member;
2515         }
2516 
2517         if (headerGenerated)
2518             endSection();
2519         ++section;
2520     }
2521 
2522     generateObsoleteMembers(sections);
2523 
2524     endDocument();
2525 }
2526 
generateSynopsisInfo(const QString & key,const QString & value)2527 void DocBookGenerator::generateSynopsisInfo(const QString &key, const QString &value)
2528 {
2529     writer->writeStartElement(dbNamespace, "synopsisinfo");
2530     writer->writeAttribute(dbNamespace, "role", key);
2531     writer->writeCharacters(value);
2532     writer->writeEndElement(); // synopsisinfo
2533     newLine();
2534 }
2535 
generateModifier(const QString & value)2536 void DocBookGenerator::generateModifier(const QString &value)
2537 {
2538     writer->writeTextElement(dbNamespace, "modifier", value);
2539     newLine();
2540 }
2541 
2542 /*!
2543   Generate the metadata for the given \a node in DocBook.
2544  */
generateDocBookSynopsis(const Node * node)2545 void DocBookGenerator::generateDocBookSynopsis(const Node *node)
2546 {
2547     if (!node)
2548         return;
2549 
2550     // From Generator::generateStatus, HtmlGenerator::generateRequisites,
2551     // Generator::generateThreadSafeness, QDocIndexFiles::generateIndexSection.
2552 
2553     // This function is the only place where DocBook extensions are used.
2554     if (config->getBool(CONFIG_DOCBOOKEXTENSIONS))
2555         return;
2556 
2557     // Nothing to export in some cases. Note that isSharedCommentNode() returns
2558     // true also for QML property groups.
2559     if (node->isGroup() || node->isGroup() || node->isSharedCommentNode() || node->isModule()
2560         || node->isJsModule() || node->isQmlModule() || node->isPageNode())
2561         return;
2562 
2563     // Cast the node to several subtypes (null pointer if the node is not of the required type).
2564     const Aggregate *aggregate =
2565             node->isAggregate() ? static_cast<const Aggregate *>(node) : nullptr;
2566     const ClassNode *classNode = node->isClass() ? static_cast<const ClassNode *>(node) : nullptr;
2567     const FunctionNode *functionNode =
2568             node->isFunction() ? static_cast<const FunctionNode *>(node) : nullptr;
2569     const PropertyNode *propertyNode =
2570             node->isProperty() ? static_cast<const PropertyNode *>(node) : nullptr;
2571     const VariableNode *variableNode =
2572             node->isVariable() ? static_cast<const VariableNode *>(node) : nullptr;
2573     const EnumNode *enumNode = node->isEnumType() ? static_cast<const EnumNode *>(node) : nullptr;
2574     const QmlPropertyNode *qpn =
2575             node->isQmlProperty() ? static_cast<const QmlPropertyNode *>(node) : nullptr;
2576     const QmlTypeNode *qcn = node->isQmlType() ? static_cast<const QmlTypeNode *>(node) : nullptr;
2577     // Typedefs are ignored, as they correspond to enums.
2578     // Groups and modules are ignored.
2579     // Documents are ignored, they have no interesting metadata.
2580 
2581     // Start the synopsis tag.
2582     QString synopsisTag = nodeToSynopsisTag(node);
2583     writer->writeStartElement(dbNamespace, synopsisTag);
2584     newLine();
2585 
2586     // Name and basic properties of each tag (like types and parameters).
2587     if (node->isClass()) {
2588         writer->writeStartElement(dbNamespace, "ooclass");
2589         writer->writeTextElement(dbNamespace, "classname", node->plainName());
2590         writer->writeEndElement(); // ooclass
2591         newLine();
2592     } else if (node->isNamespace()) {
2593         writer->writeTextElement(dbNamespace, "namespacename", node->plainName());
2594         newLine();
2595     } else if (node->isQmlType()) {
2596         writer->writeStartElement(dbNamespace, "ooclass");
2597         writer->writeTextElement(dbNamespace, "classname", node->plainName());
2598         writer->writeEndElement(); // ooclass
2599         newLine();
2600         if (!qcn->groupNames().isEmpty())
2601             writer->writeAttribute("groups", qcn->groupNames().join(QLatin1Char(',')));
2602     } else if (node->isProperty()) {
2603         writer->writeTextElement(dbNamespace, "modifier", "(Qt property)");
2604         newLine();
2605         writer->writeTextElement(dbNamespace, "type", propertyNode->dataType());
2606         newLine();
2607         writer->writeTextElement(dbNamespace, "varname", node->plainName());
2608         newLine();
2609     } else if (node->isVariable()) {
2610         if (variableNode->isStatic()) {
2611             writer->writeTextElement(dbNamespace, "modifier", "static");
2612             newLine();
2613         }
2614         writer->writeTextElement(dbNamespace, "type", variableNode->dataType());
2615         newLine();
2616         writer->writeTextElement(dbNamespace, "varname", node->plainName());
2617         newLine();
2618     } else if (node->isEnumType()) {
2619         writer->writeTextElement(dbNamespace, "enumname", node->plainName());
2620         newLine();
2621     } else if (node->isQmlProperty()) {
2622         QString name = node->name();
2623         if (qpn->isAttached())
2624             name.prepend(qpn->element() + QLatin1Char('.'));
2625 
2626         writer->writeTextElement(dbNamespace, "type", qpn->dataType());
2627         newLine();
2628         writer->writeTextElement(dbNamespace, "varname", name);
2629         newLine();
2630 
2631         if (qpn->isAttached()) {
2632             writer->writeTextElement(dbNamespace, "modifier", "attached");
2633             newLine();
2634         }
2635         if ((const_cast<QmlPropertyNode *>(qpn))->isWritable()) {
2636             writer->writeTextElement(dbNamespace, "modifier", "writable");
2637             newLine();
2638         }
2639 
2640         if (qpn->isReadOnly()) {
2641             generateModifier("[read-only]");
2642             newLine();
2643         }
2644         if (qpn->isDefault()) {
2645             generateModifier("[default]");
2646             newLine();
2647         }
2648     } else if (node->isFunction()) {
2649         if (functionNode->virtualness() != "non")
2650             generateModifier("virtual");
2651         if (functionNode->isConst())
2652             generateModifier("const");
2653         if (functionNode->isStatic())
2654             generateModifier("static");
2655 
2656         if (!functionNode->isMacro()) {
2657             if (functionNode->returnType() == "void")
2658                 writer->writeEmptyElement(dbNamespace, "void");
2659             else
2660                 writer->writeTextElement(dbNamespace, "type", functionNode->returnType());
2661             newLine();
2662         }
2663         // Remove two characters from the plain name to only get the name
2664         // of the method without parentheses.
2665         writer->writeTextElement(dbNamespace, "methodname", node->plainName().chopped(2));
2666         newLine();
2667 
2668         if (functionNode->isOverload())
2669             generateModifier("overload");
2670         if (functionNode->isDefault())
2671             generateModifier("default");
2672         if (functionNode->isFinal())
2673             generateModifier("final");
2674         if (functionNode->isOverride())
2675             generateModifier("override");
2676 
2677         if (!functionNode->isMacro() && functionNode->parameters().isEmpty()) {
2678             writer->writeEmptyElement(dbNamespace, "void");
2679             newLine();
2680         }
2681 
2682         const Parameters &lp = functionNode->parameters();
2683         for (int i = 0; i < lp.count(); ++i) {
2684             const Parameter &parameter = lp.at(i);
2685             writer->writeStartElement(dbNamespace, "methodparam");
2686             newLine();
2687             writer->writeTextElement(dbNamespace, "type", parameter.type());
2688             newLine();
2689             writer->writeTextElement(dbNamespace, "parameter", parameter.name());
2690             newLine();
2691             if (!parameter.defaultValue().isEmpty()) {
2692                 writer->writeTextElement(dbNamespace, "initializer", parameter.defaultValue());
2693                 newLine();
2694             }
2695             writer->writeEndElement(); // methodparam
2696             newLine();
2697         }
2698 
2699         generateSynopsisInfo("meta", functionNode->metanessString());
2700 
2701         if (functionNode->isOverload())
2702             generateSynopsisInfo("overload-number",
2703                                  QString::number(functionNode->overloadNumber()));
2704 
2705         if (functionNode->isRef())
2706             generateSynopsisInfo("refness", QString::number(1));
2707         else if (functionNode->isRefRef())
2708             generateSynopsisInfo("refness", QString::number(2));
2709 
2710         if (functionNode->hasAssociatedProperties()) {
2711             QStringList associatedProperties;
2712             const NodeList &nodes = functionNode->associatedProperties();
2713             for (const Node *n : nodes) {
2714                 const auto pn = static_cast<const PropertyNode *>(n);
2715                 associatedProperties << pn->name();
2716             }
2717             associatedProperties.sort();
2718             generateSynopsisInfo("associated-property",
2719                                  associatedProperties.join(QLatin1Char(',')));
2720         }
2721 
2722         QString signature = functionNode->signature(false, false);
2723         // 'const' is already part of FunctionNode::signature()
2724         if (functionNode->isFinal())
2725             signature += " final";
2726         if (functionNode->isOverride())
2727             signature += " override";
2728         if (functionNode->isPureVirtual())
2729             signature += " = 0";
2730         else if (functionNode->isDefault())
2731             signature += " = default";
2732         generateSynopsisInfo("signature", signature);
2733     } else if (node->isTypedef()) {
2734         writer->writeTextElement(dbNamespace, "type", node->plainName());
2735     } else {
2736         node->doc().location().warning(tr("Unexpected node type in generateDocBookSynopsis: %1")
2737                                                .arg(node->nodeTypeString()));
2738         newLine();
2739     }
2740 
2741     // Accessibility status.
2742     if (!node->isPageNode() && !node->isCollectionNode()) {
2743         switch (node->access()) {
2744         case Node::Public:
2745             generateSynopsisInfo("access", "public");
2746             break;
2747         case Node::Protected:
2748             generateSynopsisInfo("access", "protected");
2749             break;
2750         case Node::Private:
2751             generateSynopsisInfo("access", "private");
2752             break;
2753         default:
2754             break;
2755         }
2756         if (node->isAbstract())
2757             generateSynopsisInfo("abstract", "true");
2758     }
2759 
2760     // Status.
2761     switch (node->status()) {
2762     case Node::Active:
2763         generateSynopsisInfo("status", "active");
2764         break;
2765     case Node::Preliminary:
2766         generateSynopsisInfo("status", "preliminary");
2767         break;
2768     case Node::Deprecated:
2769         generateSynopsisInfo("status", "deprecated");
2770         break;
2771     case Node::Obsolete:
2772         generateSynopsisInfo("status", "obsolete");
2773         break;
2774     case Node::Internal:
2775         generateSynopsisInfo("status", "internal");
2776         break;
2777     default:
2778         generateSynopsisInfo("status", "main");
2779         break;
2780     }
2781 
2782     // C++ classes and name spaces.
2783     if (aggregate) {
2784         // Includes.
2785         if (!aggregate->includeFiles().isEmpty()) {
2786             for (const QString &include : aggregate->includeFiles())
2787                 generateSynopsisInfo("headers", include);
2788         }
2789 
2790         // Since and project.
2791         if (!aggregate->since().isEmpty())
2792             generateSynopsisInfo("since", formatSince(aggregate));
2793 
2794         if (aggregate->nodeType() == Node::Class || aggregate->nodeType() == Node::Namespace) {
2795             // QT variable.
2796             if (!aggregate->physicalModuleName().isEmpty()) {
2797                 const CollectionNode *cn =
2798                         qdb_->getCollectionNode(aggregate->physicalModuleName(), Node::Module);
2799                 if (cn && !cn->qtVariable().isEmpty())
2800                     generateSynopsisInfo("qmake", "QT += " + cn->qtVariable());
2801             }
2802         }
2803 
2804         if (aggregate->nodeType() == Node::Class) {
2805             // Instantiated by.
2806             auto *classe = const_cast<ClassNode *>(static_cast<const ClassNode *>(aggregate));
2807             if (classe->qmlElement() != nullptr && classe->status() != Node::Internal) {
2808                 const Node *otherNode = nullptr;
2809                 Atom a = Atom(Atom::LinkNode, CodeMarker::stringForNode(classe->qmlElement()));
2810                 QString link = getAutoLink(&a, aggregate, &otherNode);
2811 
2812                 writer->writeStartElement(dbNamespace, "synopsisinfo");
2813                 writer->writeAttribute(dbNamespace, "role", "instantiatedBy");
2814                 generateSimpleLink(link, classe->qmlElement()->name());
2815                 writer->writeEndElement(); // synopsisinfo
2816                 newLine();
2817             }
2818 
2819             // Inherits.
2820             QVector<RelatedClass>::ConstIterator r;
2821             if (!classe->baseClasses().isEmpty()) {
2822                 writer->writeStartElement(dbNamespace, "synopsisinfo");
2823                 writer->writeAttribute(dbNamespace, "role", "inherits");
2824 
2825                 r = classe->baseClasses().constBegin();
2826                 int index = 0;
2827                 while (r != classe->baseClasses().constEnd()) {
2828                     if ((*r).node_) {
2829                         generateFullName((*r).node_, classe);
2830 
2831                         if ((*r).access_ == Node::Protected) {
2832                             writer->writeCharacters(" (protected)");
2833                         } else if ((*r).access_ == Node::Private) {
2834                             writer->writeCharacters(" (private)");
2835                         }
2836                         writer->writeCharacters(comma(index++, classe->baseClasses().count()));
2837                     }
2838                     ++r;
2839                 }
2840 
2841                 writer->writeEndElement(); // synopsisinfo
2842                 newLine();
2843             }
2844 
2845             // Inherited by.
2846             if (!classe->derivedClasses().isEmpty()) {
2847                 writer->writeStartElement(dbNamespace, "synopsisinfo");
2848                 writer->writeAttribute(dbNamespace, "role", "inheritedBy");
2849                 generateSortedNames(classe, classe->derivedClasses());
2850                 writer->writeEndElement(); // synopsisinfo
2851                 newLine();
2852             }
2853         }
2854     }
2855 
2856     // QML types.
2857     if (qcn) {
2858         // Module name and version (i.e. import).
2859         QString logicalModuleVersion;
2860         const CollectionNode *collection =
2861                 qdb_->getCollectionNode(qcn->logicalModuleName(), qcn->nodeType());
2862         if (collection)
2863             logicalModuleVersion = collection->logicalModuleVersion();
2864         else
2865             logicalModuleVersion = qcn->logicalModuleVersion();
2866 
2867         generateSynopsisInfo("import",
2868                              "import " + qcn->logicalModuleName() + QLatin1Char(' ')
2869                                      + logicalModuleVersion);
2870 
2871         // Since and project.
2872         if (!qcn->since().isEmpty())
2873             generateSynopsisInfo("since", formatSince(qcn));
2874 
2875         // Inherited by.
2876         NodeList subs;
2877         QmlTypeNode::subclasses(qcn, subs);
2878         if (!subs.isEmpty()) {
2879             writer->writeTextElement(dbNamespace, "synopsisinfo");
2880             writer->writeAttribute(dbNamespace, "role", "inheritedBy");
2881             generateSortedQmlNames(qcn, subs);
2882             writer->writeEndElement(); // synopsisinfo
2883             newLine();
2884         }
2885 
2886         // Inherits.
2887         QmlTypeNode *base = qcn->qmlBaseNode();
2888         while (base && base->isInternal())
2889             base = base->qmlBaseNode();
2890         if (base) {
2891             const Node *otherNode = nullptr;
2892             Atom a = Atom(Atom::LinkNode, CodeMarker::stringForNode(base));
2893             QString link = getAutoLink(&a, base, &otherNode);
2894 
2895             writer->writeTextElement(dbNamespace, "synopsisinfo");
2896             writer->writeAttribute(dbNamespace, "role", "inherits");
2897             generateSimpleLink(link, base->name());
2898             writer->writeEndElement(); // synopsisinfo
2899             newLine();
2900         }
2901 
2902         // Instantiates.
2903         ClassNode *cn = (const_cast<QmlTypeNode *>(qcn))->classNode();
2904         if (cn && (cn->status() != Node::Internal)) {
2905             const Node *otherNode = nullptr;
2906             Atom a = Atom(Atom::LinkNode, CodeMarker::stringForNode(qcn));
2907             QString link = getAutoLink(&a, cn, &otherNode);
2908 
2909             writer->writeTextElement(dbNamespace, "synopsisinfo");
2910             writer->writeAttribute(dbNamespace, "role", "instantiates");
2911             generateSimpleLink(link, cn->name());
2912             writer->writeEndElement(); // synopsisinfo
2913             newLine();
2914         }
2915     }
2916 
2917     // Thread safeness.
2918     switch (node->threadSafeness()) {
2919     case Node::UnspecifiedSafeness:
2920         generateSynopsisInfo("threadsafeness", "unspecified");
2921         break;
2922     case Node::NonReentrant:
2923         generateSynopsisInfo("threadsafeness", "non-reentrant");
2924         break;
2925     case Node::Reentrant:
2926         generateSynopsisInfo("threadsafeness", "reentrant");
2927         break;
2928     case Node::ThreadSafe:
2929         generateSynopsisInfo("threadsafeness", "thread safe");
2930         break;
2931     default:
2932         generateSynopsisInfo("threadsafeness", "unspecified");
2933         break;
2934     }
2935 
2936     // Module.
2937     if (!node->physicalModuleName().isEmpty())
2938         generateSynopsisInfo("module", node->physicalModuleName());
2939 
2940     // Group.
2941     if (classNode && !classNode->groupNames().isEmpty()) {
2942         generateSynopsisInfo("groups", classNode->groupNames().join(QLatin1Char(',')));
2943     } else if (qcn && !qcn->groupNames().isEmpty()) {
2944         generateSynopsisInfo("groups", qcn->groupNames().join(QLatin1Char(',')));
2945     }
2946 
2947     // Properties.
2948     if (propertyNode) {
2949         for (const Node *fnNode : propertyNode->getters()) {
2950             if (fnNode) {
2951                 const auto funcNode = static_cast<const FunctionNode *>(fnNode);
2952                 generateSynopsisInfo("getter", funcNode->name());
2953             }
2954         }
2955         for (const Node *fnNode : propertyNode->setters()) {
2956             if (fnNode) {
2957                 const auto funcNode = static_cast<const FunctionNode *>(fnNode);
2958                 generateSynopsisInfo("setter", funcNode->name());
2959             }
2960         }
2961         for (const Node *fnNode : propertyNode->resetters()) {
2962             if (fnNode) {
2963                 const auto funcNode = static_cast<const FunctionNode *>(fnNode);
2964                 generateSynopsisInfo("resetter", funcNode->name());
2965             }
2966         }
2967         for (const Node *fnNode : propertyNode->notifiers()) {
2968             if (fnNode) {
2969                 const auto funcNode = static_cast<const FunctionNode *>(fnNode);
2970                 generateSynopsisInfo("notifier", funcNode->name());
2971             }
2972         }
2973     }
2974 
2975     // Enums and typedefs.
2976     if (enumNode) {
2977         for (const EnumItem &item : enumNode->items()) {
2978             writer->writeStartElement(dbNamespace, "enumitem");
2979             writer->writeAttribute(dbNamespace, "enumidentifier", item.name());
2980             writer->writeAttribute(dbNamespace, "enumvalue", item.value());
2981             writer->writeEndElement(); // enumitem
2982             newLine();
2983         }
2984     }
2985 
2986     writer->writeEndElement(); // nodeToSynopsisTag (like classsynopsis)
2987     newLine();
2988 
2989     // The typedef associated to this enum.
2990     if (enumNode && enumNode->flagsType()) {
2991         writer->writeStartElement(dbNamespace, "typedefsynopsis");
2992         newLine();
2993 
2994         writer->writeTextElement(dbNamespace, "typedefname",
2995                                  enumNode->flagsType()->fullDocumentName());
2996 
2997         writer->writeEndElement(); // typedefsynopsis
2998         newLine();
2999     }
3000 }
3001 
taggedNode(const Node * node)3002 QString taggedNode(const Node *node)
3003 {
3004     // From CodeMarker::taggedNode, but without the tag part (i.e. only the QML specific case
3005     // remaining).
3006     // TODO: find a better name for this.
3007     if (node->nodeType() == Node::QmlType && node->name().startsWith(QLatin1String("QML:")))
3008         return node->name().mid(4);
3009     return node->name();
3010 }
3011 
3012 /*!
3013   Parses a string with method/variable name and (return) type
3014   to include type tags.
3015  */
typified(const QString & string,const Node * relative,bool trailingSpace,bool generateType)3016 void DocBookGenerator::typified(const QString &string, const Node *relative, bool trailingSpace,
3017                                 bool generateType)
3018 {
3019     // Adapted from CodeMarker::typified and HtmlGenerator::highlightedCode.
3020     // Note: CppCodeMarker::markedUpIncludes is not needed for DocBook, as this part is natively
3021     // generated as DocBook. Hence, there is no need to reimplement <@headerfile> from
3022     // HtmlGenerator::highlightedCode.
3023     QString result;
3024     QString pendingWord;
3025 
3026     for (int i = 0; i <= string.size(); ++i) {
3027         QChar ch;
3028         if (i != string.size())
3029             ch = string.at(i);
3030 
3031         QChar lower = ch.toLower();
3032         if ((lower >= QLatin1Char('a') && lower <= QLatin1Char('z')) || ch.digitValue() >= 0
3033             || ch == QLatin1Char('_') || ch == QLatin1Char(':')) {
3034             pendingWord += ch;
3035         } else {
3036             if (!pendingWord.isEmpty()) {
3037                 bool isProbablyType = (pendingWord != QLatin1String("const"));
3038                 if (generateType && isProbablyType) {
3039                     // Flush the current buffer.
3040                     writer->writeCharacters(result);
3041                     result.truncate(0);
3042 
3043                     // Add the link, logic from HtmlGenerator::highlightedCode.
3044                     const Node *n = qdb_->findTypeNode(pendingWord, relative, Node::DontCare);
3045                     QString href;
3046                     if (!(n && (n->isQmlBasicType() || n->isJsBasicType()))
3047                         || (relative
3048                             && (relative->genus() == n->genus() || Node::DontCare == n->genus()))) {
3049                         href = linkForNode(n, relative);
3050                     }
3051 
3052                     writer->writeStartElement(dbNamespace, "type");
3053                     if (href.isEmpty())
3054                         writer->writeCharacters(pendingWord);
3055                     else
3056                         generateSimpleLink(href, pendingWord);
3057                     writer->writeEndElement(); // type
3058                 } else {
3059                     result += pendingWord;
3060                 }
3061             }
3062             pendingWord.clear();
3063 
3064             if (ch.unicode() != '\0')
3065                 result += ch;
3066         }
3067     }
3068 
3069     if (trailingSpace && string.size()) {
3070         if (!string.endsWith(QLatin1Char('*')) && !string.endsWith(QLatin1Char('&')))
3071             result += QLatin1Char(' ');
3072     }
3073 
3074     writer->writeCharacters(result);
3075 }
3076 
generateSynopsisName(const Node * node,const Node * relative,bool generateNameLink)3077 void DocBookGenerator::generateSynopsisName(const Node *node, const Node *relative,
3078                                             bool generateNameLink)
3079 {
3080     // Implements the rewriting of <@link> from HtmlGenerator::highlightedCode, only due to calls to
3081     // CodeMarker::linkTag in CppCodeMarker::markedUpSynopsis.
3082     QString name = taggedNode(node);
3083 
3084     if (!generateNameLink) {
3085         writer->writeCharacters(name);
3086         return;
3087     }
3088 
3089     writer->writeStartElement(dbNamespace, "emphasis");
3090     writer->writeAttribute("role", "bold");
3091     generateSimpleLink(linkForNode(node, relative), name);
3092     writer->writeEndElement(); // emphasis
3093 }
3094 
generateParameter(const Parameter & parameter,const Node * relative,bool generateExtra,bool generateType)3095 void DocBookGenerator::generateParameter(const Parameter &parameter, const Node *relative,
3096                                          bool generateExtra, bool generateType)
3097 {
3098     const QString &pname = parameter.name();
3099     const QString &ptype = parameter.type();
3100     QString paramName;
3101     if (!pname.isEmpty()) {
3102         typified(ptype, relative, true, generateType);
3103         paramName = pname;
3104     } else {
3105         paramName = ptype;
3106     }
3107     if (generateExtra || pname.isEmpty()) {
3108         // Look for the _ character in the member name followed by a number (or n):
3109         // this is intended to be rendered as a subscript.
3110         QRegExp sub("([a-z]+)_([0-9]+|n)");
3111 
3112         writer->writeStartElement(dbNamespace, "emphasis");
3113         if (sub.indexIn(paramName) != -1) {
3114             writer->writeCharacters(sub.cap(0));
3115             writer->writeStartElement(dbNamespace, "sub");
3116             writer->writeCharacters(sub.cap(1));
3117             writer->writeEndElement(); // sub
3118         } else {
3119             writer->writeCharacters(paramName);
3120         }
3121         writer->writeEndElement(); // emphasis
3122     }
3123 
3124     const QString &pvalue = parameter.defaultValue();
3125     if (generateExtra && !pvalue.isEmpty())
3126         writer->writeCharacters(" = " + pvalue);
3127 }
3128 
generateSynopsis(const Node * node,const Node * relative,Section::Style style)3129 void DocBookGenerator::generateSynopsis(const Node *node, const Node *relative,
3130                                         Section::Style style)
3131 {
3132     // From HtmlGenerator::generateSynopsis (conditions written as booleans).
3133     const bool generateExtra = style != Section::AllMembers;
3134     const bool generateType = style != Section::Details;
3135     const bool generateNameLink = style != Section::Details;
3136 
3137     // From CppCodeMarker::markedUpSynopsis, reversed the generation of "extra" and "synopsis".
3138     const int MaxEnumValues = 6;
3139 
3140     // First generate the extra part if needed (condition from HtmlGenerator::generateSynopsis).
3141     if (generateExtra) {
3142         if (style != Section::Summary && style != Section::Accessors) {
3143             QStringList bracketed;
3144             if (node->isFunction()) {
3145                 const auto func = static_cast<const FunctionNode *>(node);
3146                 if (func->isStatic()) {
3147                     bracketed += "static";
3148                 } else if (!func->isNonvirtual()) {
3149                     if (func->isFinal())
3150                         bracketed += "final";
3151                     if (func->isOverride())
3152                         bracketed += "override";
3153                     if (func->isPureVirtual())
3154                         bracketed += "pure";
3155                     bracketed += "virtual";
3156                 }
3157 
3158                 if (func->access() == Node::Protected)
3159                     bracketed += "protected";
3160                 else if (func->access() == Node::Private)
3161                     bracketed += "private";
3162 
3163                 if (func->isSignal())
3164                     bracketed += "signal";
3165                 else if (func->isSlot())
3166                     bracketed += "slot";
3167             } else if (node->isTypeAlias()) {
3168                 bracketed += "alias";
3169             }
3170             if (!bracketed.isEmpty())
3171                 writer->writeCharacters(QLatin1Char('[') + bracketed.join(' ')
3172                                         + QStringLiteral("] "));
3173         }
3174 
3175         if (style == Section::Summary) {
3176             QString extra;
3177             if (node->isPreliminary())
3178                 extra = "(preliminary) ";
3179             else if (node->isDeprecated())
3180                 extra = "(deprecated) ";
3181             else if (node->isObsolete())
3182                 extra = "(obsolete) ";
3183             else if (node->isTypeAlias())
3184                 extra = "(alias) ";
3185 
3186             if (!extra.isEmpty())
3187                 writer->writeCharacters(extra);
3188         }
3189     }
3190 
3191     // Then generate the synopsis.
3192     if (style == Section::Details) {
3193         if (!node->isRelatedNonmember() && !node->isProxyNode() && !node->parent()->name().isEmpty()
3194             && !node->parent()->isHeader() && !node->isProperty() && !node->isQmlNode()
3195             && !node->isJsNode()) {
3196             writer->writeCharacters(taggedNode(node->parent()) + "::");
3197         }
3198     }
3199 
3200     switch (node->nodeType()) {
3201     case Node::Namespace:
3202         writer->writeCharacters("namespace ");
3203         generateSynopsisName(node, relative, generateNameLink);
3204         break;
3205     case Node::Class:
3206         writer->writeCharacters("class ");
3207         generateSynopsisName(node, relative, generateNameLink);
3208         break;
3209     case Node::Function: {
3210         const auto func = (const FunctionNode *)node;
3211 
3212         // First, the part coming before the name.
3213         if (style == Section::Summary || style == Section::Accessors) {
3214             if (!func->isNonvirtual())
3215                 writer->writeCharacters(QStringLiteral("virtual "));
3216         }
3217 
3218         // Name and parameters.
3219         if (style != Section::AllMembers && !func->returnType().isEmpty())
3220             typified(func->returnType(), relative, true, generateType);
3221         generateSynopsisName(node, relative, generateNameLink);
3222 
3223         if (!func->isMacroWithoutParams()) {
3224             writer->writeCharacters(QStringLiteral("("));
3225             if (!func->parameters().isEmpty()) {
3226                 const Parameters &parameters = func->parameters();
3227                 for (int i = 0; i < parameters.count(); i++) {
3228                     if (i > 0)
3229                         writer->writeCharacters(QStringLiteral(", "));
3230                     generateParameter(parameters.at(i), relative, generateExtra, generateType);
3231                 }
3232             }
3233             writer->writeCharacters(QStringLiteral(")"));
3234         }
3235         if (func->isConst())
3236             writer->writeCharacters(QStringLiteral(" const"));
3237 
3238         if (style == Section::Summary || style == Section::Accessors) {
3239             // virtual is prepended, if needed.
3240             QString synopsis;
3241             if (func->isFinal())
3242                 synopsis += QStringLiteral(" final");
3243             if (func->isOverride())
3244                 synopsis += QStringLiteral(" override");
3245             if (func->isPureVirtual())
3246                 synopsis += QStringLiteral(" = 0");
3247             if (func->isRef())
3248                 synopsis += QStringLiteral(" &");
3249             else if (func->isRefRef())
3250                 synopsis += QStringLiteral(" &&");
3251             writer->writeCharacters(synopsis);
3252         } else if (style == Section::AllMembers) {
3253             if (!func->returnType().isEmpty() && func->returnType() != "void") {
3254                 writer->writeCharacters(QStringLiteral(" : "));
3255                 typified(func->returnType(), relative, false, generateType);
3256             }
3257         } else {
3258             QString synopsis;
3259             if (func->isRef())
3260                 synopsis += QStringLiteral(" &");
3261             else if (func->isRefRef())
3262                 synopsis += QStringLiteral(" &&");
3263             writer->writeCharacters(synopsis);
3264         }
3265     } break;
3266     case Node::Enum: {
3267         const auto enume = static_cast<const EnumNode *>(node);
3268         writer->writeCharacters(QStringLiteral("enum "));
3269         generateSynopsisName(node, relative, generateNameLink);
3270 
3271         QString synopsis;
3272         if (style == Section::Summary) {
3273             synopsis += " { ";
3274 
3275             QStringList documentedItems = enume->doc().enumItemNames();
3276             if (documentedItems.isEmpty()) {
3277                 const auto &enumItems = enume->items();
3278                 for (const auto &item : enumItems)
3279                     documentedItems << item.name();
3280             }
3281             const QStringList omitItems = enume->doc().omitEnumItemNames();
3282             for (const auto &item : omitItems)
3283                 documentedItems.removeAll(item);
3284 
3285             if (documentedItems.size() > MaxEnumValues) {
3286                 // Take the last element and keep it safe, then elide the surplus.
3287                 const QString last = documentedItems.last();
3288                 documentedItems = documentedItems.mid(0, MaxEnumValues - 1);
3289                 documentedItems += "&#x2026;"; // Ellipsis: in HTML, &hellip;.
3290                 documentedItems += last;
3291             }
3292             synopsis += documentedItems.join(QLatin1String(", "));
3293 
3294             if (!documentedItems.isEmpty())
3295                 synopsis += QLatin1Char(' ');
3296             synopsis += QLatin1Char('}');
3297         }
3298         writer->writeCharacters(synopsis);
3299     } break;
3300     case Node::Typedef: {
3301         const auto typedeff = static_cast<const TypedefNode *>(node);
3302         if (typedeff->associatedEnum())
3303             writer->writeCharacters("flags ");
3304         else
3305             writer->writeCharacters("typedef ");
3306         generateSynopsisName(node, relative, generateNameLink);
3307     } break;
3308     case Node::Property: {
3309         const auto property = static_cast<const PropertyNode *>(node);
3310         generateSynopsisName(node, relative, generateNameLink);
3311         writer->writeCharacters(" : ");
3312         typified(property->qualifiedDataType(), relative, false, generateType);
3313     } break;
3314     case Node::Variable: {
3315         const auto variable = static_cast<const VariableNode *>(node);
3316         if (style == Section::AllMembers) {
3317             generateSynopsisName(node, relative, generateNameLink);
3318             writer->writeCharacters(" : ");
3319             typified(variable->dataType(), relative, false, generateType);
3320         } else {
3321             typified(variable->leftType(), relative, false, generateType);
3322             writer->writeCharacters(" ");
3323             generateSynopsisName(node, relative, generateNameLink);
3324             writer->writeCharacters(variable->rightType());
3325         }
3326     } break;
3327     default:
3328         generateSynopsisName(node, relative, generateNameLink);
3329     }
3330 }
3331 
generateEnumValue(const QString & enumValue,const Node * relative)3332 void DocBookGenerator::generateEnumValue(const QString &enumValue, const Node *relative)
3333 {
3334     // From CppCodeMarker::markedUpEnumValue, simplifications from Generator::plainCode (removing
3335     // <@op>). With respect to CppCodeMarker::markedUpEnumValue, the order of generation of parents
3336     // must be reversed so that they are processed in the order
3337     if (!relative->isEnumType()) {
3338         writer->writeCharacters(enumValue);
3339         return;
3340     }
3341 
3342     QVector<const Node *> parents;
3343     const Node *node = relative->parent();
3344     while (!node->isHeader() && node->parent()) {
3345         parents.prepend(node);
3346         if (node->parent() == relative || node->parent()->name().isEmpty())
3347             break;
3348         node = node->parent();
3349     }
3350     if (static_cast<const EnumNode *>(relative)->isScoped())
3351         parents << relative;
3352 
3353     writer->writeStartElement(dbNamespace, "code");
3354     for (auto parent : parents) {
3355         generateSynopsisName(parent, relative, true);
3356         writer->writeCharacters("::");
3357     }
3358 
3359     writer->writeCharacters(enumValue);
3360     writer->writeEndElement(); // code
3361 }
3362 
3363 /*!
3364   If the node is an overloaded signal, and a node with an
3365   example on how to connect to it
3366 
3367   Someone didn't finish writing this comment, and I don't know what this
3368   function is supposed to do, so I have not tried to complete the comment
3369   yet.
3370  */
generateOverloadedSignal(const Node * node)3371 void DocBookGenerator::generateOverloadedSignal(const Node *node)
3372 {
3373     // From Generator::generateOverloadedSignal.
3374     QString code = getOverloadedSignalCode(node);
3375     if (code.isEmpty())
3376         return;
3377 
3378     writer->writeStartElement(dbNamespace, "note");
3379     newLine();
3380     writer->writeStartElement(dbNamespace, "para");
3381     writer->writeCharacters("Signal ");
3382     writer->writeTextElement(dbNamespace, "emphasis", node->name());
3383     writer->writeCharacters(" is overloaded in this class. To connect to this "
3384                             "signal by using the function pointer syntax, Qt "
3385                             "provides a convenient helper for obtaining the "
3386                             "function pointer as shown in this example:");
3387     writer->writeTextElement(dbNamespace, "code", code);
3388     writer->writeEndElement(); // para
3389     newLine();
3390     writer->writeEndElement(); // note
3391     newLine();
3392 }
3393 
3394 /*!
3395   Generates an addendum note of type \a type for \a node. \a marker
3396   is unused in this generator.
3397 */
generateAddendum(const Node * node,Addendum type,CodeMarker * marker,bool generateNote)3398 void DocBookGenerator::generateAddendum(const Node *node, Addendum type, CodeMarker *marker,
3399                                         bool generateNote)
3400 {
3401     Q_UNUSED(marker);
3402     Q_ASSERT(node && !node->name().isEmpty());
3403     if (generateNote) {
3404         writer->writeStartElement(dbNamespace, "note");
3405         newLine();
3406     }
3407     switch (type) {
3408     case Invokable:
3409         writer->writeStartElement(dbNamespace, "para");
3410         writer->writeCharacters(
3411             "This function can be invoked via the meta-object system and from QML. See ");
3412         generateSimpleLink(node->url(), "Q_INVOKABLE");
3413         writer->writeCharacters(".");
3414         writer->writeEndElement(); // para
3415         newLine();
3416         break;
3417     case PrivateSignal:
3418         writer->writeTextElement(dbNamespace, "para",
3419             "This is a private signal. It can be used in signal connections but "
3420             "cannot be emitted by the user.");
3421         break;
3422     case QmlSignalHandler:
3423     {
3424         QString handler(node->name());
3425         handler[0] = handler[0].toTitleCase();
3426         handler.prepend(QLatin1String("on"));
3427         writer->writeStartElement(dbNamespace, "para");
3428         writer->writeCharacters("The corresponding handler is ");
3429         writer->writeTextElement(dbNamespace, "code", handler);
3430         writer->writeCharacters(".");
3431         writer->writeEndElement(); // para
3432         newLine();
3433         break;
3434     }
3435     case AssociatedProperties:
3436     {
3437         if (!node->isFunction())
3438             return;
3439         const FunctionNode *fn = static_cast<const FunctionNode *>(node);
3440         NodeList nodes = fn->associatedProperties();
3441         if (nodes.isEmpty())
3442             return;
3443         std::sort(nodes.begin(), nodes.end(), Node::nodeNameLessThan);
3444         for (const auto node : qAsConst(nodes)) {
3445             QString msg;
3446             const auto pn = static_cast<const PropertyNode *>(node);
3447             switch (pn->role(fn)) {
3448             case PropertyNode::Getter:
3449                 msg = QStringLiteral("Getter function");
3450                 break;
3451             case PropertyNode::Setter:
3452                 msg = QStringLiteral("Setter function");
3453                 break;
3454             case PropertyNode::Resetter:
3455                 msg = QStringLiteral("Resetter function");
3456                 break;
3457             case PropertyNode::Notifier:
3458                 msg = QStringLiteral("Notifier signal");
3459                 break;
3460             default:
3461                 continue;
3462             }
3463             writer->writeCharacters(msg + " for property ");
3464             generateSimpleLink(linkForNode(pn, nullptr), pn->name());
3465             writer->writeCharacters(". ");
3466         }
3467         break;
3468     }
3469     case TypeAlias:
3470     {
3471         if (!node->isTypeAlias())
3472             return;
3473         writer->writeStartElement(dbNamespace, "para");
3474         const auto *ta = static_cast<const TypeAliasNode *>(node);
3475         writer->writeCharacters("This is a type alias for ");
3476         if (ta->aliasedNode() && ta->aliasedNode()->isInAPI())
3477             generateSimpleLink(linkForNode(ta->aliasedNode(), nullptr),
3478                     ta->aliasedNode()->plainFullName(ta->parent()));
3479         else
3480             writer->writeTextElement(dbNamespace, "code", ta->aliasedType());
3481 
3482         writer->writeCharacters(".");
3483         writer->writeEndElement(); // para
3484         newLine();
3485         break;
3486     }
3487 
3488     default:
3489         break;
3490     }
3491 
3492     if (generateNote) {
3493         writer->writeEndElement(); // note
3494         newLine();
3495     }
3496 }
3497 
generateDetailedMember(const Node * node,const PageNode * relative)3498 void DocBookGenerator::generateDetailedMember(const Node *node, const PageNode *relative)
3499 {
3500     // From HtmlGenerator::generateDetailedMember.
3501     writer->writeStartElement(dbNamespace, "section");
3502     if (node->isSharedCommentNode()) {
3503         const auto scn = reinterpret_cast<const SharedCommentNode *>(node);
3504         const QVector<Node *> &collective = scn->collective();
3505 
3506         bool firstFunction = true;
3507         for (const Node *n : collective) {
3508             if (n->isFunction()) {
3509                 QString nodeRef = refForNode(n);
3510 
3511                 if (firstFunction) {
3512                     writer->writeAttribute("xml:id", refForNode(collective.at(0)));
3513                     newLine();
3514                     writer->writeStartElement(dbNamespace, "title");
3515                     generateSynopsis(n, relative, Section::Details);
3516                     writer->writeEndElement(); // title
3517                     newLine();
3518 
3519                     firstFunction = false;
3520                 } else {
3521                     writer->writeStartElement(dbNamespace, "bridgehead");
3522                     writer->writeAttribute("renderas", "sect2");
3523                     writer->writeAttribute("xml:id", nodeRef);
3524                     generateSynopsis(n, relative, Section::Details);
3525                     writer->writeEndElement(); // bridgehead
3526                     newLine();
3527                 }
3528             }
3529         }
3530     } else {
3531         const EnumNode *etn;
3532         QString nodeRef = refForNode(node);
3533         if (node->isEnumType() && (etn = static_cast<const EnumNode *>(node))->flagsType()) {
3534             writer->writeAttribute("xml:id", nodeRef);
3535             newLine();
3536             writer->writeStartElement(dbNamespace, "title");
3537             generateSynopsis(etn, relative, Section::Details);
3538             writer->writeEndElement(); // title
3539             newLine();
3540             writer->writeStartElement(dbNamespace, "bridgehead");
3541             generateSynopsis(etn->flagsType(), relative, Section::Details);
3542             writer->writeEndElement(); // bridgehead
3543             newLine();
3544         } else {
3545             writer->writeAttribute("xml:id", nodeRef);
3546             newLine();
3547             writer->writeStartElement(dbNamespace, "title");
3548             generateSynopsis(node, relative, Section::Details);
3549             writer->writeEndElement(); // title
3550             newLine();
3551         }
3552     }
3553 
3554     generateDocBookSynopsis(node);
3555 
3556     generateStatus(node);
3557     generateBody(node);
3558     generateOverloadedSignal(node);
3559     generateThreadSafeness(node);
3560     generateSince(node);
3561 
3562     if (node->isProperty()) {
3563         const auto property = static_cast<const PropertyNode *>(node);
3564         Section section(Section::Accessors, Section::Active);
3565 
3566         section.appendMembers(property->getters().toVector());
3567         section.appendMembers(property->setters().toVector());
3568         section.appendMembers(property->resetters().toVector());
3569 
3570         if (!section.members().isEmpty()) {
3571             writer->writeStartElement(dbNamespace, "para");
3572             newLine();
3573             writer->writeStartElement(dbNamespace, "emphasis");
3574             writer->writeAttribute("role", "bold");
3575             writer->writeCharacters("Access functions:");
3576             newLine();
3577             writer->writeEndElement(); // emphasis
3578             newLine();
3579             writer->writeEndElement(); // para
3580             newLine();
3581             generateSectionList(section, node);
3582         }
3583 
3584         Section notifiers(Section::Accessors, Section::Active);
3585         notifiers.appendMembers(property->notifiers().toVector());
3586 
3587         if (!notifiers.members().isEmpty()) {
3588             writer->writeStartElement(dbNamespace, "para");
3589             newLine();
3590             writer->writeStartElement(dbNamespace, "emphasis");
3591             writer->writeAttribute("role", "bold");
3592             writer->writeCharacters("Notifier signal:");
3593             newLine();
3594             writer->writeEndElement(); // emphasis
3595             newLine();
3596             writer->writeEndElement(); // para
3597             newLine();
3598             generateSectionList(notifiers, node);
3599         }
3600     } else if (node->isEnumType()) {
3601         const auto en = static_cast<const EnumNode *>(node);
3602 
3603         if (qflagsHref_.isEmpty()) {
3604             Node *qflags = qdb_->findClassNode(QStringList("QFlags"));
3605             if (qflags)
3606                 qflagsHref_ = linkForNode(qflags, nullptr);
3607         }
3608 
3609         if (en->flagsType()) {
3610             writer->writeStartElement(dbNamespace, "para");
3611             writer->writeCharacters("The " + en->flagsType()->name() + " type is a typedef for ");
3612             generateSimpleLink(qflagsHref_, "QFlags");
3613             writer->writeCharacters("&lt;" + en->name() + "&gt;. ");
3614             writer->writeCharacters("It stores an OR combination of " + en->name() + "values.");
3615             writer->writeEndElement(); // para
3616             newLine();
3617         }
3618     }
3619     generateAlsoList(node);
3620     endSection(); // section
3621 }
3622 
generateSectionList(const Section & section,const Node * relative,Section::Status status)3623 void DocBookGenerator::generateSectionList(const Section &section, const Node *relative,
3624                                            Section::Status status)
3625 {
3626     // From HtmlGenerator::generateSectionList, just generating a list (not tables).
3627     const NodeVector &members =
3628             (status == Section::Obsolete ? section.obsoleteMembers() : section.members());
3629     if (!members.isEmpty()) {
3630         bool hasPrivateSignals = false;
3631         bool isInvokable = false;
3632 
3633         writer->writeStartElement(dbNamespace, "itemizedlist");
3634         newLine();
3635 
3636         int i = 0;
3637         NodeVector::ConstIterator m = members.constBegin();
3638         while (m != members.constEnd()) {
3639             if ((*m)->access() == Node::Private) {
3640                 ++m;
3641                 continue;
3642             }
3643 
3644             writer->writeStartElement(dbNamespace, "listitem");
3645             newLine();
3646             writer->writeStartElement(dbNamespace, "para");
3647 
3648             // prefix no more needed.
3649             generateSynopsis(*m, relative, section.style());
3650             if ((*m)->isFunction()) {
3651                 const auto fn = static_cast<const FunctionNode *>(*m);
3652                 if (fn->isPrivateSignal())
3653                     hasPrivateSignals = true;
3654                 else if (fn->isInvokable())
3655                     isInvokable = true;
3656             }
3657 
3658             writer->writeEndElement(); // para
3659             newLine();
3660             writer->writeEndElement(); // listitem
3661             newLine();
3662 
3663             i++;
3664             ++m;
3665         }
3666 
3667         writer->writeEndElement(); // itemizedlist
3668         newLine();
3669 
3670         if (hasPrivateSignals)
3671             generateAddendum(relative, Generator::PrivateSignal);
3672         if (isInvokable)
3673             generateAddendum(relative, Generator::Invokable);
3674     }
3675 
3676     if (status != Section::Obsolete && section.style() == Section::Summary
3677         && !section.inheritedMembers().isEmpty()) {
3678         writer->writeStartElement(dbNamespace, "itemizedlist");
3679         newLine();
3680 
3681         generateSectionInheritedList(section, relative);
3682 
3683         writer->writeEndElement(); // itemizedlist
3684         newLine();
3685     }
3686 }
3687 
generateSectionInheritedList(const Section & section,const Node * relative)3688 void DocBookGenerator::generateSectionInheritedList(const Section &section, const Node *relative)
3689 {
3690     // From HtmlGenerator::generateSectionInheritedList.
3691     QVector<QPair<Aggregate *, int>>::ConstIterator p = section.inheritedMembers().constBegin();
3692     while (p != section.inheritedMembers().constEnd()) {
3693         writer->writeStartElement(dbNamespace, "listitem");
3694         writer->writeCharacters(QString((*p).second) + " ");
3695         if ((*p).second == 1)
3696             writer->writeCharacters(section.singular());
3697         else
3698             writer->writeCharacters(section.plural());
3699         writer->writeCharacters(" inherited from ");
3700         generateSimpleLink(fileName((*p).first) + '#'
3701                                    + Generator::cleanRef(section.title().toLower()),
3702                            (*p).first->plainFullName(relative));
3703         ++p;
3704     }
3705 }
3706 
3707 /*!
3708   Generate the DocBook page for an entity that doesn't map
3709   to any underlying parsable C++, QML, or Javascript element.
3710  */
generatePageNode(PageNode * pn)3711 void DocBookGenerator::generatePageNode(PageNode *pn)
3712 {
3713     Q_ASSERT(writer == nullptr);
3714     // From HtmlGenerator::generatePageNode, remove anything related to TOCs.
3715     writer = startDocument(pn);
3716 
3717     generateHeader(pn->fullTitle(), pn->subtitle(), pn);
3718     generateBody(pn);
3719     generateAlsoList(pn);
3720     generateFooter();
3721 
3722     endDocument();
3723 }
3724 
3725 /*!
3726   Extract sections of markup text and output them.
3727  */
generateQmlText(const Text & text,const Node * relative,CodeMarker * marker,const QString & qmlName)3728 bool DocBookGenerator::generateQmlText(const Text &text, const Node *relative, CodeMarker *marker,
3729                                        const QString &qmlName)
3730 {
3731     Q_UNUSED(marker);
3732     Q_UNUSED(qmlName);
3733     // From Generator::generateQmlText.
3734     const Atom *atom = text.firstAtom();
3735     bool result = false;
3736 
3737     if (atom != nullptr) {
3738         initializeTextOutput();
3739         while (atom) {
3740             if (atom->type() != Atom::QmlText)
3741                 atom = atom->next();
3742             else {
3743                 atom = atom->next();
3744                 while (atom && (atom->type() != Atom::EndQmlText)) {
3745                     int n = 1 + generateAtom(atom, relative);
3746                     while (n-- > 0)
3747                         atom = atom->next();
3748                 }
3749             }
3750         }
3751         result = true;
3752     }
3753     return result;
3754 }
3755 
3756 /*!
3757   Generate the DocBook page for a QML type. \qcn is the QML type.
3758  */
generateQmlTypePage(QmlTypeNode * qcn)3759 void DocBookGenerator::generateQmlTypePage(QmlTypeNode *qcn)
3760 {
3761     // From HtmlGenerator::generateQmlTypePage.
3762     // Start producing the DocBook file.
3763     Q_ASSERT(writer == nullptr);
3764     writer = startDocument(qcn);
3765 
3766     Generator::setQmlTypeContext(qcn);
3767     QString title = qcn->fullTitle();
3768     if (qcn->isJsType())
3769         title += " JavaScript Type";
3770     else
3771         title += " QML Type";
3772 
3773     generateHeader(title, qcn->subtitle(), qcn);
3774     generateQmlRequisites(qcn);
3775 
3776     startSection(registerRef("details"), "Detailed Description");
3777     generateBody(qcn);
3778 
3779     ClassNode *cn = qcn->classNode();
3780     if (cn)
3781         generateQmlText(cn->doc().body(), cn);
3782     generateAlsoList(qcn);
3783 
3784     endSection();
3785 
3786     Sections sections(qcn);
3787     for (const auto &section : sections.stdQmlTypeDetailsSections()) {
3788         if (!section.isEmpty()) {
3789             startSection(registerRef(section.title().toLower()), section.title());
3790 
3791             for (const auto &member : section.members())
3792                 generateDetailedQmlMember(member, qcn);
3793 
3794             endSection();
3795         }
3796     }
3797 
3798     generateObsoleteQmlMembers(sections);
3799 
3800     generateFooter();
3801     Generator::setQmlTypeContext(nullptr);
3802 
3803     endDocument();
3804 }
3805 
3806 /*!
3807   Generate the DocBook page for the QML basic type represented
3808   by the QML basic type node \a qbtn.
3809  */
generateQmlBasicTypePage(QmlBasicTypeNode * qbtn)3810 void DocBookGenerator::generateQmlBasicTypePage(QmlBasicTypeNode *qbtn)
3811 {
3812     // From HtmlGenerator::generateQmlBasicTypePage.
3813     // Start producing the DocBook file.
3814     Q_ASSERT(writer == nullptr);
3815     writer = startDocument(qbtn);
3816 
3817     QString htmlTitle = qbtn->fullTitle();
3818     if (qbtn->isJsType())
3819         htmlTitle += " JavaScript Basic Type";
3820     else
3821         htmlTitle += " QML Basic Type";
3822 
3823     Sections sections(qbtn);
3824     generateHeader(htmlTitle, qbtn->subtitle(), qbtn);
3825 
3826     startSection(registerRef("details"), "Detailed Description");
3827 
3828     generateBody(qbtn);
3829     generateAlsoList(qbtn);
3830 
3831     endSection();
3832 
3833     SectionVector::ConstIterator s = sections.stdQmlTypeDetailsSections().constBegin();
3834     while (s != sections.stdQmlTypeDetailsSections().constEnd()) {
3835         if (!s->isEmpty()) {
3836             startSection(registerRef(s->title().toLower()), s->title());
3837 
3838             NodeVector::ConstIterator m = s->members().constBegin();
3839             while (m != s->members().constEnd()) {
3840                 generateDetailedQmlMember(*m, qbtn);
3841                 ++m;
3842             }
3843 
3844             endSection();
3845         }
3846         ++s;
3847     }
3848     generateFooter();
3849 
3850     endDocument();
3851 }
3852 
3853 /*!
3854   Outputs the DocBook detailed documentation for a section
3855   on a QML element reference page.
3856  */
generateDetailedQmlMember(Node * node,const Aggregate * relative)3857 void DocBookGenerator::generateDetailedQmlMember(Node *node, const Aggregate *relative)
3858 {
3859     // From HtmlGenerator::generateDetailedQmlMember, with elements from
3860     // CppCodeMarker::markedUpQmlItem and HtmlGenerator::generateQmlItem.
3861     std::function<QString(QmlPropertyNode *)> getQmlPropertyTitle = [&](QmlPropertyNode *n) {
3862         if (!n->isReadOnlySet() && n->declarativeCppNode())
3863             n->markReadOnly(!n->isWritable());
3864 
3865         QString title;
3866         if (!n->isWritable())
3867             title += "[read-only] ";
3868         if (n->isDefault())
3869             title += "[default] ";
3870 
3871         // Finalise generation of name, as per CppCodeMarker::markedUpQmlItem.
3872         if (n->isAttached())
3873             title += n->element() + QLatin1Char('.');
3874         title += n->name() + " : " + n->dataType();
3875 
3876         return title;
3877     };
3878 
3879     std::function<void(Node *)> generateQmlMethodTitle = [&](Node *node) {
3880         generateSynopsis(node, relative, Section::Details);
3881     };
3882 
3883     bool generateEndSection = true;
3884 
3885     if (node->isPropertyGroup()) {
3886         const auto scn = static_cast<const SharedCommentNode *>(node);
3887 
3888         QString heading;
3889         if (!scn->name().isEmpty())
3890             heading = scn->name() + " group";
3891         else
3892             heading = node->name();
3893         startSection(refForNode(scn), heading);
3894         // This last call creates a title for this section. In other words,
3895         // titles are forbidden for the rest of the section.
3896 
3897         const QVector<Node *> sharedNodes = scn->collective();
3898         for (const auto &node : sharedNodes) {
3899             if (node->isQmlProperty() || node->isJsProperty()) {
3900                 auto *qpn = static_cast<QmlPropertyNode *>(node);
3901 
3902                 writer->writeStartElement(dbNamespace, "bridgehead");
3903                 writer->writeAttribute("renderas", "sect2");
3904                 writer->writeAttribute("xml:id", refForNode(qpn));
3905                 writer->writeCharacters(getQmlPropertyTitle(qpn));
3906                 writer->writeEndElement(); // bridgehead
3907                 newLine();
3908 
3909                 generateDocBookSynopsis(qpn);
3910             }
3911         }
3912     } else if (node->isQmlProperty() || node->isJsProperty()) {
3913         auto qpn = static_cast<QmlPropertyNode *>(node);
3914         startSection(refForNode(qpn), getQmlPropertyTitle(qpn));
3915         generateDocBookSynopsis(qpn);
3916     } else if (node->isSharedCommentNode()) {
3917         const auto scn = reinterpret_cast<const SharedCommentNode *>(node);
3918         const QVector<Node *> &sharedNodes = scn->collective();
3919 
3920         // In the section, generate a title for the first node, then bridgeheads for
3921         // the next ones.
3922         int i = 0;
3923         for (const auto m : sharedNodes) {
3924             // Ignore this element if there is nothing to generate.
3925             if (!node->isFunction(Node::QML) && !node->isFunction(Node::JS)
3926                 && !node->isQmlProperty() && !node->isJsProperty()) {
3927                 continue;
3928             }
3929 
3930             // Complete the section tag.
3931             if (i == 0) {
3932                 writer->writeStartElement(dbNamespace, "section");
3933                 writer->writeAttribute("xml:id", refForNode(m));
3934                 newLine();
3935             }
3936 
3937             // Write the tag containing the title.
3938             writer->writeStartElement(dbNamespace, (i == 0) ? "title" : "bridgehead");
3939             if (i > 0)
3940                 writer->writeAttribute("renderas", "sect2");
3941 
3942             // Write the title.
3943             QString title;
3944             if (node->isFunction(Node::QML) || node->isFunction(Node::JS))
3945                 generateQmlMethodTitle(node);
3946             else if (node->isQmlProperty() || node->isJsProperty())
3947                 writer->writeCharacters(getQmlPropertyTitle(static_cast<QmlPropertyNode *>(node)));
3948 
3949             // Complete the title and the synopsis.
3950             generateDocBookSynopsis(m);
3951             ++i;
3952         }
3953 
3954         if (i == 0)
3955             generateEndSection = false;
3956     } else { // assume the node is a method/signal handler
3957         startSectionBegin(refForNode(node));
3958         generateQmlMethodTitle(node);
3959         startSectionEnd();
3960     }
3961 
3962     generateStatus(node);
3963     generateBody(node);
3964     generateThreadSafeness(node);
3965     generateSince(node);
3966     generateAlsoList(node);
3967 
3968     if (generateEndSection)
3969         endSection();
3970 }
3971 
3972 /*!
3973   Recursive writing of DocBook files from the root \a node.
3974  */
generateDocumentation(Node * node)3975 void DocBookGenerator::generateDocumentation(Node *node)
3976 {
3977     // Mainly from Generator::generateDocumentation, with parts from
3978     // Generator::generateDocumentation and WebXMLGenerator::generateDocumentation.
3979     // Don't generate nodes that are already processed, or if they're not
3980     // supposed to generate output, ie. external, index or images nodes.
3981     if (!node->url().isNull())
3982         return;
3983     if (node->isIndexNode())
3984         return;
3985     if (node->isInternal() && !showInternal_)
3986         return;
3987     if (node->isExternalPage())
3988         return;
3989 
3990     if (node->parent()) {
3991         if (node->isCollectionNode()) {
3992             /*
3993               A collection node collects: groups, C++ modules,
3994               QML modules or JavaScript modules. Testing for a
3995               CollectionNode must be done before testing for a
3996               TextPageNode because a CollectionNode is a PageNode
3997               at this point.
3998 
3999               Don't output an HTML page for the collection
4000               node unless the \group, \module, \qmlmodule or
4001               \jsmodule command was actually seen by qdoc in
4002               the qdoc comment for the node.
4003 
4004               A key prerequisite in this case is the call to
4005               mergeCollections(cn). We must determine whether
4006               this group, module, QML module, or JavaScript
4007               module has members in other modules. We know at
4008               this point that cn's members list contains only
4009               members in the current module. Therefore, before
4010               outputting the page for cn, we must search for
4011               members of cn in the other modules and add them
4012               to the members list.
4013             */
4014             auto cn = static_cast<CollectionNode *>(node);
4015             if (cn->wasSeen()) {
4016                 qdb_->mergeCollections(cn);
4017                 generateCollectionNode(cn);
4018             } else if (cn->isGenericCollection()) {
4019                 // Currently used only for the module's related orphans page
4020                 // but can be generalized for other kinds of collections if
4021                 // other use cases pop up.
4022                 generateGenericCollectionPage(cn);
4023             }
4024         } else if (node->isTextPageNode()) { // Pages.
4025             generatePageNode(static_cast<PageNode *>(node));
4026         } else if (node->isAggregate()) { // Aggregates.
4027             if ((node->isClassNode() || node->isHeader() || node->isNamespace())
4028                 && node->docMustBeGenerated()) {
4029                 generateCppReferencePage(static_cast<Aggregate *>(node));
4030             } else if (node->isQmlType() || node->isJsType()) {
4031                 generateQmlTypePage(static_cast<QmlTypeNode *>(node));
4032             } else if (node->isQmlBasicType() || node->isJsBasicType()) {
4033                 generateQmlBasicTypePage(static_cast<QmlBasicTypeNode *>(node));
4034             } else if (node->isProxyNode()) {
4035                 generateProxyPage(static_cast<Aggregate *>(node));
4036             }
4037         }
4038     }
4039 
4040     if (node->isAggregate()) {
4041         auto *aggregate = static_cast<Aggregate *>(node);
4042         for (auto c : aggregate->childNodes()) {
4043             if (node->isPageNode() && !node->isPrivate())
4044                 generateDocumentation(c);
4045         }
4046     }
4047 }
4048 
generateProxyPage(Aggregate * aggregate)4049 void DocBookGenerator::generateProxyPage(Aggregate *aggregate)
4050 {
4051     // Adapted from HtmlGenerator::generateProxyPage.
4052     Q_ASSERT(aggregate->isProxyNode());
4053 
4054     // Start producing the DocBook file.
4055     Q_ASSERT(writer == nullptr);
4056     writer = startDocument(aggregate);
4057 
4058     // Info container.
4059     generateHeader(aggregate->plainFullName(), "", aggregate);
4060 
4061     // No element synopsis.
4062 
4063     // Actual content.
4064     if (!aggregate->doc().isEmpty()) {
4065         startSection(registerRef("details"), "Detailed Description");
4066 
4067         generateBody(aggregate);
4068         generateAlsoList(aggregate);
4069         generateMaintainerList(aggregate);
4070 
4071         endSection();
4072     }
4073 
4074     Sections sections(aggregate);
4075     SectionVector *detailsSections = &sections.stdDetailsSections();
4076 
4077     for (const auto &section : qAsConst(*detailsSections)) {
4078         if (section.isEmpty())
4079             continue;
4080 
4081         startSection(section.title().toLower(), section.title());
4082 
4083         const QVector<Node *> &members = section.members();
4084         for (const auto &member : members) {
4085             if (!member->isPrivate()) { // ### check necessary?
4086                 if (!member->isClassNode()) {
4087                     generateDetailedMember(member, aggregate);
4088                 } else {
4089                     startSectionBegin();
4090                     generateFullName(member, aggregate);
4091                     startSectionEnd();
4092                     generateBrief(member);
4093                     endSection();
4094                 }
4095             }
4096         }
4097 
4098         endSection();
4099     }
4100 
4101     generateFooter();
4102 
4103     endDocument();
4104 }
4105 
4106 /*!
4107   Generate the HTML page for a group, module, or QML module.
4108  */
generateCollectionNode(CollectionNode * cn)4109 void DocBookGenerator::generateCollectionNode(CollectionNode *cn)
4110 {
4111     // Adapted from HtmlGenerator::generateCollectionNode.
4112     // Start producing the DocBook file.
4113     Q_ASSERT(writer == nullptr);
4114     writer = startDocument(cn);
4115 
4116     // Info container.
4117     generateHeader(cn->fullTitle(), cn->subtitle(), cn);
4118 
4119     // Element synopsis.
4120     generateDocBookSynopsis(cn);
4121 
4122     // Generate brief for C++ modules, status for all modules.
4123     if (cn->genus() != Node::DOC && cn->genus() != Node::DontCare) {
4124         if (cn->isModule())
4125             generateBrief(cn);
4126         generateStatus(cn);
4127         generateSince(cn);
4128     }
4129 
4130     // Actual content.
4131     if (cn->isModule()) {
4132         if (!cn->noAutoList()) {
4133             NodeMultiMap nmm;
4134             cn->getMemberNamespaces(nmm);
4135             if (!nmm.isEmpty()) {
4136                 startSection(registerRef("namespaces"), "Namespaces");
4137                 generateAnnotatedList(cn, nmm, "namespaces");
4138                 endSection();
4139             }
4140             nmm.clear();
4141             cn->getMemberClasses(nmm);
4142             if (!nmm.isEmpty()) {
4143                 startSection(registerRef("classes"), "Classes");
4144                 generateAnnotatedList(cn, nmm, "classes");
4145                 endSection();
4146             }
4147         }
4148     }
4149 
4150     bool generatedTitle = false;
4151     if (cn->isModule() && !cn->doc().briefText().isEmpty()) {
4152         startSection(registerRef("details"), "Detailed Description");
4153         generatedTitle = true;
4154     } else {
4155         writeAnchor(registerRef("details"));
4156     }
4157 
4158     generateBody(cn);
4159     generateAlsoList(cn);
4160 
4161     if (!cn->noAutoList() && (cn->isGroup() || cn->isQmlModule() || cn->isJsModule()))
4162         generateAnnotatedList(cn, cn->members(), "members");
4163 
4164     if (generatedTitle)
4165         endSection();
4166 
4167     generateFooter();
4168 
4169     endDocument();
4170 }
4171 
4172 /*!
4173   Generate the HTML page for a generic collection. This is usually
4174   a collection of C++ elements that are related to an element in
4175   a different module.
4176  */
generateGenericCollectionPage(CollectionNode * cn)4177 void DocBookGenerator::generateGenericCollectionPage(CollectionNode *cn)
4178 {
4179     // Adapted from HtmlGenerator::generateGenericCollectionPage.
4180     // TODO: factor out this code to generate a file name.
4181     QString name = cn->name().toLower();
4182     name.replace(QChar(' '), QString("-"));
4183     QString filename = cn->tree()->physicalModuleName() + "-" + name + "." + fileExtension();
4184 
4185     // Start producing the DocBook file.
4186     Q_ASSERT(writer == nullptr);
4187     writer = startGenericDocument(cn, filename);
4188 
4189     // Info container.
4190     generateHeader(cn->fullTitle(), cn->subtitle(), cn);
4191 
4192     // Element synopsis.
4193     generateDocBookSynopsis(cn);
4194 
4195     // Actual content.
4196     writer->writeStartElement(dbNamespace, "para");
4197     writer->writeCharacters("Each function or type documented here is related to a class or "
4198                             "namespace that is documented in a different module. The reference "
4199                             "page for that class or namespace will link to the function or type "
4200                             "on this page.");
4201     writer->writeEndElement(); // para
4202 
4203     const CollectionNode *cnc = cn;
4204     const QList<Node *> members = cn->members();
4205     for (const auto &member : members)
4206         generateDetailedMember(member, cnc);
4207 
4208     generateFooter();
4209 
4210     endDocument();
4211 }
4212 
generateFullName(const Node * node,const Node * relative)4213 void DocBookGenerator::generateFullName(const Node *node, const Node *relative)
4214 {
4215     // From Generator::appendFullName.
4216     writer->writeStartElement(dbNamespace, "link");
4217     writer->writeAttribute(xlinkNamespace, "href", fullDocumentLocation(node));
4218     writer->writeAttribute(xlinkNamespace, "role", targetType(node));
4219     writer->writeCharacters(node->fullName(relative));
4220     writer->writeEndElement(); // link
4221 }
4222 
generateFullName(const Node * apparentNode,const QString & fullName,const Node * actualNode)4223 void DocBookGenerator::generateFullName(const Node *apparentNode, const QString &fullName,
4224                                         const Node *actualNode)
4225 {
4226     // From Generator::appendFullName.
4227     if (actualNode == nullptr)
4228         actualNode = apparentNode;
4229     writer->writeStartElement(dbNamespace, "link");
4230     writer->writeAttribute(xlinkNamespace, "href", fullDocumentLocation(actualNode));
4231     writer->writeAttribute("type", targetType(actualNode));
4232     writer->writeCharacters(fullName);
4233     writer->writeEndElement(); // link
4234 }
4235 
4236 QT_END_NAMESPACE
4237