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("<Missing DocBook>");
844 writer->writeEndElement(); // emphasis
845 break;
846 case Atom::UnknownCommand:
847 writer->writeStartElement(dbNamespace, "emphasis");
848 writer->writeAttribute("role", "bold");
849 writer->writeCharacters("<Unknown command>");
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 §ions)
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 §ions)
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 §ions.stdDetailsSections() :
2485 §ions.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 ¶meter = 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 ¶meter, 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 ¶meters = 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 += "…"; // Ellipsis: in HTML, ….
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("<" + en->name() + ">. ");
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 §ion, 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 §ion, 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 §ion : 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 = §ions.stdDetailsSections();
4076
4077 for (const auto §ion : 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