1 | // Copyright (C) 2019 Thibaut Cuvelier |
2 | // Copyright (C) 2021 The Qt Company Ltd. |
3 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 |
4 | |
5 | #include "docbookgenerator.h" |
6 | |
7 | #include "access.h" |
8 | #include "aggregate.h" |
9 | #include "classnode.h" |
10 | #include "codemarker.h" |
11 | #include "collectionnode.h" |
12 | #include "config.h" |
13 | #include "enumnode.h" |
14 | #include "examplenode.h" |
15 | #include "functionnode.h" |
16 | #include "generator.h" |
17 | #include "node.h" |
18 | #include "propertynode.h" |
19 | #include "quoter.h" |
20 | #include "qdocdatabase.h" |
21 | #include "qmlpropertynode.h" |
22 | #include "sharedcommentnode.h" |
23 | #include "typedefnode.h" |
24 | #include "variablenode.h" |
25 | |
26 | #include <QtCore/qlist.h> |
27 | #include <QtCore/qmap.h> |
28 | #include <QtCore/quuid.h> |
29 | #include <QtCore/qurl.h> |
30 | #include <QtCore/qregularexpression.h> |
31 | #include <QtCore/qversionnumber.h> |
32 | |
33 | #include <cctype> |
34 | |
35 | QT_BEGIN_NAMESPACE |
36 | |
37 | using namespace Qt::StringLiterals; |
38 | |
39 | static const char dbNamespace[] = "http://docbook.org/ns/docbook" ; |
40 | static const char xlinkNamespace[] = "http://www.w3.org/1999/xlink" ; |
41 | static const char itsNamespace[] = "http://www.w3.org/2005/11/its" ; |
42 | |
43 | DocBookGenerator::DocBookGenerator(FileResolver& file_resolver) : XmlGenerator(file_resolver) {} |
44 | |
45 | inline void DocBookGenerator::newLine() |
46 | { |
47 | m_writer->writeCharacters(text: "\n" ); |
48 | } |
49 | |
50 | inline void DocBookGenerator::writeRawHtml(const QString &rawCode) |
51 | { |
52 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "programlisting" ); |
53 | m_writer->writeAttribute(qualifiedName: "role" , value: "raw-html" ); |
54 | m_writer->writeCDATA(text: rawCode); |
55 | m_writer->writeEndElement(); // programlisting |
56 | newLine(); |
57 | } |
58 | |
59 | void DocBookGenerator::writeXmlId(const QString &id) |
60 | { |
61 | if (id.isEmpty()) |
62 | return; |
63 | |
64 | m_writer->writeAttribute(qualifiedName: "xml:id" , value: registerRef(ref: id, xmlCompliant: true)); |
65 | } |
66 | |
67 | void DocBookGenerator::writeXmlId(const Node *node) |
68 | { |
69 | if (!node) |
70 | return; |
71 | |
72 | // Specifically for nodes, do not use the same code path, as refForNode |
73 | // calls registerRef in all cases. Calling registerRef a second time adds |
74 | // a character to "disambiguate" the two IDs (the one returned by |
75 | // refForNode, then the one that is written as xml:id). |
76 | m_writer->writeAttribute(qualifiedName: "xml:id" , value: Generator::cleanRef(ref: refForNode(node), xmlCompliant: true)); |
77 | } |
78 | |
79 | void DocBookGenerator::startSectionBegin(const QString &id) |
80 | { |
81 | m_hasSection = true; |
82 | |
83 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "section" ); |
84 | writeXmlId(id); |
85 | newLine(); |
86 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "title" ); |
87 | } |
88 | |
89 | void DocBookGenerator::startSectionBegin(const Node *node) |
90 | { |
91 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "section" ); |
92 | writeXmlId(node); |
93 | newLine(); |
94 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "title" ); |
95 | } |
96 | |
97 | void DocBookGenerator::startSectionEnd() |
98 | { |
99 | m_writer->writeEndElement(); // title |
100 | newLine(); |
101 | } |
102 | |
103 | void DocBookGenerator::startSection(const QString &id, const QString &title) |
104 | { |
105 | startSectionBegin(id); |
106 | m_writer->writeCharacters(text: title); |
107 | startSectionEnd(); |
108 | } |
109 | |
110 | void DocBookGenerator::startSection(const Node *node, const QString &title) |
111 | { |
112 | startSectionBegin(node); |
113 | m_writer->writeCharacters(text: title); |
114 | startSectionEnd(); |
115 | } |
116 | |
117 | void DocBookGenerator::startSection(const QString &title) |
118 | { |
119 | // No xml:id given: down the calls, "" is interpreted as "no ID". |
120 | startSection(id: "" , title); |
121 | } |
122 | |
123 | void DocBookGenerator::endSection() |
124 | { |
125 | m_writer->writeEndElement(); // section |
126 | newLine(); |
127 | } |
128 | |
129 | void DocBookGenerator::writeAnchor(const QString &id) |
130 | { |
131 | if (id.isEmpty()) |
132 | return; |
133 | |
134 | m_writer->writeEmptyElement(namespaceUri: dbNamespace, name: "anchor" ); |
135 | writeXmlId(id); |
136 | newLine(); |
137 | } |
138 | |
139 | /*! |
140 | Initializes the DocBook output generator's data structures |
141 | from the configuration (Config). |
142 | */ |
143 | void DocBookGenerator::initializeGenerator() |
144 | { |
145 | // Excerpts from HtmlGenerator::initializeGenerator. |
146 | Generator::initializeGenerator(); |
147 | m_config = &Config::instance(); |
148 | |
149 | m_project = m_config->get(CONFIG_PROJECT).asString(); |
150 | |
151 | m_projectDescription = m_config->get(CONFIG_DESCRIPTION).asString(); |
152 | if (m_projectDescription.isEmpty() && !m_project.isEmpty()) |
153 | m_projectDescription = m_project + QLatin1String(" Reference Documentation" ); |
154 | |
155 | m_naturalLanguage = m_config->get(CONFIG_NATURALLANGUAGE).asString(); |
156 | if (m_naturalLanguage.isEmpty()) |
157 | m_naturalLanguage = QLatin1String("en" ); |
158 | |
159 | m_buildVersion = m_config->get(CONFIG_BUILDVERSION).asString(); |
160 | m_useDocBook52 = m_config->get(CONFIG_DOCBOOKEXTENSIONS).asBool() || |
161 | m_config->get(var: format() + Config::dot + "usedocbookextensions" ).asBool(); |
162 | m_useITS = m_config->get(var: format() + Config::dot + "its" ).asBool(); |
163 | } |
164 | |
165 | QString DocBookGenerator::format() |
166 | { |
167 | return "DocBook" ; |
168 | } |
169 | |
170 | /*! |
171 | Returns "xml" for this subclass of Generator. |
172 | */ |
173 | QString DocBookGenerator::fileExtension() const |
174 | { |
175 | return "xml" ; |
176 | } |
177 | |
178 | /*! |
179 | Generate the documentation for \a relative. i.e. \a relative |
180 | is the node that represents the entity where a qdoc comment |
181 | was found, and \a text represents the qdoc comment. |
182 | */ |
183 | bool DocBookGenerator::generateText(const Text &text, const Node *relative) |
184 | { |
185 | // From Generator::generateText. |
186 | if (!text.firstAtom()) |
187 | return false; |
188 | |
189 | int numAtoms = 0; |
190 | initializeTextOutput(); |
191 | generateAtomList(atom: text.firstAtom(), relative, generate: true, numAtoms); |
192 | closeTextSections(); |
193 | return true; |
194 | } |
195 | |
196 | /*! |
197 | Generate the text for \a atom relatively to \a relative. |
198 | \a generate indicates if output to \a writer is expected. |
199 | The number of generated atoms is returned in the argument |
200 | \a numAtoms. The returned value is the first atom that was not |
201 | generated. |
202 | */ |
203 | const Atom *DocBookGenerator::generateAtomList(const Atom *atom, const Node *relative, |
204 | bool generate, int &numAtoms) |
205 | { |
206 | Q_ASSERT(m_writer); |
207 | // From Generator::generateAtomList. |
208 | while (atom) { |
209 | switch (atom->type()) { |
210 | case Atom::FormatIf: { |
211 | int numAtoms0 = numAtoms; |
212 | atom = generateAtomList(atom: atom->next(), relative, generate, numAtoms); |
213 | if (!atom) |
214 | return nullptr; |
215 | |
216 | if (atom->type() == Atom::FormatElse) { |
217 | ++numAtoms; |
218 | atom = generateAtomList(atom: atom->next(), relative, generate: false, numAtoms); |
219 | if (!atom) |
220 | return nullptr; |
221 | } |
222 | |
223 | if (atom->type() == Atom::FormatEndif) { |
224 | if (generate && numAtoms0 == numAtoms) { |
225 | relative->location().warning(QStringLiteral("Output format %1 not handled %2" ) |
226 | .arg(args: format(), args: outFileName())); |
227 | Atom unhandledFormatAtom(Atom::UnhandledFormat, format()); |
228 | generateAtomList(atom: &unhandledFormatAtom, relative, generate, numAtoms); |
229 | } |
230 | atom = atom->next(); |
231 | } |
232 | } break; |
233 | case Atom::FormatElse: |
234 | case Atom::FormatEndif: |
235 | return atom; |
236 | default: |
237 | int n = 1; |
238 | if (generate) { |
239 | n += generateAtom(atom, relative); |
240 | numAtoms += n; |
241 | } |
242 | while (n-- > 0) |
243 | atom = atom->next(); |
244 | } |
245 | } |
246 | return nullptr; |
247 | } |
248 | |
249 | QString removeCodeMarkers(const QString& code) { |
250 | QString rewritten = code; |
251 | static const QRegularExpression re("(<@[^>&]*>)|(<\\/@[^&>]*>)" ); |
252 | rewritten.replace(re, after: "" ); |
253 | return rewritten; |
254 | } |
255 | |
256 | /*! |
257 | Generate DocBook from an instance of Atom. |
258 | */ |
259 | qsizetype DocBookGenerator::generateAtom(const Atom *atom, const Node *relative) |
260 | { |
261 | Q_ASSERT(m_writer); |
262 | // From HtmlGenerator::generateAtom, without warning generation. |
263 | int idx = 0; |
264 | int skipAhead = 0; |
265 | Node::Genus genus = Node::DontCare; |
266 | |
267 | switch (atom->type()) { |
268 | case Atom::AutoLink: |
269 | // Allow auto-linking to nodes in API reference |
270 | genus = Node::API; |
271 | Q_FALLTHROUGH(); |
272 | case Atom::NavAutoLink: |
273 | if (!m_inLink && !m_inContents && !m_inSectionHeading) { |
274 | const Node *node = nullptr; |
275 | QString link = getAutoLink(atom, relative, node: &node, genus); |
276 | if (!link.isEmpty() && node && node->isDeprecated() |
277 | && relative->parent() != node && !relative->isDeprecated()) { |
278 | link.clear(); |
279 | } |
280 | if (link.isEmpty()) { |
281 | m_writer->writeCharacters(text: atom->string()); |
282 | } else { |
283 | beginLink(link, node, relative); |
284 | generateLink(atom); |
285 | endLink(); |
286 | } |
287 | } else { |
288 | m_writer->writeCharacters(text: atom->string()); |
289 | } |
290 | break; |
291 | case Atom::BaseName: |
292 | break; |
293 | case Atom::BriefLeft: |
294 | if (!hasBrief(node: relative)) { |
295 | skipAhead = skipAtoms(atom, type: Atom::BriefRight); |
296 | break; |
297 | } |
298 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "para" ); |
299 | m_inPara = true; |
300 | rewritePropertyBrief(atom, relative); |
301 | break; |
302 | case Atom::BriefRight: |
303 | if (hasBrief(node: relative)) { |
304 | m_writer->writeEndElement(); // para |
305 | m_inPara = false; |
306 | newLine(); |
307 | } |
308 | break; |
309 | case Atom::C: |
310 | // This may at one time have been used to mark up C++ code but it is |
311 | // now widely used to write teletype text. As a result, text marked |
312 | // with the \c command is not passed to a code marker. |
313 | if (m_inTeletype) |
314 | m_writer->writeCharacters(text: plainCode(markedCode: atom->string())); |
315 | else |
316 | m_writer->writeTextElement(namespaceUri: dbNamespace, name: "code" , text: plainCode(markedCode: atom->string())); |
317 | break; |
318 | case Atom::CaptionLeft: |
319 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "title" ); |
320 | break; |
321 | case Atom::CaptionRight: |
322 | endLink(); |
323 | m_writer->writeEndElement(); // title |
324 | newLine(); |
325 | break; |
326 | case Atom::Qml: |
327 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "programlisting" ); |
328 | m_writer->writeAttribute(qualifiedName: "language" , value: "qml" ); |
329 | if (m_useITS) |
330 | m_writer->writeAttribute(namespaceUri: itsNamespace, name: "translate" , value: "no" ); |
331 | m_writer->writeCharacters(text: removeCodeMarkers(code: atom->string())); |
332 | m_writer->writeEndElement(); // programlisting |
333 | newLine(); |
334 | break; |
335 | case Atom::Code: |
336 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "programlisting" ); |
337 | m_writer->writeAttribute(qualifiedName: "language" , value: "cpp" ); |
338 | if (m_useITS) |
339 | m_writer->writeAttribute(namespaceUri: itsNamespace, name: "translate" , value: "no" ); |
340 | m_writer->writeCharacters(text: removeCodeMarkers(code: atom->string())); |
341 | m_writer->writeEndElement(); // programlisting |
342 | newLine(); |
343 | break; |
344 | case Atom::CodeBad: |
345 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "programlisting" ); |
346 | m_writer->writeAttribute(qualifiedName: "language" , value: "cpp" ); |
347 | m_writer->writeAttribute(qualifiedName: "role" , value: "bad" ); |
348 | if (m_useITS) |
349 | m_writer->writeAttribute(namespaceUri: itsNamespace, name: "translate" , value: "no" ); |
350 | m_writer->writeCharacters(text: removeCodeMarkers(code: atom->string())); |
351 | m_writer->writeEndElement(); // programlisting |
352 | newLine(); |
353 | break; |
354 | case Atom::DetailsLeft: |
355 | case Atom::DetailsRight: |
356 | break; |
357 | case Atom::DivLeft: |
358 | case Atom::DivRight: |
359 | break; |
360 | case Atom::FootnoteLeft: |
361 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "footnote" ); |
362 | newLine(); |
363 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "para" ); |
364 | m_inPara = true; |
365 | break; |
366 | case Atom::FootnoteRight: |
367 | m_writer->writeEndElement(); // para |
368 | m_inPara = false; |
369 | newLine(); |
370 | m_writer->writeEndElement(); // footnote |
371 | break; |
372 | case Atom::FormatElse: |
373 | case Atom::FormatEndif: |
374 | case Atom::FormatIf: |
375 | break; |
376 | case Atom::FormattingLeft: |
377 | if (atom->string() == ATOM_FORMATTING_BOLD) { |
378 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "emphasis" ); |
379 | m_writer->writeAttribute(qualifiedName: "role" , value: "bold" ); |
380 | } else if (atom->string() == ATOM_FORMATTING_ITALIC) { |
381 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "emphasis" ); |
382 | } else if (atom->string() == ATOM_FORMATTING_UNDERLINE) { |
383 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "emphasis" ); |
384 | m_writer->writeAttribute(qualifiedName: "role" , value: "underline" ); |
385 | } else if (atom->string() == ATOM_FORMATTING_SUBSCRIPT) { |
386 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "subscript" ); |
387 | } else if (atom->string() == ATOM_FORMATTING_SUPERSCRIPT) { |
388 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "superscript" ); |
389 | } else if (atom->string() == ATOM_FORMATTING_TELETYPE |
390 | || atom->string() == ATOM_FORMATTING_PARAMETER) { |
391 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "code" ); |
392 | if (m_useITS) |
393 | m_writer->writeAttribute(namespaceUri: itsNamespace, name: "translate" , value: "no" ); |
394 | |
395 | if (atom->string() == ATOM_FORMATTING_PARAMETER) |
396 | m_writer->writeAttribute(qualifiedName: "role" , value: "parameter" ); |
397 | else // atom->string() == ATOM_FORMATTING_TELETYPE |
398 | m_inTeletype = true; |
399 | |
400 | // For parameters, understand subscripts. |
401 | if (atom->string() == ATOM_FORMATTING_PARAMETER) { |
402 | if (atom->next() != nullptr && atom->next()->type() == Atom::String) { |
403 | static const QRegularExpression subscriptRegExp("^([a-z]+)_([0-9n])$" ); |
404 | auto match = subscriptRegExp.match(subject: atom->next()->string()); |
405 | if (match.hasMatch()) { |
406 | m_writer->writeCharacters(text: match.captured(nth: 1)); |
407 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "subscript" ); |
408 | m_writer->writeCharacters(text: match.captured(nth: 2)); |
409 | m_writer->writeEndElement(); // subscript |
410 | skipAhead = 1; |
411 | } |
412 | } |
413 | } |
414 | } else if (atom->string() == ATOM_FORMATTING_UICONTROL) { |
415 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "guilabel" ); |
416 | if (m_useITS) |
417 | m_writer->writeAttribute(namespaceUri: itsNamespace, name: "translate" , value: "no" ); |
418 | } else { |
419 | relative->location().warning(QStringLiteral("Unsupported formatting: %1" ).arg(a: atom->string())); |
420 | } |
421 | break; |
422 | case Atom::FormattingRight: |
423 | if (atom->string() == ATOM_FORMATTING_BOLD || atom->string() == ATOM_FORMATTING_ITALIC |
424 | || atom->string() == ATOM_FORMATTING_UNDERLINE |
425 | || atom->string() == ATOM_FORMATTING_SUBSCRIPT |
426 | || atom->string() == ATOM_FORMATTING_SUPERSCRIPT |
427 | || atom->string() == ATOM_FORMATTING_TELETYPE |
428 | || atom->string() == ATOM_FORMATTING_PARAMETER |
429 | || atom->string() == ATOM_FORMATTING_UICONTROL) { |
430 | m_writer->writeEndElement(); |
431 | } else if (atom->string() == ATOM_FORMATTING_LINK) { |
432 | if (atom->string() == ATOM_FORMATTING_TELETYPE) |
433 | m_inTeletype = false; |
434 | endLink(); |
435 | } else { |
436 | relative->location().warning(QStringLiteral("Unsupported formatting: %1" ).arg(a: atom->string())); |
437 | } |
438 | break; |
439 | case Atom::AnnotatedList: |
440 | if (const CollectionNode *cn = m_qdb->getCollectionNode(name: atom->string(), type: Node::Group)) |
441 | generateList(relative: cn, selector: atom->string()); |
442 | break; |
443 | case Atom::GeneratedList: { |
444 | bool hasGeneratedSomething = false; |
445 | if (atom->string() == QLatin1String("annotatedclasses" ) |
446 | || atom->string() == QLatin1String("attributions" ) |
447 | || atom->string() == QLatin1String("namespaces" )) { |
448 | const NodeMultiMap things = atom->string() == QLatin1String("annotatedclasses" ) |
449 | ? m_qdb->getCppClasses() |
450 | : atom->string() == QLatin1String("attributions" ) ? m_qdb->getAttributions() |
451 | : m_qdb->getNamespaces(); |
452 | generateAnnotatedList(relative, nodeList: things.values(), selector: atom->string()); |
453 | hasGeneratedSomething = !things.isEmpty(); |
454 | } else if (atom->string() == QLatin1String("annotatedexamples" ) |
455 | || atom->string() == QLatin1String("annotatedattributions" )) { |
456 | const NodeMultiMap things = atom->string() == QLatin1String("annotatedexamples" ) |
457 | ? m_qdb->getAttributions() |
458 | : m_qdb->getExamples(); |
459 | generateAnnotatedLists(relative, nmm: things, selector: atom->string()); |
460 | hasGeneratedSomething = !things.isEmpty(); |
461 | } else if (atom->string() == QLatin1String("classes" ) |
462 | || atom->string() == QLatin1String("qmlbasictypes" ) // deprecated! |
463 | || atom->string() == QLatin1String("qmlvaluetypes" ) |
464 | || atom->string() == QLatin1String("qmltypes" )) { |
465 | const NodeMultiMap things = atom->string() == QLatin1String("classes" ) |
466 | ? m_qdb->getCppClasses() |
467 | : (atom->string() == QLatin1String("qmlvaluetypes" ) |
468 | || atom->string() == QLatin1String("qmlbasictypes" )) |
469 | ? m_qdb->getQmlValueTypes() |
470 | : m_qdb->getQmlTypes(); |
471 | generateCompactList(relative, nmm: things, includeAlphabet: true, commonPrefix: QString(), selector: atom->string()); |
472 | hasGeneratedSomething = !things.isEmpty(); |
473 | } else if (atom->string().contains(s: "classes " )) { |
474 | QString rootName = atom->string().mid(position: atom->string().indexOf(s: "classes" ) + 7).trimmed(); |
475 | NodeMultiMap things = m_qdb->getCppClasses(); |
476 | |
477 | hasGeneratedSomething = !things.isEmpty(); |
478 | generateCompactList(relative, nmm: things, includeAlphabet: true, commonPrefix: rootName, selector: atom->string()); |
479 | } else if ((idx = atom->string().indexOf(QStringLiteral("bymodule" ))) != -1) { |
480 | QString moduleName = atom->string().mid(position: idx + 8).trimmed(); |
481 | Node::NodeType type = typeFromString(atom); |
482 | QDocDatabase *qdb = QDocDatabase::qdocDB(); |
483 | if (const CollectionNode *cn = qdb->getCollectionNode(name: moduleName, type)) { |
484 | if (type == Node::Module) { |
485 | NodeMap m; |
486 | cn->getMemberClasses(out&: m); |
487 | if (!m.isEmpty()) |
488 | generateAnnotatedList(relative, nodeList: m.values(), selector: atom->string()); |
489 | hasGeneratedSomething = !m.isEmpty(); |
490 | } else { |
491 | generateAnnotatedList(relative, nodeList: cn->members(), selector: atom->string()); |
492 | hasGeneratedSomething = !cn->members().isEmpty(); |
493 | } |
494 | } |
495 | } else if (atom->string() == QLatin1String("classhierarchy" )) { |
496 | generateClassHierarchy(relative, classMap&: m_qdb->getCppClasses()); |
497 | hasGeneratedSomething = !m_qdb->getCppClasses().isEmpty(); |
498 | } else if (atom->string().startsWith(s: "obsolete" )) { |
499 | QString prefix = atom->string().contains(s: "cpp" ) ? QStringLiteral("Q" ) : QString(); |
500 | const NodeMultiMap &things = atom->string() == QLatin1String("obsoleteclasses" ) |
501 | ? m_qdb->getObsoleteClasses() |
502 | : atom->string() == QLatin1String("obsoleteqmltypes" ) |
503 | ? m_qdb->getObsoleteQmlTypes() |
504 | : atom->string() == QLatin1String("obsoletecppmembers" ) |
505 | ? m_qdb->getClassesWithObsoleteMembers() |
506 | : m_qdb->getQmlTypesWithObsoleteMembers(); |
507 | generateCompactList(relative, nmm: things, includeAlphabet: false, commonPrefix: prefix, selector: atom->string()); |
508 | hasGeneratedSomething = !things.isEmpty(); |
509 | } else if (atom->string() == QLatin1String("functionindex" )) { |
510 | generateFunctionIndex(relative); |
511 | hasGeneratedSomething = !m_qdb->getFunctionIndex().isEmpty(); |
512 | } else if (atom->string() == QLatin1String("legalese" )) { |
513 | generateLegaleseList(relative); |
514 | hasGeneratedSomething = !m_qdb->getLegaleseTexts().isEmpty(); |
515 | } else if (atom->string() == QLatin1String("overviews" ) |
516 | || atom->string() == QLatin1String("cpp-modules" ) |
517 | || atom->string() == QLatin1String("qml-modules" ) |
518 | || atom->string() == QLatin1String("related" )) { |
519 | generateList(relative, selector: atom->string()); |
520 | hasGeneratedSomething = true; // Approximation, because there is |
521 | // some nontrivial logic in generateList. |
522 | } else if (const auto *cn = m_qdb->getCollectionNode(name: atom->string(), type: Node::Group); cn) { |
523 | generateAnnotatedList(relative: cn, nodeList: cn->members(), selector: atom->string(), type: ItemizedList); |
524 | hasGeneratedSomething = true; // Approximation |
525 | } |
526 | |
527 | // There must still be some content generated for the DocBook document |
528 | // to be valid (except if already in a paragraph). |
529 | if (!hasGeneratedSomething && !m_inPara) { |
530 | m_writer->writeEmptyElement(namespaceUri: dbNamespace, name: "para" ); |
531 | newLine(); |
532 | } |
533 | } |
534 | break; |
535 | case Atom::SinceList: |
536 | // Table of contents, should automatically be generated by the DocBook processor. |
537 | Q_FALLTHROUGH(); |
538 | case Atom::LineBreak: |
539 | case Atom::BR: |
540 | case Atom::HR: |
541 | // Not supported in DocBook. |
542 | break; |
543 | case Atom::Image: // mediaobject |
544 | // An Image atom is always followed by an ImageText atom, |
545 | // containing the alternative text. |
546 | // If no caption is present, we just output a <db:mediaobject>, |
547 | // avoiding the wrapper as it is not required. |
548 | // For bordered images, there is another atom before the |
549 | // caption, DivRight (the corresponding DivLeft being just |
550 | // before the image). |
551 | |
552 | if (atom->next() && matchAhead(atom: atom->next(), expectedAtomType: Atom::DivRight) && atom->next()->next() |
553 | && matchAhead(atom: atom->next()->next(), expectedAtomType: Atom::CaptionLeft)) { |
554 | // If there is a caption, there must be a <db:figure> |
555 | // wrapper starting with the caption. |
556 | Q_ASSERT(atom->next()); |
557 | Q_ASSERT(atom->next()->next()); |
558 | Q_ASSERT(atom->next()->next()->next()); |
559 | Q_ASSERT(atom->next()->next()->next()->next()); |
560 | Q_ASSERT(atom->next()->next()->next()->next()->next()); |
561 | |
562 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "figure" ); |
563 | newLine(); |
564 | |
565 | const Atom *current = atom->next()->next()->next(); |
566 | skipAhead += 2; |
567 | |
568 | Q_ASSERT(current->type() == Atom::CaptionLeft); |
569 | generateAtom(atom: current, relative); |
570 | current = current->next(); |
571 | ++skipAhead; |
572 | |
573 | while (current->type() != Atom::CaptionRight) { // The actual caption. |
574 | generateAtom(atom: current, relative); |
575 | current = current->next(); |
576 | ++skipAhead; |
577 | } |
578 | |
579 | Q_ASSERT(current->type() == Atom::CaptionRight); |
580 | generateAtom(atom: current, relative); |
581 | current = current->next(); |
582 | ++skipAhead; |
583 | |
584 | m_closeFigureWrapper = true; |
585 | } |
586 | |
587 | if (atom->next() && matchAhead(atom: atom->next(), expectedAtomType: Atom::CaptionLeft)) { |
588 | // If there is a caption, there must be a <db:figure> |
589 | // wrapper starting with the caption. |
590 | Q_ASSERT(atom->next()); |
591 | Q_ASSERT(atom->next()->next()); |
592 | Q_ASSERT(atom->next()->next()->next()); |
593 | Q_ASSERT(atom->next()->next()->next()->next()); |
594 | |
595 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "figure" ); |
596 | newLine(); |
597 | |
598 | const Atom *current = atom->next()->next(); |
599 | ++skipAhead; |
600 | |
601 | Q_ASSERT(current->type() == Atom::CaptionLeft); |
602 | generateAtom(atom: current, relative); |
603 | current = current->next(); |
604 | ++skipAhead; |
605 | |
606 | while (current->type() != Atom::CaptionRight) { // The actual caption. |
607 | generateAtom(atom: current, relative); |
608 | current = current->next(); |
609 | ++skipAhead; |
610 | } |
611 | |
612 | Q_ASSERT(current->type() == Atom::CaptionRight); |
613 | generateAtom(atom: current, relative); |
614 | current = current->next(); |
615 | ++skipAhead; |
616 | |
617 | m_closeFigureWrapper = true; |
618 | } |
619 | |
620 | Q_FALLTHROUGH(); |
621 | case Atom::InlineImage: { // inlinemediaobject |
622 | // TODO: [generator-insufficient-structural-abstraction] |
623 | // The structure of the computations for this part of the |
624 | // docbook generation and the same parts in other format |
625 | // generators is the same. |
626 | // |
627 | // The difference, instead, lies in what the generated output |
628 | // is like. A correct abstraction for a generator would take |
629 | // this structural equivalence into account and encapsulate it |
630 | // into a driver for the format generators. |
631 | // |
632 | // This would avoid the replication of content, and the |
633 | // subsequent friction for changes and desynchronization |
634 | // between generators. |
635 | // |
636 | // Review all the generators routines and find the actual |
637 | // skeleton that is shared between them, then consider it when |
638 | // extracting the logic for the generation phase. |
639 | QString tag = atom->type() == Atom::Image ? "mediaobject" : "inlinemediaobject" ; |
640 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: tag); |
641 | newLine(); |
642 | |
643 | auto maybe_resolved_file{file_resolver.resolve(filename: atom->string())}; |
644 | if (!maybe_resolved_file) { |
645 | // TODO: [uncetnralized-admonition][failed-resolve-file] |
646 | relative->location().warning(QStringLiteral("Missing image: %1" ).arg(a: atom->string())); |
647 | |
648 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "textobject" ); |
649 | newLine(); |
650 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "para" ); |
651 | m_writer->writeTextElement(namespaceUri: dbNamespace, name: "emphasis" , |
652 | text: "[Missing image " + atom->string() + "]" ); |
653 | m_writer->writeEndElement(); // para |
654 | newLine(); |
655 | m_writer->writeEndElement(); // textobject |
656 | newLine(); |
657 | } else { |
658 | ResolvedFile file{*maybe_resolved_file}; |
659 | QString file_name{QFileInfo{file.get_path()}.fileName()}; |
660 | |
661 | // TODO: [uncentralized-output-directory-structure] |
662 | Config::copyFile(location: relative->doc().location(), sourceFilePath: file.get_path(), userFriendlySourceFilePath: file_name, targetDirPath: outputDir() + QLatin1String("/images" )); |
663 | |
664 | if (atom->next() && !atom->next()->string().isEmpty() |
665 | && atom->next()->type() == Atom::ImageText) { |
666 | m_writer->writeTextElement(namespaceUri: dbNamespace, name: "alt" , text: atom->next()->string()); |
667 | newLine(); |
668 | } |
669 | |
670 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "imageobject" ); |
671 | newLine(); |
672 | m_writer->writeEmptyElement(namespaceUri: dbNamespace, name: "imagedata" ); |
673 | // TODO: [uncentralized-output-directory-structure] |
674 | m_writer->writeAttribute(qualifiedName: "fileref" , value: "images/" + file_name); |
675 | newLine(); |
676 | m_writer->writeEndElement(); // imageobject |
677 | newLine(); |
678 | |
679 | // TODO: [uncentralized-output-directory-structure] |
680 | setImageFileName(relative, fileName: "images/" + file_name); |
681 | } |
682 | |
683 | m_writer->writeEndElement(); // [inline]mediaobject |
684 | if (atom->type() == Atom::Image) |
685 | newLine(); |
686 | |
687 | if (m_closeFigureWrapper) { |
688 | m_writer->writeEndElement(); // figure |
689 | newLine(); |
690 | m_closeFigureWrapper = false; |
691 | } |
692 | } break; |
693 | case Atom::ImageText: |
694 | break; |
695 | case Atom::ImportantLeft: |
696 | case Atom::NoteLeft: |
697 | case Atom::WarningLeft: { |
698 | QString admonType = atom->typeString().toLower(); |
699 | // Remove 'Left' to get the admonition type |
700 | admonType.chop(n: 4); |
701 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: admonType); |
702 | newLine(); |
703 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "para" ); |
704 | m_inPara = true; |
705 | } break; |
706 | case Atom::ImportantRight: |
707 | case Atom::NoteRight: |
708 | case Atom::WarningRight: |
709 | m_writer->writeEndElement(); // para |
710 | m_inPara = false; |
711 | newLine(); |
712 | m_writer->writeEndElement(); // note/important |
713 | newLine(); |
714 | break; |
715 | case Atom::LegaleseLeft: |
716 | case Atom::LegaleseRight: |
717 | break; |
718 | case Atom::Link: |
719 | case Atom::NavLink: { |
720 | const Node *node = nullptr; |
721 | QString link = getLink(atom, relative, node: &node); |
722 | beginLink(link, node, relative); // Ended at Atom::FormattingRight |
723 | skipAhead = 1; |
724 | } break; |
725 | case Atom::LinkNode: { |
726 | const Node *node = CodeMarker::nodeForString(string: atom->string()); |
727 | beginLink(link: linkForNode(node, relative), node, relative); |
728 | skipAhead = 1; |
729 | } break; |
730 | case Atom::ListLeft: |
731 | if (m_inPara) { |
732 | // The variable m_inPara is not set in a very smart way, because |
733 | // it ignores nesting. This might in theory create false positives |
734 | // here. A better solution would be to track the depth of |
735 | // paragraphs the generator is in, but determining the right check |
736 | // for this condition is far from trivial (think of nested lists). |
737 | m_writer->writeEndElement(); // para |
738 | newLine(); |
739 | m_inPara = false; |
740 | } |
741 | |
742 | if (atom->string() == ATOM_LIST_BULLET) { |
743 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "itemizedlist" ); |
744 | newLine(); |
745 | } else if (atom->string() == ATOM_LIST_TAG) { |
746 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "variablelist" ); |
747 | newLine(); |
748 | } else if (atom->string() == ATOM_LIST_VALUE) { |
749 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "informaltable" ); |
750 | newLine(); |
751 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "thead" ); |
752 | newLine(); |
753 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "tr" ); |
754 | newLine(); |
755 | m_writer->writeTextElement(namespaceUri: dbNamespace, name: "th" , text: "Constant" ); |
756 | newLine(); |
757 | |
758 | m_threeColumnEnumValueTable = isThreeColumnEnumValueTable(atom); |
759 | if (m_threeColumnEnumValueTable && relative->nodeType() == Node::Enum) { |
760 | // With three columns, if not in \enum topic, skip the value column |
761 | m_writer->writeTextElement(namespaceUri: dbNamespace, name: "th" , text: "Value" ); |
762 | newLine(); |
763 | } |
764 | |
765 | if (!isOneColumnValueTable(atom)) { |
766 | m_writer->writeTextElement(namespaceUri: dbNamespace, name: "th" , text: "Description" ); |
767 | newLine(); |
768 | } |
769 | |
770 | m_writer->writeEndElement(); // tr |
771 | newLine(); |
772 | m_writer->writeEndElement(); // thead |
773 | newLine(); |
774 | } else { // No recognized list type. |
775 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "orderedlist" ); |
776 | |
777 | if (atom->next() != nullptr && atom->next()->string().toInt() > 1) |
778 | m_writer->writeAttribute(qualifiedName: "startingnumber" , value: atom->next()->string()); |
779 | |
780 | if (atom->string() == ATOM_LIST_UPPERALPHA) |
781 | m_writer->writeAttribute(qualifiedName: "numeration" , value: "upperalpha" ); |
782 | else if (atom->string() == ATOM_LIST_LOWERALPHA) |
783 | m_writer->writeAttribute(qualifiedName: "numeration" , value: "loweralpha" ); |
784 | else if (atom->string() == ATOM_LIST_UPPERROMAN) |
785 | m_writer->writeAttribute(qualifiedName: "numeration" , value: "upperroman" ); |
786 | else if (atom->string() == ATOM_LIST_LOWERROMAN) |
787 | m_writer->writeAttribute(qualifiedName: "numeration" , value: "lowerroman" ); |
788 | else // (atom->string() == ATOM_LIST_NUMERIC) |
789 | m_writer->writeAttribute(qualifiedName: "numeration" , value: "arabic" ); |
790 | |
791 | newLine(); |
792 | } |
793 | m_inList++; |
794 | break; |
795 | case Atom::ListItemNumber: |
796 | break; |
797 | case Atom::ListTagLeft: |
798 | if (atom->string() == ATOM_LIST_TAG) { |
799 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "varlistentry" ); |
800 | newLine(); |
801 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "item" ); |
802 | } else { // (atom->string() == ATOM_LIST_VALUE) |
803 | std::pair<QString, int> pair = getAtomListValue(atom); |
804 | skipAhead = pair.second; |
805 | |
806 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "tr" ); |
807 | newLine(); |
808 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "td" ); |
809 | newLine(); |
810 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "para" ); |
811 | if (m_useITS) |
812 | m_writer->writeAttribute(namespaceUri: itsNamespace, name: "translate" , value: "no" ); |
813 | generateEnumValue(enumValue: pair.first, relative); |
814 | m_writer->writeEndElement(); // para |
815 | newLine(); |
816 | m_writer->writeEndElement(); // td |
817 | newLine(); |
818 | |
819 | if (relative->nodeType() == Node::Enum) { |
820 | const auto enume = static_cast<const EnumNode *>(relative); |
821 | QString itemValue = enume->itemValue(name: atom->next()->string()); |
822 | |
823 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "td" ); |
824 | if (itemValue.isEmpty()) |
825 | m_writer->writeCharacters(text: "?" ); |
826 | else { |
827 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "code" ); |
828 | if (m_useITS) |
829 | m_writer->writeAttribute(namespaceUri: itsNamespace, name: "translate" , value: "no" ); |
830 | m_writer->writeCharacters(text: itemValue); |
831 | m_writer->writeEndElement(); // code |
832 | } |
833 | m_writer->writeEndElement(); // td |
834 | newLine(); |
835 | } |
836 | } |
837 | m_inList++; |
838 | break; |
839 | case Atom::SinceTagRight: |
840 | if (atom->string() == ATOM_LIST_TAG) { |
841 | m_writer->writeEndElement(); // item |
842 | newLine(); |
843 | } |
844 | break; |
845 | case Atom::ListTagRight: |
846 | if (m_inList > 0 && atom->string() == ATOM_LIST_TAG) { |
847 | m_writer->writeEndElement(); // item |
848 | newLine(); |
849 | m_inList = false; |
850 | } |
851 | break; |
852 | case Atom::ListItemLeft: |
853 | if (m_inList > 0) { |
854 | m_inListItemLineOpen = false; |
855 | if (atom->string() == ATOM_LIST_TAG) { |
856 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "listitem" ); |
857 | newLine(); |
858 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "para" ); |
859 | m_inPara = true; |
860 | } else if (atom->string() == ATOM_LIST_VALUE) { |
861 | if (m_threeColumnEnumValueTable) { |
862 | if (matchAhead(atom, expectedAtomType: Atom::ListItemRight)) { |
863 | m_writer->writeEmptyElement(namespaceUri: dbNamespace, name: "td" ); |
864 | newLine(); |
865 | m_inListItemLineOpen = false; |
866 | } else { |
867 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "td" ); |
868 | newLine(); |
869 | m_inListItemLineOpen = true; |
870 | } |
871 | } |
872 | } else { |
873 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "listitem" ); |
874 | newLine(); |
875 | } |
876 | // Don't skip a paragraph, DocBook requires them within list items. |
877 | } |
878 | break; |
879 | case Atom::ListItemRight: |
880 | if (m_inList > 0) { |
881 | if (atom->string() == ATOM_LIST_TAG) { |
882 | m_writer->writeEndElement(); // para |
883 | m_inPara = false; |
884 | newLine(); |
885 | m_writer->writeEndElement(); // listitem |
886 | newLine(); |
887 | m_writer->writeEndElement(); // varlistentry |
888 | newLine(); |
889 | } else if (atom->string() == ATOM_LIST_VALUE) { |
890 | if (m_inListItemLineOpen) { |
891 | m_writer->writeEndElement(); // td |
892 | newLine(); |
893 | m_inListItemLineOpen = false; |
894 | } |
895 | m_writer->writeEndElement(); // tr |
896 | newLine(); |
897 | } else { |
898 | m_writer->writeEndElement(); // listitem |
899 | newLine(); |
900 | } |
901 | } |
902 | break; |
903 | case Atom::ListRight: |
904 | // Depending on atom->string(), closing a different item: |
905 | // - ATOM_LIST_BULLET: itemizedlist |
906 | // - ATOM_LIST_TAG: variablelist |
907 | // - ATOM_LIST_VALUE: informaltable |
908 | // - ATOM_LIST_NUMERIC: orderedlist |
909 | m_writer->writeEndElement(); |
910 | newLine(); |
911 | m_inList--; |
912 | break; |
913 | case Atom::Nop: |
914 | break; |
915 | case Atom::ParaLeft: |
916 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "para" ); |
917 | m_inPara = true; |
918 | break; |
919 | case Atom::ParaRight: |
920 | endLink(); |
921 | if (m_inPara) { |
922 | m_writer->writeEndElement(); // para |
923 | newLine(); |
924 | m_inPara = false; |
925 | } |
926 | break; |
927 | case Atom::QuotationLeft: |
928 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "blockquote" ); |
929 | m_inBlockquote = true; |
930 | break; |
931 | case Atom::QuotationRight: |
932 | m_writer->writeEndElement(); // blockquote |
933 | newLine(); |
934 | m_inBlockquote = false; |
935 | break; |
936 | case Atom::RawString: { |
937 | // Many of these transformations are only useful when dealing with |
938 | // older versions of Qt, with their idiosyncrasies. However, they |
939 | // also make qdoc hardened against new problematic raw strings. |
940 | bool hasRewrittenString = false; |
941 | const QString &str = atom->string().trimmed(); |
942 | |
943 | static QHash<QString, QString> entitiesMapping; |
944 | if (entitiesMapping.isEmpty()) { |
945 | // These mappings come from qtbase\doc\global\macros.qdocconf. |
946 | entitiesMapping["á" ] = "á" ; |
947 | entitiesMapping["Å" ] = "Å" ; |
948 | entitiesMapping["å" ] = "å" ; |
949 | entitiesMapping["Ä" ] = "Ä" ; |
950 | entitiesMapping["©right;" ] = "©" ; |
951 | entitiesMapping["é" ] = "é" ; |
952 | entitiesMapping["í" ] = "í" ; |
953 | entitiesMapping["ø" ] = "ø" ; |
954 | entitiesMapping["ö" ] = "ö" ; |
955 | entitiesMapping["&rarrow;" ] = "→" ; |
956 | entitiesMapping["ü" ] = "ü" ; |
957 | entitiesMapping["—" ] = "—" ; |
958 | entitiesMapping["Π" ] = "Π" ; |
959 | } |
960 | |
961 | if (str.startsWith(s: R"(<link rel="stylesheet" type="text/css")" )) { |
962 | hasRewrittenString = true; |
963 | m_writer->writeComment(text: str); |
964 | } else if (str == "\\sup{*}" ) { |
965 | hasRewrittenString = true; |
966 | m_writer->writeTextElement(namespaceUri: dbNamespace, name: "superscript" , text: "*" ); |
967 | } else if (str.startsWith(s: "<sup>" ) && str.endsWith(s: "</sup>" )) { |
968 | hasRewrittenString = true; |
969 | m_writer->writeTextElement(namespaceUri: dbNamespace, name: "superscript" , text: str.mid(position: 5, n: str.size() - 5 - 6)); |
970 | } else if (str.startsWith(s: "<div class=\"video" )) { |
971 | hasRewrittenString = true; |
972 | |
973 | // Sequence of atoms: |
974 | // - RawString (this one): <div class="video">\n<a href="https://www.youtube.com/watch/?v= |
975 | // - String: video ID |
976 | // - RawString: ">\n<img src="images/ |
977 | // - String: video ID, again (but with an associated image) |
978 | // - RawString: .jpg" title="Click to play in a browser" /></a>\n</div>\n |
979 | // TODO: No call to file resolver, like the other generators. Would it be required? |
980 | // auto maybe_resolved_file{file_resolver.resolve(atom->string())}; |
981 | Q_ASSERT(atom->next()); |
982 | Q_ASSERT(atom->next()->next()); |
983 | Q_ASSERT(atom->next()->next()->next()); |
984 | Q_ASSERT(atom->next()->next()->next()->next()); |
985 | Q_ASSERT(atom->next()->type() == Atom::String); |
986 | Q_ASSERT(atom->next()->next()->next()->type() == Atom::String); |
987 | skipAhead += 4; |
988 | |
989 | const QString &videoID = atom->next()->string(); |
990 | const QString &imageID = atom->next()->next()->next()->string(); |
991 | |
992 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "mediaobject" ); |
993 | newLine(); |
994 | |
995 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "videoobject" ); |
996 | newLine(); |
997 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "videodata" ); |
998 | m_writer->writeAttribute(qualifiedName: "fileref" , value: videoID); |
999 | newLine(); |
1000 | m_writer->writeEndElement(); // videodata |
1001 | newLine(); |
1002 | m_writer->writeEndElement(); // videoobject |
1003 | newLine(); |
1004 | |
1005 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "imageobject" ); |
1006 | newLine(); |
1007 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "imagedata" ); |
1008 | m_writer->writeAttribute(qualifiedName: "fileref" , value: "images/" + imageID + ".jpg" ); |
1009 | newLine(); |
1010 | m_writer->writeEndElement(); // imagedata |
1011 | newLine(); |
1012 | m_writer->writeEndElement(); // imageobject |
1013 | newLine(); |
1014 | |
1015 | m_writer->writeEndElement(); // mediaobject |
1016 | newLine(); |
1017 | } else if (str.startsWith(s: "<h" ) && str.size() >= 9) { // <hX></hX>: 9 characters. |
1018 | // If qdoc has just closed a section, suppose that the person |
1019 | // writing this RawString knows what they are doing generate a |
1020 | // section. Otherwise, create a bridgehead. |
1021 | bool hasJustClosedASection = !m_writer->device()->isSequential() && |
1022 | m_writer->device()->readAll().trimmed().endsWith(bv: "</db:section>" ); |
1023 | |
1024 | // Parse the raw string. If nothing matches, no title is found, |
1025 | // and no rewriting is performed. |
1026 | QChar level = str[2]; |
1027 | QString title {"" }; |
1028 | QString id {"" }; |
1029 | |
1030 | if (str.startsWith(s: "<h" + level + ">" ) && str.endsWith(s: "</h" + level + ">" )) { |
1031 | title = str.mid(position: 4, n: str.size() - 9); |
1032 | } else if (str.startsWith(s: "<h" + level + " id=" ) && str.endsWith(s: "</h" + level + ">" )) { |
1033 | // <hX id=: 7 characters. |
1034 | QString idToEndTag = str.mid(position: 8, n: str.size() - 7 - 5); |
1035 | id = idToEndTag.split(sep: "\"" )[0]; |
1036 | title = idToEndTag.remove(i: 0, len: id.size() + 2).chopped(n: 1); |
1037 | } |
1038 | |
1039 | // Output the DocBook equivalent. |
1040 | if (!title.isEmpty()) { |
1041 | hasRewrittenString = true; |
1042 | |
1043 | if (hasJustClosedASection) { |
1044 | startSection(id, title); |
1045 | m_closeSectionAfterRawTitle = true; |
1046 | } else { |
1047 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "bridgehead" ); |
1048 | m_writer->writeAttribute(qualifiedName: "renderas" , value: "sect" + level); |
1049 | writeXmlId(id); |
1050 | m_writer->writeCharacters(text: title); |
1051 | m_writer->writeEndElement(); // bridgehead |
1052 | } |
1053 | |
1054 | // If there is an anchor just after with the same ID, skip it. |
1055 | if (matchAhead(atom, expectedAtomType: Atom::Target) |
1056 | && Utilities::asAsciiPrintable(name: atom->next()->string()) == id) { |
1057 | ++skipAhead; |
1058 | } |
1059 | } else { |
1060 | // The formatting is not recognized: it starts with a tittle, |
1061 | // then some unknown stuff. It's highly likely some qdoc |
1062 | // example: output that as raw HTML in DocBook too. |
1063 | writeRawHtml(rawCode: str); |
1064 | hasRewrittenString = true; |
1065 | } |
1066 | } else if (// Formatting of images. |
1067 | str.startsWith(s: R"(<div class="table"><table style="background:transparent; border:0px">)" ) || |
1068 | str.startsWith(s: R"(</td><td style="border:0px">)" ) || |
1069 | str.simplified().startsWith(s: "</td></tr> </table></div>" ) || |
1070 | str.startsWith(s: R"(<br style="clear: both" />)" ) || |
1071 | str.startsWith(s: R"(<div style="float: left; margin-right: 2em">)" ) || |
1072 | str.startsWith(s: R"(<div style="float: right; margin-left: 2em">)" ) || |
1073 | str.startsWith(s: "</div>" ) || |
1074 | str.startsWith(s: "<span></span>" ) || |
1075 | str.simplified().startsWith(s: "</td></tr> </table></div>" ) || |
1076 | str.startsWith(s: R"(<br style="clear: both" />)" ) || |
1077 | // Other formatting, only for QMake. |
1078 | str == "<br />" ) { |
1079 | // Ignore this part, as it's only for formatting of images. |
1080 | hasRewrittenString = true; |
1081 | } else if (str.startsWith(s: R"(<div style="padding:10px;color:#fff;background)" ) && |
1082 | matchAhead(atom, expectedAtomType: Atom::String) && matchAhead(atom: atom->next(), expectedAtomType: Atom::RawString) && |
1083 | matchAhead(atom: atom->next()->next(), expectedAtomType: Atom::String) && |
1084 | matchAhead(atom: atom->next()->next()->next(), expectedAtomType: Atom::RawString) && |
1085 | matchAhead(atom: atom->next()->next()->next()->next(), expectedAtomType: Atom::String) && |
1086 | matchAhead(atom: atom->next()->next()->next()->next()->next(), expectedAtomType: Atom::RawString)) { |
1087 | hasRewrittenString = true; |
1088 | skipAhead += 6; |
1089 | |
1090 | const QString color = atom->next()->string(); // == atom->next()->next()->next()->string() |
1091 | const QString text = atom->next()->next()->next()->next()->next()->string(); |
1092 | |
1093 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "phrase" ); |
1094 | m_writer->writeAttribute(qualifiedName: "role" , value: "color:" + color); |
1095 | m_writer->writeCharacters(text: color); |
1096 | m_writer->writeCharacters(text: " " ); |
1097 | if (text.isEmpty()) |
1098 | m_writer->writeCharacters(text); |
1099 | else |
1100 | m_writer->writeCharacters(text: "          " ); |
1101 | m_writer->writeEndElement(); // phrase |
1102 | } |
1103 | // The following two cases handle some specificities of the documentation of Qt Quick |
1104 | // Controls 2. A small subset of pages is involved, as of Qt 6.4.0: |
1105 | // qtquickcontrols2-imagine, qtquickcontrols2-macos, qtquickcontrols2-material, |
1106 | // qtquickcontrols2-universal, qtquickcontrols2-windows. The string to rewrite looks like |
1107 | // the following, with XML comments to indicate the start of atoms: |
1108 | // <!--RawString--><table class="alignedsummary"><tbody><tr><td class="memItemLeft |
1109 | // rightAlign topAlign"> Import Statement:</td><td class="memItemRight bottomAlign"> |
1110 | // import |
1111 | // <!--String-->QtQuick.Controls.Imagine 2.12 |
1112 | // <!--RawString--></td></tr><tr><td class="memItemLeft rightAlign topAlign"> |
1113 | // Since:</td> <td class="memItemRight bottomAlign"> |
1114 | // <!--String-->Qt 5.10 |
1115 | // <!--RawString--></td></tr></tbody></table> |
1116 | // The structure being fixed, a rigid but simple solution is implemented: don't parse the |
1117 | // table, simply output the expected set of tags. An alternative would be to parse the HTML |
1118 | // table and to replicate the tags. The output is identical to |
1119 | // DocBookGenerator::generateQmlRequisites. |
1120 | else if ( |
1121 | str.startsWith( |
1122 | s: R"(<table class="alignedsummary"><tbody><tr><td class="memItemLeft rightAlign topAlign"> Import Statement:)" ) |
1123 | && matchAhead(atom, expectedAtomType: Atom::String) && matchAhead(atom: atom->next(), expectedAtomType: Atom::RawString) |
1124 | && matchAhead(atom: atom->next()->next(), expectedAtomType: Atom::String) |
1125 | && matchAhead(atom: atom->next()->next()->next(), expectedAtomType: Atom::RawString)) { |
1126 | m_rewritingCustomQmlModuleSummary = true; |
1127 | hasRewrittenString = true; |
1128 | |
1129 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "variablelist" ); |
1130 | newLine(); |
1131 | |
1132 | generateStartRequisite(description: "Import Statement" ); |
1133 | m_writer->writeCharacters(text: "import " ); |
1134 | } else if (m_rewritingCustomQmlModuleSummary) { |
1135 | if (str.startsWith( |
1136 | s: R"(</td></tr><tr><td class="memItemLeft rightAlign topAlign"> Since:)" )) { |
1137 | generateEndRequisite(); |
1138 | generateStartRequisite(description: "Since" ); |
1139 | |
1140 | hasRewrittenString = true; |
1141 | } else if (str.startsWith(s: R"(</td></tr></tbody></table>)" )) { |
1142 | m_rewritingCustomQmlModuleSummary = false; |
1143 | hasRewrittenString = true; |
1144 | |
1145 | generateEndRequisite(); |
1146 | m_writer->writeEndElement(); // variablelist |
1147 | newLine(); |
1148 | } |
1149 | } |
1150 | // Another idiosyncrasy for this module: |
1151 | // <!--RawString--><div class="qmlproto"><table class="qmlname"><tbody><tr valign="top" |
1152 | // class="odd" id=" |
1153 | // <!--String-->universal-accent-attached-prop |
1154 | // <!--RawString-->"><td class="tblQmlPropNode"><p><span class="name"> |
1155 | // <!--String-->Universal.accent |
1156 | // <!--RawString--></span> : <span class="type"> |
1157 | // <!--String-->color |
1158 | // <!--RawString--></span></p></td></tr></tbody></table></div> |
1159 | // Several variants of this template exist, with more than three String between the |
1160 | // RawString. They are defined in |
1161 | // qtdeclarative\src\quickcontrols2\doc\qtquickcontrols.qdocconf. |
1162 | else if ( |
1163 | str.startsWith( |
1164 | s: R"(<div class="qmlproto"><table class="qmlname"><tbody><tr valign="top" class="odd" id=")" ) |
1165 | && matchAhead(atom, expectedAtomType: Atom::String) && matchAhead(atom: atom->next(), expectedAtomType: Atom::RawString) |
1166 | && matchAhead(atom: atom->next()->next(), expectedAtomType: Atom::String) |
1167 | && matchAhead(atom: atom->next()->next()->next(), expectedAtomType: Atom::RawString)) { |
1168 | hasRewrittenString = true; |
1169 | m_hasSection = true; |
1170 | |
1171 | // Determine which case occurs (property or method). |
1172 | const bool isStyleProperty = atom->next()->next()->string().startsWith( |
1173 | s: R"("><td class="tblQmlPropNode"><p><span class="name">)" ); |
1174 | const bool isStyleMethod = |
1175 | !isStyleProperty; // atom->next()->next()->string().startsWith(R"("><td |
1176 | // class="tblQmlFuncNode"><p><span class="type">)") |
1177 | |
1178 | // Parse the sequence of atoms. |
1179 | const Atom *nextStringAtom = |
1180 | atom->next(); // Invariant: ->type() == Atom::String (except after parsing). |
1181 | const QString id = nextStringAtom->string(); |
1182 | skipAhead += 2; |
1183 | QString name; |
1184 | QString type; |
1185 | QString arg1; |
1186 | QString type1; |
1187 | QString arg2; |
1188 | QString type2; |
1189 | |
1190 | if (isStyleProperty) { |
1191 | nextStringAtom = nextStringAtom->next()->next(); |
1192 | name = nextStringAtom->string(); |
1193 | skipAhead += 2; |
1194 | |
1195 | nextStringAtom = nextStringAtom->next()->next(); |
1196 | type = nextStringAtom->string(); |
1197 | skipAhead += 2; |
1198 | } else if (isStyleMethod) { |
1199 | nextStringAtom = nextStringAtom->next()->next(); |
1200 | type = nextStringAtom->string(); |
1201 | skipAhead += 2; |
1202 | |
1203 | nextStringAtom = nextStringAtom->next()->next(); |
1204 | type = nextStringAtom->string(); |
1205 | skipAhead += 2; |
1206 | |
1207 | nextStringAtom = nextStringAtom->next()->next(); |
1208 | arg1 = nextStringAtom->string(); |
1209 | skipAhead += 2; |
1210 | |
1211 | nextStringAtom = nextStringAtom->next()->next(); |
1212 | type1 = nextStringAtom->string(); |
1213 | skipAhead += 2; |
1214 | |
1215 | if (matchAhead(atom: nextStringAtom, expectedAtomType: Atom::RawString) |
1216 | && matchAhead(atom: nextStringAtom->next(), expectedAtomType: Atom::String) |
1217 | && matchAhead(atom: nextStringAtom->next()->next(), expectedAtomType: Atom::RawString) |
1218 | && matchAhead(atom: nextStringAtom->next()->next()->next(), expectedAtomType: Atom::String) |
1219 | && matchAhead(atom: nextStringAtom->next()->next()->next()->next(), |
1220 | expectedAtomType: Atom::RawString)) { |
1221 | nextStringAtom = nextStringAtom->next()->next(); |
1222 | arg2 = nextStringAtom->string(); |
1223 | skipAhead += 2; |
1224 | |
1225 | nextStringAtom = nextStringAtom->next()->next(); |
1226 | type2 = nextStringAtom->string(); |
1227 | skipAhead += 2; |
1228 | } |
1229 | |
1230 | // For now (Qt 6.4.0), the macro is only defined up to two arguments: \stylemethod |
1231 | // and \stylemethod2. |
1232 | } |
1233 | |
1234 | // Write the corresponding DocBook. |
1235 | // This should be wrapped in a section, but there is no mechanism to check for |
1236 | // \endstyleproperty or \endstylemethod within qdoc (it must be done at the macro |
1237 | // level), hence the bridgehead. |
1238 | QString title; |
1239 | if (isStyleProperty) { |
1240 | title = name + " : " + type; |
1241 | } else if (isStyleMethod) { |
1242 | title = type + " " + name; |
1243 | } |
1244 | |
1245 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "bridgehead" ); |
1246 | m_writer->writeAttribute(qualifiedName: "renderas" , value: "sect2" ); |
1247 | writeXmlId(id); |
1248 | m_writer->writeCharacters(text: title); |
1249 | m_writer->writeEndElement(); // bridgehead |
1250 | newLine(); |
1251 | |
1252 | if (m_useDocBook52) { |
1253 | if (isStyleProperty) { |
1254 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "fieldsynopsis" ); |
1255 | |
1256 | m_writer->writeTextElement(namespaceUri: dbNamespace, name: "type" , text: type); |
1257 | newLine(); |
1258 | m_writer->writeTextElement(namespaceUri: dbNamespace, name: "varname" , text: name); |
1259 | newLine(); |
1260 | |
1261 | m_writer->writeEndElement(); // fieldsynopsis |
1262 | } else if (isStyleMethod) { |
1263 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "methodsynopsis" ); |
1264 | |
1265 | m_writer->writeTextElement(namespaceUri: dbNamespace, name: "type" , text: type); |
1266 | newLine(); |
1267 | m_writer->writeTextElement(namespaceUri: dbNamespace, name: "methodname" , text: name); |
1268 | newLine(); |
1269 | |
1270 | if (!arg1.isEmpty() && !type1.isEmpty()) { |
1271 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "methodparam" ); |
1272 | newLine(); |
1273 | m_writer->writeTextElement(namespaceUri: dbNamespace, name: "type" , text: type1); |
1274 | newLine(); |
1275 | m_writer->writeTextElement(namespaceUri: dbNamespace, name: "parameter" , text: arg1); |
1276 | newLine(); |
1277 | m_writer->writeEndElement(); // methodparam |
1278 | newLine(); |
1279 | } |
1280 | if (!arg2.isEmpty() && !type2.isEmpty()) { |
1281 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "methodparam" ); |
1282 | newLine(); |
1283 | m_writer->writeTextElement(namespaceUri: dbNamespace, name: "type" , text: type2); |
1284 | newLine(); |
1285 | m_writer->writeTextElement(namespaceUri: dbNamespace, name: "parameter" , text: arg2); |
1286 | newLine(); |
1287 | m_writer->writeEndElement(); // methodparam |
1288 | newLine(); |
1289 | } |
1290 | |
1291 | m_writer->writeEndElement(); // methodsynopsis |
1292 | } |
1293 | } |
1294 | } |
1295 | // This time, a specificity of Qt Virtual Keyboard to embed SVG images. Typically, there are |
1296 | // several images at once with the same encoding. |
1297 | else if ( |
1298 | str.startsWith( |
1299 | s: R"(<div align="center"><figure><svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg")" )) { |
1300 | const QStringList images = str.split(sep: "</div>" , behavior: Qt::SkipEmptyParts, cs: Qt::CaseInsensitive); |
1301 | |
1302 | for (const QString& image : images) { |
1303 | // Find the caption. |
1304 | const QStringList parts = image.split(sep: "</svg>" ); |
1305 | const QString svgImage = "<svg" + parts[0].split(sep: "<svg" )[1] + "</svg>" ; |
1306 | const QString caption = parts[1].split(sep: "<figcaption>" )[1].split(sep: "</figcaption>" )[0]; |
1307 | |
1308 | // Output the DocBook equivalent. |
1309 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "figure" ); |
1310 | newLine(); |
1311 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "title" ); |
1312 | m_writer->writeCharacters(text: caption); |
1313 | m_writer->writeEndElement(); // title |
1314 | newLine(); |
1315 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "mediaobject" ); |
1316 | newLine(); |
1317 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "imageobject" ); |
1318 | newLine(); |
1319 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "imagedata" ); |
1320 | newLine(); |
1321 | m_writer->device()->write(data: svgImage.toUtf8()); // SVG image as raw XML. |
1322 | m_writer->writeEndElement(); // imagedata |
1323 | newLine(); |
1324 | m_writer->writeEndElement(); // imageobject |
1325 | newLine(); |
1326 | m_writer->writeEndElement(); // mediaobject |
1327 | newLine(); |
1328 | m_writer->writeEndElement(); // figure |
1329 | newLine(); |
1330 | } |
1331 | |
1332 | hasRewrittenString = true; |
1333 | } |
1334 | // This time, a specificity of Qt Virtual Keyboard to embed SVG images. Typically, there are |
1335 | // several images at once with the same encoding. |
1336 | else if ( |
1337 | str.startsWith( |
1338 | s: R"(<div align="center"><figure><svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg")" )) { |
1339 | const QStringList images = str.split(sep: "</div>" , behavior: Qt::SkipEmptyParts, cs: Qt::CaseInsensitive); |
1340 | |
1341 | for (const QString &image : images) { |
1342 | // Find the caption. |
1343 | const QStringList parts = image.split(sep: "</svg>" ); |
1344 | const QString svgImage = "<svg" + parts[0].split(sep: "<svg" )[1] + "</svg>" ; |
1345 | const QString caption = parts[1].split(sep: "<figcaption>" )[1].split(sep: "</figcaption>" )[0]; |
1346 | |
1347 | // Output the DocBook equivalent. |
1348 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "figure" ); |
1349 | newLine(); |
1350 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "title" ); |
1351 | m_writer->writeCharacters(text: caption); |
1352 | m_writer->writeEndElement(); // title |
1353 | newLine(); |
1354 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "mediaobject" ); |
1355 | newLine(); |
1356 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "imageobject" ); |
1357 | newLine(); |
1358 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "imagedata" ); |
1359 | newLine(); |
1360 | m_writer->device()->write(data: svgImage.toUtf8()); // SVG image as raw XML. |
1361 | m_writer->writeEndElement(); // imagedata |
1362 | newLine(); |
1363 | m_writer->writeEndElement(); // imageobject |
1364 | newLine(); |
1365 | m_writer->writeEndElement(); // mediaobject |
1366 | newLine(); |
1367 | m_writer->writeEndElement(); // figure |
1368 | newLine(); |
1369 | } |
1370 | |
1371 | hasRewrittenString = true; |
1372 | } |
1373 | // For ActiveQt, there is some raw HTML that has no meaningful |
1374 | // translation into DocBook. |
1375 | else if (str.trimmed().toLower().startsWith(s: R"(<script language="javascript">)" ) |
1376 | || str.trimmed().toLower().startsWith(s: R"(<script language="vbscript">)" ) |
1377 | || str.trimmed().toLower().startsWith(s: "<object id=" )) { |
1378 | writeRawHtml(rawCode: str); |
1379 | hasRewrittenString = true; |
1380 | } |
1381 | // Raw HTML encoding of some tables. Perform some basic soundness |
1382 | // checks to ensure the conversion has some chance of success. |
1383 | else if (str.startsWith(s: "<table " ) && |
1384 | str.count(s: "<tr" ) == str.count(s: "</tr" ) && |
1385 | str.count(s: "<td" ) == str.count(s: "</td" ) && |
1386 | str.count(s: "<tr" ) > 0 && str.count(s: "</td" ) > 0) { |
1387 | QStringList tables = str.split(sep: "</table>" , behavior: Qt::SkipEmptyParts); |
1388 | for (QString table : tables) { |
1389 | // Several changes: |
1390 | // - name spaces for each element |
1391 | // - use of informaltable (no caption) instead of table |
1392 | // - DocBook-compliant encoding of the background cell color. |
1393 | // In case the background color is given by value instead of |
1394 | // name (like "#d0d0d0" instead of "gray"), remove the sharp |
1395 | // so that the output class is still allowed by CSS. |
1396 | // - use of emphasis instead of HTML b or i, DocBook link |
1397 | // instead of HTML a, DocBook para instead of HTML p |
1398 | // - removal of the "nowrap" and "align" attributes (no DocBook |
1399 | // encoding), correction of rowspan and colspan attributes |
1400 | // - adding a </tbody> in case it is opened and never closed |
1401 | // - encoding of images and titles (as bridgeheads) |
1402 | // - remove line feeds |
1403 | table = table.replace(before: "</" , after: "</db:" ); |
1404 | table = table.replace(before: "<" , after: "<db:" ); |
1405 | table = table.replace(before: "<db:/db:" , after: "</db:" ); |
1406 | |
1407 | table = table.replace(before: "<db:table" , after: "<db:informaltable" ); |
1408 | |
1409 | table = table.replace(before: "<db:b>" , after: R"(<db:emphasis role="bold">)" ); |
1410 | table = table.replace(before: "</db:b>" , after: "</db:emphasis>" ); |
1411 | table = table.replace(before: "<db:i>" , after: "<db:emphasis>" ); |
1412 | table = table.replace(before: "</db:i>" , after: "</db:emphasis>" ); |
1413 | |
1414 | table = table.replace(before: "<db:a href=" , after: "<db:link xlink:href=" ); |
1415 | table = table.replace(before: "</db:a>" , after: "</db:link>" ); |
1416 | |
1417 | table = table.replace(before: "<db:p>" , after: "<db:para>" ); |
1418 | table = table.replace(before: "</db:p>" , after: "</db:para>" ); |
1419 | |
1420 | table = table.replace(before: "<db:br />" , after: QString()); |
1421 | table = table.replace(before: "<db:br/>" , after: QString()); |
1422 | |
1423 | static const QRegularExpression re1(R"regex(<db:h(\d).*)>(.*)</db:h(\d)>)regex" ); |
1424 | table.replace(re: re1, |
1425 | after: R"xml(<db:bridgehead renderas="sect\1">\2</bridgehead>)xml" ); |
1426 | // Expecting \1 == \3. |
1427 | |
1428 | table = table.replace(before: R"( nowrap="nowrap")" , after: QString()); |
1429 | table = table.replace(before: R"( align="center")" , after: QString()); |
1430 | static const QRegularExpression re2(R"regex((row|col)span="\s+(.*)")regex" ); |
1431 | table.replace(re: re2, |
1432 | after: R"(\1span="\2")" ); |
1433 | |
1434 | static const QRegularExpression re3(R"regex(<db:td (.*)bgcolor="#(.*)"(.*)>(.*)</db:td>)regex" ); |
1435 | table.replace(re: re3, |
1436 | after: R"xml(<db:td \1 class="bgcolor-\2" \3><?dbhtml bgcolor="\2" ?><?dbfo bgcolor="\2" ?>\4</db:td>)xml" ); |
1437 | static const QRegularExpression re4(R"regex(<db:td (.*)bgcolor="(.*)"(.*)>(.*)</db:td>)regex" ); |
1438 | table.replace(re: re4, |
1439 | after: R"xml(<db:td \1 class="bgcolor-\2" \3><?dbhtml bgcolor="\2" ?><?dbfo bgcolor="\2" ?>\4</db:td>)xml" ); |
1440 | static const QRegularExpression re5(R"regex(<db:tr (.*)bgcolor="#(.*)"(.*)>)regex" ); |
1441 | table.replace(re: re5, |
1442 | after: R"xml(<db:tr \1 class="bgcolor-\2" \3><?dbhtml bgcolor="\2" ?><?dbfo bgcolor="\2" ?>)xml" ); |
1443 | static const QRegularExpression re6(R"regex(<db:tr (.*)bgcolor="(.*)"(.*)>³)regex" ); |
1444 | table.replace(re: re6, |
1445 | after: R"xml(<db:tr \1 class="bgcolor-\2" \3><?dbhtml bgcolor="\2" ?><?dbfo bgcolor="\2" ?>)xml" ); |
1446 | |
1447 | static const QRegularExpression re7(R"regex(<db:img src="(.*)" alt="(.*)"\s*/>)regex" ); |
1448 | table.replace(re: re7, |
1449 | after: R"xml(<db:figure> |
1450 | <db:title>\2</db:title> |
1451 | <db:mediaobject> |
1452 | <db:imageobject> |
1453 | <db:imagedata fileref="\1"/> |
1454 | </db:imageobject> |
1455 | </db:mediaobject> |
1456 | </db:figure>)xml" ); |
1457 | |
1458 | m_writer->device()->write(data: table.toUtf8()); |
1459 | |
1460 | // Finalize the table by writing the end tags. |
1461 | // m_writer->writeEndElement cannot be used, as the opening |
1462 | // tags are output directly through m_writer->device(). |
1463 | if (table.contains(s: "<db:tbody" ) && !table.contains(s: "</db:tbody" )) { |
1464 | m_writer->device()->write(data: "</db:tbody>\n" ); |
1465 | } |
1466 | m_writer->device()->write(data: "</db:informaltable>\n" ); |
1467 | } |
1468 | |
1469 | hasRewrittenString = true; |
1470 | } |
1471 | |
1472 | // No rewriting worked: for blockquotes, this is likely a qdoc example. |
1473 | // Use some programlisting to encode this raw HTML. |
1474 | if (!hasRewrittenString && m_inBlockquote) { |
1475 | writeRawHtml(rawCode: atom->string()); |
1476 | hasRewrittenString = true; |
1477 | } else { |
1478 | // Deal with some HTML entities to convert into XML. |
1479 | // This implementation complements the entities in qtbase\doc\global\macros.qdocconf, |
1480 | // because this code focuses on the RawString atom, while the configuration only works |
1481 | // for macros that generate HTML/XML entities. |
1482 | auto end = entitiesMapping.keyEnd(); |
1483 | for (auto it = entitiesMapping.keyBegin(); it != end; ++it) { |
1484 | if (const QString &entity = *it; str.startsWith(s: entity)) { |
1485 | QString rewrittenString = str; |
1486 | rewrittenString.replace(before: entity, after: entitiesMapping.value(key: entity)); |
1487 | |
1488 | m_writer->device()->write(data: rewrittenString.toUtf8()); |
1489 | |
1490 | hasRewrittenString = true; |
1491 | } |
1492 | } |
1493 | } |
1494 | |
1495 | // The RawString may be a macro specialized for DocBook, in which case no escaping is expected. |
1496 | // QXmlStreamWriter always write UTF-8 contents. |
1497 | if (!hasRewrittenString) |
1498 | m_writer->device()->write(data: atom->string().toUtf8()); // str has been trimmed. |
1499 | } |
1500 | break; |
1501 | case Atom::SectionLeft: |
1502 | m_hasSection = true; |
1503 | |
1504 | currentSectionLevel = atom->string().toInt() + hOffset(node: relative); |
1505 | // Level 1 is dealt with at the header level (info tag). |
1506 | if (currentSectionLevel > 1) { |
1507 | // Unfortunately, SectionRight corresponds to the end of any section, |
1508 | // i.e. going to a new section, even deeper. |
1509 | while (!sectionLevels.empty() && sectionLevels.top() >= currentSectionLevel) { |
1510 | sectionLevels.pop(); |
1511 | m_writer->writeEndElement(); // section |
1512 | newLine(); |
1513 | } |
1514 | |
1515 | sectionLevels.push(t: currentSectionLevel); |
1516 | |
1517 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "section" ); |
1518 | writeXmlId(id: Utilities::asAsciiPrintable(name: Text::sectionHeading(sectionBegin: atom).toString())); |
1519 | newLine(); |
1520 | // Unlike startSectionBegin, don't start a title here. |
1521 | } |
1522 | |
1523 | if (matchAhead(atom, expectedAtomType: Atom::SectionHeadingLeft) && |
1524 | matchAhead(atom: atom->next(), expectedAtomType: Atom::String) && |
1525 | matchAhead(atom: atom->next()->next(), expectedAtomType: Atom::SectionHeadingRight) && |
1526 | matchAhead(atom: atom->next()->next()->next(), expectedAtomType: Atom::SectionRight) && |
1527 | !atom->next()->next()->next()->next()->next()) { |
1528 | // A lonely section at the end of the document indicates that a |
1529 | // generated list of some sort should be within this section. |
1530 | // Close this section later on, in generateFooter(). |
1531 | generateAtom(atom: atom->next(), relative); |
1532 | generateAtom(atom: atom->next()->next(), relative); |
1533 | generateAtom(atom: atom->next()->next()->next(), relative); |
1534 | |
1535 | m_closeSectionAfterGeneratedList = true; |
1536 | skipAhead += 4; |
1537 | sectionLevels.pop(); |
1538 | } |
1539 | |
1540 | if (!matchAhead(atom, expectedAtomType: Atom::SectionHeadingLeft)) { |
1541 | // No section title afterwards, make one up. This likely indicates a problem in the original documentation. |
1542 | m_writer->writeTextElement(namespaceUri: dbNamespace, name: "title" , text: "" ); |
1543 | } |
1544 | break; |
1545 | case Atom::SectionRight: |
1546 | // All the logic about closing sections is done in the SectionLeft case |
1547 | // and generateFooter() for the end of the page. |
1548 | break; |
1549 | case Atom::SectionHeadingLeft: |
1550 | // Level 1 is dealt with at the header level (info tag). |
1551 | if (currentSectionLevel > 1) { |
1552 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "title" ); |
1553 | m_inSectionHeading = true; |
1554 | } |
1555 | break; |
1556 | case Atom::SectionHeadingRight: |
1557 | // Level 1 is dealt with at the header level (info tag). |
1558 | if (currentSectionLevel > 1) { |
1559 | m_writer->writeEndElement(); // title |
1560 | newLine(); |
1561 | m_inSectionHeading = false; |
1562 | } |
1563 | break; |
1564 | case Atom::SidebarLeft: |
1565 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "sidebar" ); |
1566 | break; |
1567 | case Atom::SidebarRight: |
1568 | m_writer->writeEndElement(); // sidebar |
1569 | newLine(); |
1570 | break; |
1571 | case Atom::String: |
1572 | if (m_inLink && !m_inContents && !m_inSectionHeading) |
1573 | generateLink(atom); |
1574 | else |
1575 | m_writer->writeCharacters(text: atom->string()); |
1576 | break; |
1577 | case Atom::TableLeft: { |
1578 | std::pair<QString, QString> pair = getTableWidthAttr(atom); |
1579 | QString attr = pair.second; |
1580 | QString width = pair.first; |
1581 | |
1582 | if (m_inPara) { |
1583 | m_writer->writeEndElement(); // para or blockquote |
1584 | newLine(); |
1585 | m_inPara = false; |
1586 | } |
1587 | |
1588 | m_tableHeaderAlreadyOutput = false; |
1589 | |
1590 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "informaltable" ); |
1591 | m_writer->writeAttribute(qualifiedName: "style" , value: attr); |
1592 | if (!width.isEmpty()) |
1593 | m_writer->writeAttribute(qualifiedName: "width" , value: width); |
1594 | newLine(); |
1595 | } break; |
1596 | case Atom::TableRight: |
1597 | m_tableWidthAttr = {"" , "" }; |
1598 | m_writer->writeEndElement(); // table |
1599 | newLine(); |
1600 | break; |
1601 | case Atom::TableHeaderLeft: { |
1602 | if (matchAhead(atom, expectedAtomType: Atom::TableHeaderRight)) { |
1603 | ++skipAhead; |
1604 | break; |
1605 | } |
1606 | |
1607 | if (m_tableHeaderAlreadyOutput) { |
1608 | // Headers are only allowed at the beginning of the table: close |
1609 | // the table and reopen one. |
1610 | m_writer->writeEndElement(); // table |
1611 | newLine(); |
1612 | |
1613 | const QString &attr = m_tableWidthAttr.second; |
1614 | const QString &width = m_tableWidthAttr.first; |
1615 | |
1616 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "informaltable" ); |
1617 | m_writer->writeAttribute(qualifiedName: "style" , value: attr); |
1618 | if (!width.isEmpty()) |
1619 | m_writer->writeAttribute(qualifiedName: "width" , value: width); |
1620 | newLine(); |
1621 | } else { |
1622 | m_tableHeaderAlreadyOutput = true; |
1623 | } |
1624 | |
1625 | const Atom *next = atom->next(); |
1626 | QString id{"" }; |
1627 | if (matchAhead(atom, expectedAtomType: Atom::Target)) { |
1628 | id = Utilities::asAsciiPrintable(name: next->string()); |
1629 | next = next->next(); |
1630 | ++skipAhead; |
1631 | } |
1632 | |
1633 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "thead" ); |
1634 | newLine(); |
1635 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "tr" ); |
1636 | writeXmlId(id); |
1637 | newLine(); |
1638 | m_inTableHeader = true; |
1639 | |
1640 | if (!matchAhead(atom, expectedAtomType: Atom::TableItemLeft)) { |
1641 | m_closeTableCell = true; |
1642 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "td" ); |
1643 | newLine(); |
1644 | } |
1645 | } |
1646 | break; |
1647 | case Atom::TableHeaderRight: |
1648 | if (m_closeTableCell) { |
1649 | m_closeTableCell = false; |
1650 | m_writer->writeEndElement(); // td |
1651 | newLine(); |
1652 | } |
1653 | |
1654 | m_writer->writeEndElement(); // tr |
1655 | newLine(); |
1656 | if (matchAhead(atom, expectedAtomType: Atom::TableHeaderLeft)) { |
1657 | skipAhead = 1; |
1658 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "tr" ); |
1659 | newLine(); |
1660 | } else { |
1661 | m_writer->writeEndElement(); // thead |
1662 | newLine(); |
1663 | m_inTableHeader = false; |
1664 | } |
1665 | break; |
1666 | case Atom::TableRowLeft: { |
1667 | if (matchAhead(atom, expectedAtomType: Atom::TableRowRight)) { |
1668 | skipAhead = 1; |
1669 | break; |
1670 | } |
1671 | |
1672 | QString id{"" }; |
1673 | bool hasTarget {false}; |
1674 | if (matchAhead(atom, expectedAtomType: Atom::Target)) { |
1675 | id = Utilities::asAsciiPrintable(name: atom->next()->string()); |
1676 | ++skipAhead; |
1677 | hasTarget = true; |
1678 | } |
1679 | |
1680 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "tr" ); |
1681 | writeXmlId(id); |
1682 | |
1683 | if (atom->string().isEmpty()) { |
1684 | m_writer->writeAttribute(qualifiedName: "valign" , value: "top" ); |
1685 | } else { |
1686 | // Basic parsing of attributes, should be enough. The input string (atom->string()) |
1687 | // looks like: |
1688 | // arg1="val1" arg2="val2" |
1689 | QStringList args = atom->string().split(sep: "\"" , behavior: Qt::SkipEmptyParts); |
1690 | // arg1=, val1, arg2=, val2, |
1691 | // \-- 1st --/ \-- 2nd --/ \-- remainder |
1692 | const int nArgs = args.size(); |
1693 | |
1694 | if (nArgs % 2) { |
1695 | // Problem... |
1696 | relative->doc().location().warning( |
1697 | QStringLiteral("Error when parsing attributes for the table: got \"%1\"" ) |
1698 | .arg(a: atom->string())); |
1699 | } |
1700 | for (int i = 0; i + 1 < nArgs; i += 2) { |
1701 | // args.at(i): name of the attribute being set. |
1702 | // args.at(i + 1): value of the said attribute. |
1703 | const QString &attr = args.at(i).chopped(n: 1); |
1704 | if (attr == "id" ) { // Too bad if there is an anchor later on |
1705 | // (currently never happens). |
1706 | writeXmlId(id: args.at(i: i + 1)); |
1707 | } else { |
1708 | m_writer->writeAttribute(qualifiedName: attr, value: args.at(i: i + 1)); |
1709 | } |
1710 | } |
1711 | } |
1712 | newLine(); |
1713 | |
1714 | // If there is nothing in this row, close it right now. There might be keywords before the row contents. |
1715 | bool isRowEmpty = hasTarget ? !matchAhead(atom: atom->next(), expectedAtomType: Atom::TableItemLeft) : !matchAhead(atom, expectedAtomType: Atom::TableItemLeft); |
1716 | if (isRowEmpty && matchAhead(atom, expectedAtomType: Atom::Keyword)) { |
1717 | const Atom* next = atom->next(); |
1718 | while (matchAhead(atom: next, expectedAtomType: Atom::Keyword)) |
1719 | next = next->next(); |
1720 | isRowEmpty = !matchAhead(atom: next, expectedAtomType: Atom::TableItemLeft); |
1721 | } |
1722 | |
1723 | if (isRowEmpty) { |
1724 | m_closeTableRow = true; |
1725 | m_writer->writeEndElement(); // td |
1726 | newLine(); |
1727 | } |
1728 | } |
1729 | break; |
1730 | case Atom::TableRowRight: |
1731 | if (m_closeTableRow) { |
1732 | m_closeTableRow = false; |
1733 | m_writer->writeEndElement(); // td |
1734 | newLine(); |
1735 | } |
1736 | |
1737 | m_writer->writeEndElement(); // tr |
1738 | newLine(); |
1739 | break; |
1740 | case Atom::TableItemLeft: |
1741 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: m_inTableHeader ? "th" : "td" ); |
1742 | |
1743 | for (int i = 0; i < atom->count(); ++i) { |
1744 | const QString &p = atom->string(i); |
1745 | if (p.contains(c: '=')) { |
1746 | QStringList lp = p.split(sep: QLatin1Char('=')); |
1747 | m_writer->writeAttribute(qualifiedName: lp.at(i: 0), value: lp.at(i: 1)); |
1748 | } else { |
1749 | QStringList spans = p.split(sep: QLatin1Char(',')); |
1750 | if (spans.size() == 2) { |
1751 | if (spans.at(i: 0) != "1" ) |
1752 | m_writer->writeAttribute(qualifiedName: "colspan" , value: spans.at(i: 0).trimmed()); |
1753 | if (spans.at(i: 1) != "1" ) |
1754 | m_writer->writeAttribute(qualifiedName: "rowspan" , value: spans.at(i: 1).trimmed()); |
1755 | } |
1756 | } |
1757 | } |
1758 | newLine(); |
1759 | // No skipahead, as opposed to HTML: in DocBook, the text must be wrapped in paragraphs. |
1760 | break; |
1761 | case Atom::TableItemRight: |
1762 | m_writer->writeEndElement(); // th if m_inTableHeader, otherwise td |
1763 | newLine(); |
1764 | break; |
1765 | case Atom::TableOfContents: |
1766 | Q_FALLTHROUGH(); |
1767 | case Atom::Keyword: |
1768 | break; |
1769 | case Atom::Target: |
1770 | // Sometimes, there is a \target just before a section title with the same ID. Only outut one xml:id. |
1771 | if (matchAhead(atom, expectedAtomType: Atom::SectionRight) && matchAhead(atom: atom->next(), expectedAtomType: Atom::SectionLeft)) { |
1772 | QString nextId = Utilities::asAsciiPrintable( |
1773 | name: Text::sectionHeading(sectionBegin: atom->next()->next()).toString()); |
1774 | QString ownId = Utilities::asAsciiPrintable(name: atom->string()); |
1775 | if (nextId == ownId) |
1776 | break; |
1777 | } |
1778 | |
1779 | writeAnchor(id: Utilities::asAsciiPrintable(name: atom->string())); |
1780 | break; |
1781 | case Atom::UnhandledFormat: |
1782 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "emphasis" ); |
1783 | m_writer->writeAttribute(qualifiedName: "role" , value: "bold" ); |
1784 | m_writer->writeCharacters(text: "<Missing DocBook>" ); |
1785 | m_writer->writeEndElement(); // emphasis |
1786 | break; |
1787 | case Atom::UnknownCommand: |
1788 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "emphasis" ); |
1789 | m_writer->writeAttribute(qualifiedName: "role" , value: "bold" ); |
1790 | if (m_useITS) |
1791 | m_writer->writeAttribute(namespaceUri: itsNamespace, name: "translate" , value: "no" ); |
1792 | m_writer->writeCharacters(text: "<Unknown command>" ); |
1793 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "code" ); |
1794 | m_writer->writeCharacters(text: atom->string()); |
1795 | m_writer->writeEndElement(); // code |
1796 | m_writer->writeEndElement(); // emphasis |
1797 | break; |
1798 | case Atom::CodeQuoteArgument: |
1799 | case Atom::CodeQuoteCommand: |
1800 | case Atom::SnippetCommand: |
1801 | case Atom::SnippetIdentifier: |
1802 | case Atom::SnippetLocation: |
1803 | // No output (ignore). |
1804 | break; |
1805 | default: |
1806 | unknownAtom(atom); |
1807 | } |
1808 | return skipAhead; |
1809 | } |
1810 | |
1811 | void DocBookGenerator::generateClassHierarchy(const Node *relative, NodeMultiMap &classMap) |
1812 | { |
1813 | // From HtmlGenerator::generateClassHierarchy. |
1814 | if (classMap.isEmpty()) |
1815 | return; |
1816 | |
1817 | std::function<void(ClassNode *)> generateClassAndChildren |
1818 | = [this, &relative, &generateClassAndChildren](ClassNode * classe) { |
1819 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "listitem" ); |
1820 | newLine(); |
1821 | |
1822 | // This class. |
1823 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "para" ); |
1824 | generateFullName(node: classe, relative); |
1825 | m_writer->writeEndElement(); // para |
1826 | newLine(); |
1827 | |
1828 | // Children, if any. |
1829 | bool hasChild = false; |
1830 | for (const RelatedClass &relatedClass : classe->derivedClasses()) { |
1831 | if (relatedClass.m_node && relatedClass.m_node->isInAPI()) { |
1832 | hasChild = true; |
1833 | break; |
1834 | } |
1835 | } |
1836 | |
1837 | if (hasChild) { |
1838 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "itemizedlist" ); |
1839 | newLine(); |
1840 | |
1841 | for (const RelatedClass &relatedClass: classe->derivedClasses()) { |
1842 | if (relatedClass.m_node && relatedClass.m_node->isInAPI()) { |
1843 | generateClassAndChildren(relatedClass.m_node); |
1844 | } |
1845 | } |
1846 | |
1847 | m_writer->writeEndElement(); // itemizedlist |
1848 | newLine(); |
1849 | } |
1850 | |
1851 | // End this class. |
1852 | m_writer->writeEndElement(); // listitem |
1853 | newLine(); |
1854 | }; |
1855 | |
1856 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "itemizedlist" ); |
1857 | newLine(); |
1858 | |
1859 | for (const auto &it : classMap) { |
1860 | auto *classe = static_cast<ClassNode *>(it); |
1861 | if (classe->baseClasses().isEmpty()) |
1862 | generateClassAndChildren(classe); |
1863 | } |
1864 | |
1865 | m_writer->writeEndElement(); // itemizedlist |
1866 | newLine(); |
1867 | } |
1868 | |
1869 | void DocBookGenerator::generateLink(const Atom *atom) |
1870 | { |
1871 | Q_ASSERT(m_inLink); |
1872 | |
1873 | // From HtmlGenerator::generateLink. |
1874 | if (m_linkNode && m_linkNode->isFunction()) { |
1875 | auto match = XmlGenerator::m_funcLeftParen.match(subject: atom->string()); |
1876 | if (match.hasMatch()) { |
1877 | // C++: move () outside of link |
1878 | qsizetype leftParenLoc = match.capturedStart(nth: 1); |
1879 | m_writer->writeCharacters(text: atom->string().left(n: leftParenLoc)); |
1880 | endLink(); |
1881 | m_writer->writeCharacters(text: atom->string().mid(position: leftParenLoc)); |
1882 | return; |
1883 | } |
1884 | } |
1885 | m_writer->writeCharacters(text: atom->string()); |
1886 | } |
1887 | |
1888 | /*! |
1889 | This version of the function is called when the \a link is known |
1890 | to be correct. |
1891 | */ |
1892 | void DocBookGenerator::beginLink(const QString &link, const Node *node, const Node *relative) |
1893 | { |
1894 | // From HtmlGenerator::beginLink. |
1895 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "link" ); |
1896 | m_writer->writeAttribute(namespaceUri: xlinkNamespace, name: "href" , value: link); |
1897 | if (node && !(relative && node->status() == relative->status()) |
1898 | && node->isDeprecated()) |
1899 | m_writer->writeAttribute(qualifiedName: "role" , value: "deprecated" ); |
1900 | m_inLink = true; |
1901 | m_linkNode = node; |
1902 | } |
1903 | |
1904 | void DocBookGenerator::endLink() |
1905 | { |
1906 | // From HtmlGenerator::endLink. |
1907 | if (m_inLink) |
1908 | m_writer->writeEndElement(); // link |
1909 | m_inLink = false; |
1910 | m_linkNode = nullptr; |
1911 | } |
1912 | |
1913 | void DocBookGenerator::generateList(const Node *relative, const QString &selector) |
1914 | { |
1915 | // From HtmlGenerator::generateList, without warnings, changing prototype. |
1916 | CNMap cnm; |
1917 | Node::NodeType type = Node::NoType; |
1918 | if (selector == QLatin1String("overviews" )) |
1919 | type = Node::Group; |
1920 | else if (selector == QLatin1String("cpp-modules" )) |
1921 | type = Node::Module; |
1922 | else if (selector == QLatin1String("qml-modules" )) |
1923 | type = Node::QmlModule; |
1924 | |
1925 | if (type != Node::NoType) { |
1926 | NodeList nodeList; |
1927 | m_qdb->mergeCollections(type, cnm, relative); |
1928 | const QList<CollectionNode *> collectionList = cnm.values(); |
1929 | nodeList.reserve(asize: collectionList.size()); |
1930 | for (auto *collectionNode : collectionList) |
1931 | nodeList.append(t: collectionNode); |
1932 | generateAnnotatedList(relative, nodeList, selector); |
1933 | } else { |
1934 | /* |
1935 | \generatelist {selector} is only allowed in a comment where |
1936 | the topic is \group, \module, or \qmlmodule. |
1937 | */ |
1938 | Node *n = const_cast<Node *>(relative); |
1939 | auto *cn = static_cast<CollectionNode *>(n); |
1940 | m_qdb->mergeCollections(c: cn); |
1941 | generateAnnotatedList(relative: cn, nodeList: cn->members(), selector); |
1942 | } |
1943 | } |
1944 | |
1945 | /*! |
1946 | Outputs an annotated list of the nodes in \a nodeList. |
1947 | A two-column table is output. |
1948 | */ |
1949 | void DocBookGenerator::generateAnnotatedList(const Node *relative, const NodeList &nodeList, |
1950 | const QString &selector, GeneratedListType type) |
1951 | { |
1952 | if (nodeList.isEmpty()) |
1953 | return; |
1954 | |
1955 | // Do nothing if all items are internal or obsolete. |
1956 | if (std::all_of(first: nodeList.cbegin(), last: nodeList.cend(), pred: [](const Node *n) { |
1957 | return n->isInternal() || n->isDeprecated(); })) { |
1958 | return; |
1959 | } |
1960 | |
1961 | // Detect if there is a need for a variablelist (i.e. titles mapped to |
1962 | // descriptions) or a regular itemizedlist (only titles). |
1963 | bool noItemsHaveTitle = |
1964 | type == ItemizedList || std::all_of(first: nodeList.begin(), last: nodeList.end(), |
1965 | pred: [](const Node* node) { |
1966 | return node->doc().briefText().toString().isEmpty(); |
1967 | }); |
1968 | |
1969 | // Wrap the list in a section if needed. |
1970 | if (type == AutoSection && m_hasSection) |
1971 | startSection(id: "" , title: "Contents" ); |
1972 | |
1973 | // From WebXMLGenerator::generateAnnotatedList. |
1974 | if (!nodeList.isEmpty()) { |
1975 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "variablelist" ); |
1976 | m_writer->writeAttribute(qualifiedName: "role" , value: selector); |
1977 | newLine(); |
1978 | |
1979 | NodeList members{nodeList}; |
1980 | std::sort(first: members.begin(), last: members.end(), comp: Node::nodeNameLessThan); |
1981 | for (const auto &node : std::as_const(t&: members)) { |
1982 | if (node->isInternal() || node->isDeprecated()) |
1983 | continue; |
1984 | |
1985 | if (noItemsHaveTitle) { |
1986 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "listitem" ); |
1987 | newLine(); |
1988 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "para" ); |
1989 | } else { |
1990 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "varlistentry" ); |
1991 | newLine(); |
1992 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "term" ); |
1993 | } |
1994 | generateFullName(node, relative); |
1995 | if (noItemsHaveTitle) { |
1996 | m_writer->writeEndElement(); // para |
1997 | newLine(); |
1998 | m_writer->writeEndElement(); // listitem |
1999 | } else { |
2000 | m_writer->writeEndElement(); // term |
2001 | newLine(); |
2002 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "listitem" ); |
2003 | newLine(); |
2004 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "para" ); |
2005 | m_writer->writeCharacters(text: node->doc().briefText().toString()); |
2006 | m_writer->writeEndElement(); // para |
2007 | newLine(); |
2008 | m_writer->writeEndElement(); // listitem |
2009 | newLine(); |
2010 | m_writer->writeEndElement(); // varlistentry |
2011 | } |
2012 | newLine(); |
2013 | } |
2014 | |
2015 | m_writer->writeEndElement(); // itemizedlist or variablelist |
2016 | newLine(); |
2017 | } |
2018 | |
2019 | if (type == AutoSection && m_hasSection) |
2020 | endSection(); |
2021 | } |
2022 | |
2023 | /*! |
2024 | Outputs a series of annotated lists from the nodes in \a nmm, |
2025 | divided into sections based by the key names in the multimap. |
2026 | */ |
2027 | void DocBookGenerator::generateAnnotatedLists(const Node *relative, const NodeMultiMap &nmm, |
2028 | const QString &selector) |
2029 | { |
2030 | // From HtmlGenerator::generateAnnotatedLists. |
2031 | for (const QString &name : nmm.uniqueKeys()) { |
2032 | if (!name.isEmpty()) |
2033 | startSection(id: name.toLower(), title: name); |
2034 | generateAnnotatedList(relative, nodeList: nmm.values(key: name), selector); |
2035 | if (!name.isEmpty()) |
2036 | endSection(); |
2037 | } |
2038 | } |
2039 | |
2040 | /*! |
2041 | This function finds the common prefix of the names of all |
2042 | the classes in the class map \a nmm and then generates a |
2043 | compact list of the class names alphabetized on the part |
2044 | of the name not including the common prefix. You can tell |
2045 | the function to use \a comonPrefix as the common prefix, |
2046 | but normally you let it figure it out itself by looking at |
2047 | the name of the first and last classes in the class map |
2048 | \a nmm. |
2049 | */ |
2050 | void DocBookGenerator::generateCompactList(const Node *relative, const NodeMultiMap &nmm, |
2051 | bool includeAlphabet, const QString &commonPrefix, |
2052 | const QString &selector) |
2053 | { |
2054 | // From HtmlGenerator::generateCompactList. No more "includeAlphabet", this should be handled by |
2055 | // the DocBook toolchain afterwards. |
2056 | // TODO: In DocBook, probably no need for this method: this is purely presentational, i.e. to be |
2057 | // fully handled by the DocBook toolchain. |
2058 | |
2059 | if (nmm.isEmpty()) |
2060 | return; |
2061 | |
2062 | const int NumParagraphs = 37; // '0' to '9', 'A' to 'Z', '_' |
2063 | qsizetype commonPrefixLen = commonPrefix.size(); |
2064 | |
2065 | /* |
2066 | Divide the data into 37 paragraphs: 0, ..., 9, A, ..., Z, |
2067 | underscore (_). QAccel will fall in paragraph 10 (A) and |
2068 | QXtWidget in paragraph 33 (X). This is the only place where we |
2069 | assume that NumParagraphs is 37. Each paragraph is a NodeMultiMap. |
2070 | */ |
2071 | NodeMultiMap paragraph[NumParagraphs + 1]; |
2072 | QString paragraphName[NumParagraphs + 1]; |
2073 | QSet<char> usedParagraphNames; |
2074 | |
2075 | for (auto c = nmm.constBegin(); c != nmm.constEnd(); ++c) { |
2076 | QStringList pieces = c.key().split(sep: "::" ); |
2077 | int idx = commonPrefixLen; |
2078 | if (idx > 0 && !pieces.last().startsWith(s: commonPrefix, cs: Qt::CaseInsensitive)) |
2079 | idx = 0; |
2080 | QString last = pieces.last().toLower(); |
2081 | QString key = last.mid(position: idx); |
2082 | |
2083 | int paragraphNr = NumParagraphs - 1; |
2084 | |
2085 | if (key[0].digitValue() != -1) { |
2086 | paragraphNr = key[0].digitValue(); |
2087 | } else if (key[0] >= QLatin1Char('a') && key[0] <= QLatin1Char('z')) { |
2088 | paragraphNr = 10 + key[0].unicode() - 'a'; |
2089 | } |
2090 | |
2091 | paragraphName[paragraphNr] = key[0].toUpper(); |
2092 | usedParagraphNames.insert(value: key[0].toLower().cell()); |
2093 | paragraph[paragraphNr].insert(key: last, value: c.value()); |
2094 | } |
2095 | |
2096 | /* |
2097 | Each paragraph j has a size: paragraph[j].count(). In the |
2098 | discussion, we will assume paragraphs 0 to 5 will have sizes |
2099 | 3, 1, 4, 1, 5, 9. |
2100 | |
2101 | We now want to compute the paragraph offset. Paragraphs 0 to 6 |
2102 | start at offsets 0, 3, 4, 8, 9, 14, 23. |
2103 | */ |
2104 | int paragraphOffset[NumParagraphs + 1]; // 37 + 1 |
2105 | paragraphOffset[0] = 0; |
2106 | for (int i = 0; i < NumParagraphs; i++) // i = 0..36 |
2107 | paragraphOffset[i + 1] = paragraphOffset[i] + paragraph[i].size(); |
2108 | |
2109 | // Output the alphabet as a row of links. |
2110 | if (includeAlphabet && !usedParagraphNames.isEmpty()) { |
2111 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "simplelist" ); |
2112 | newLine(); |
2113 | |
2114 | for (int i = 0; i < 26; i++) { |
2115 | QChar ch('a' + i); |
2116 | if (usedParagraphNames.contains(value: char('a' + i))) { |
2117 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "member" ); |
2118 | generateSimpleLink(href: ch, text: ch.toUpper()); |
2119 | m_writer->writeEndElement(); // member |
2120 | newLine(); |
2121 | } |
2122 | } |
2123 | |
2124 | m_writer->writeEndElement(); // simplelist |
2125 | newLine(); |
2126 | } |
2127 | |
2128 | // Actual output. |
2129 | int curParNr = 0; |
2130 | int curParOffset = 0; |
2131 | QString previousName; |
2132 | bool multipleOccurrences = false; |
2133 | |
2134 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "variablelist" ); |
2135 | m_writer->writeAttribute(qualifiedName: "role" , value: selector); |
2136 | newLine(); |
2137 | |
2138 | for (int i = 0; i < nmm.size(); i++) { |
2139 | while ((curParNr < NumParagraphs) && (curParOffset == paragraph[curParNr].size())) { |
2140 | |
2141 | ++curParNr; |
2142 | curParOffset = 0; |
2143 | } |
2144 | |
2145 | // Starting a new paragraph means starting a new varlistentry. |
2146 | if (curParOffset == 0) { |
2147 | if (i > 0) { |
2148 | m_writer->writeEndElement(); // itemizedlist |
2149 | newLine(); |
2150 | m_writer->writeEndElement(); // listitem |
2151 | newLine(); |
2152 | m_writer->writeEndElement(); // varlistentry |
2153 | newLine(); |
2154 | } |
2155 | |
2156 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "varlistentry" ); |
2157 | if (includeAlphabet) |
2158 | writeXmlId(id: paragraphName[curParNr][0].toLower()); |
2159 | newLine(); |
2160 | |
2161 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "term" ); |
2162 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "emphasis" ); |
2163 | m_writer->writeAttribute(qualifiedName: "role" , value: "bold" ); |
2164 | m_writer->writeCharacters(text: paragraphName[curParNr]); |
2165 | m_writer->writeEndElement(); // emphasis |
2166 | m_writer->writeEndElement(); // term |
2167 | newLine(); |
2168 | |
2169 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "listitem" ); |
2170 | newLine(); |
2171 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "itemizedlist" ); |
2172 | newLine(); |
2173 | } |
2174 | |
2175 | // Output a listitem for the current offset in the current paragraph. |
2176 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "listitem" ); |
2177 | newLine(); |
2178 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "para" ); |
2179 | |
2180 | if ((curParNr < NumParagraphs) && !paragraphName[curParNr].isEmpty()) { |
2181 | NodeMultiMap::Iterator it; |
2182 | NodeMultiMap::Iterator next; |
2183 | it = paragraph[curParNr].begin(); |
2184 | for (int j = 0; j < curParOffset; j++) |
2185 | ++it; |
2186 | |
2187 | // Cut the name into pieces to determine whether it is simple (one piece) or complex |
2188 | // (more than one piece). |
2189 | QStringList pieces; |
2190 | if (it.value()->isQmlType()) { |
2191 | QString name = it.value()->name(); |
2192 | next = it; |
2193 | ++next; |
2194 | if (name != previousName) |
2195 | multipleOccurrences = false; |
2196 | if ((next != paragraph[curParNr].end()) && (name == next.value()->name())) { |
2197 | multipleOccurrences = true; |
2198 | previousName = name; |
2199 | } |
2200 | if (multipleOccurrences) |
2201 | name += ": " + it.value()->tree()->camelCaseModuleName(); |
2202 | pieces << name; |
2203 | } else |
2204 | pieces = it.value()->fullName(relative).split(sep: "::" ); |
2205 | |
2206 | // Write the link to the element, which is identical if the element is obsolete or not. |
2207 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "link" ); |
2208 | m_writer->writeAttribute(namespaceUri: xlinkNamespace, name: "href" , value: linkForNode(node: *it, relative)); |
2209 | if (const QString type = targetType(node: it.value()); !type.isEmpty()) |
2210 | m_writer->writeAttribute(qualifiedName: "role" , value: type); |
2211 | m_writer->writeCharacters(text: pieces.last()); |
2212 | m_writer->writeEndElement(); // link |
2213 | |
2214 | // Outside the link, give the full name of the node if it is complex. |
2215 | if (pieces.size() > 1) { |
2216 | m_writer->writeCharacters(text: " (" ); |
2217 | generateFullName(node: it.value()->parent(), relative); |
2218 | m_writer->writeCharacters(text: ")" ); |
2219 | } |
2220 | } |
2221 | |
2222 | m_writer->writeEndElement(); // para |
2223 | newLine(); |
2224 | m_writer->writeEndElement(); // listitem |
2225 | newLine(); |
2226 | |
2227 | curParOffset++; |
2228 | } |
2229 | m_writer->writeEndElement(); // itemizedlist |
2230 | newLine(); |
2231 | m_writer->writeEndElement(); // listitem |
2232 | newLine(); |
2233 | m_writer->writeEndElement(); // varlistentry |
2234 | newLine(); |
2235 | |
2236 | m_writer->writeEndElement(); // variablelist |
2237 | newLine(); |
2238 | } |
2239 | |
2240 | void DocBookGenerator::generateFunctionIndex(const Node *relative) |
2241 | { |
2242 | // From HtmlGenerator::generateFunctionIndex. |
2243 | |
2244 | // First list: links to parts of the second list, one item per letter. |
2245 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "simplelist" ); |
2246 | m_writer->writeAttribute(qualifiedName: "role" , value: "functionIndex" ); |
2247 | newLine(); |
2248 | for (int i = 0; i < 26; i++) { |
2249 | QChar ch('a' + i); |
2250 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "member" ); |
2251 | m_writer->writeAttribute(namespaceUri: xlinkNamespace, name: "href" , value: QString("#" ) + ch); |
2252 | m_writer->writeCharacters(text: ch.toUpper()); |
2253 | m_writer->writeEndElement(); // member |
2254 | newLine(); |
2255 | } |
2256 | m_writer->writeEndElement(); // simplelist |
2257 | newLine(); |
2258 | |
2259 | // Second list: the actual list of functions, sorted by alphabetical |
2260 | // order. One entry of the list per letter. |
2261 | if (m_qdb->getFunctionIndex().isEmpty()) |
2262 | return; |
2263 | char nextLetter = 'a'; |
2264 | char currentLetter; |
2265 | |
2266 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "itemizedlist" ); |
2267 | newLine(); |
2268 | |
2269 | NodeMapMap &funcIndex = m_qdb->getFunctionIndex(); |
2270 | QMap<QString, NodeMap>::ConstIterator f = funcIndex.constBegin(); |
2271 | while (f != funcIndex.constEnd()) { |
2272 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "listitem" ); |
2273 | newLine(); |
2274 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "para" ); |
2275 | m_writer->writeCharacters(text: f.key() + ": " ); |
2276 | |
2277 | currentLetter = f.key()[0].unicode(); |
2278 | while (islower(currentLetter) && currentLetter >= nextLetter) { |
2279 | writeAnchor(id: QString(nextLetter)); |
2280 | nextLetter++; |
2281 | } |
2282 | |
2283 | NodeMap::ConstIterator s = (*f).constBegin(); |
2284 | while (s != (*f).constEnd()) { |
2285 | m_writer->writeCharacters(text: " " ); |
2286 | generateFullName(node: (*s)->parent(), relative); |
2287 | ++s; |
2288 | } |
2289 | |
2290 | m_writer->writeEndElement(); // para |
2291 | newLine(); |
2292 | m_writer->writeEndElement(); // listitem |
2293 | newLine(); |
2294 | ++f; |
2295 | } |
2296 | m_writer->writeEndElement(); // itemizedlist |
2297 | newLine(); |
2298 | } |
2299 | |
2300 | void DocBookGenerator::generateLegaleseList(const Node *relative) |
2301 | { |
2302 | // From HtmlGenerator::generateLegaleseList. |
2303 | TextToNodeMap &legaleseTexts = m_qdb->getLegaleseTexts(); |
2304 | for (auto it = legaleseTexts.cbegin(), end = legaleseTexts.cend(); it != end; ++it) { |
2305 | Text text = it.key(); |
2306 | generateText(text, relative); |
2307 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "itemizedlist" ); |
2308 | newLine(); |
2309 | do { |
2310 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "listitem" ); |
2311 | newLine(); |
2312 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "para" ); |
2313 | generateFullName(node: it.value(), relative); |
2314 | m_writer->writeEndElement(); // para |
2315 | newLine(); |
2316 | m_writer->writeEndElement(); // listitem |
2317 | newLine(); |
2318 | ++it; |
2319 | } while (it != legaleseTexts.constEnd() && it.key() == text); |
2320 | m_writer->writeEndElement(); // itemizedlist |
2321 | newLine(); |
2322 | } |
2323 | } |
2324 | |
2325 | void DocBookGenerator::generateBrief(const Node *node) |
2326 | { |
2327 | // From HtmlGenerator::generateBrief. Also see generateHeader, which is specifically dealing |
2328 | // with the DocBook header (and thus wraps the brief in an abstract). |
2329 | Text brief = node->doc().briefText(); |
2330 | |
2331 | if (!brief.isEmpty()) { |
2332 | if (!brief.lastAtom()->string().endsWith(c: '.')) |
2333 | brief << Atom(Atom::String, "." ); |
2334 | |
2335 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "para" ); |
2336 | generateText(text: brief, relative: node); |
2337 | m_writer->writeEndElement(); // para |
2338 | newLine(); |
2339 | } |
2340 | } |
2341 | |
2342 | bool DocBookGenerator::generateSince(const Node *node) |
2343 | { |
2344 | // From Generator::generateSince. |
2345 | if (!node->since().isEmpty()) { |
2346 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "para" ); |
2347 | m_writer->writeCharacters(text: "This " + typeString(node) + " was introduced in " ); |
2348 | m_writer->writeCharacters(text: formatSince(node) + "." ); |
2349 | m_writer->writeEndElement(); // para |
2350 | newLine(); |
2351 | |
2352 | return true; |
2353 | } |
2354 | |
2355 | return false; |
2356 | } |
2357 | |
2358 | /*! |
2359 | Generate the DocBook header for the file, including the abstract. |
2360 | Equivalent to calling generateTitle and generateBrief in HTML. |
2361 | */ |
2362 | void DocBookGenerator::(const QString &title, const QString &subTitle, |
2363 | const Node *node) |
2364 | { |
2365 | refMap.clear(); |
2366 | |
2367 | // Output the DocBook header. |
2368 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "info" ); |
2369 | newLine(); |
2370 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "title" ); |
2371 | if (node->genus() & Node::API && m_useITS) |
2372 | m_writer->writeAttribute(namespaceUri: itsNamespace, name: "translate" , value: "no" ); |
2373 | m_writer->writeCharacters(text: title); |
2374 | m_writer->writeEndElement(); // title |
2375 | newLine(); |
2376 | |
2377 | if (!subTitle.isEmpty()) { |
2378 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "subtitle" ); |
2379 | if (node->genus() & Node::API && m_useITS) |
2380 | m_writer->writeAttribute(namespaceUri: itsNamespace, name: "translate" , value: "no" ); |
2381 | m_writer->writeCharacters(text: subTitle); |
2382 | m_writer->writeEndElement(); // subtitle |
2383 | newLine(); |
2384 | } |
2385 | |
2386 | if (!m_project.isEmpty()) { |
2387 | m_writer->writeTextElement(namespaceUri: dbNamespace, name: "productname" , text: m_project); |
2388 | newLine(); |
2389 | } |
2390 | |
2391 | if (!m_buildVersion.isEmpty()) { |
2392 | m_writer->writeTextElement(namespaceUri: dbNamespace, name: "edition" , text: m_buildVersion); |
2393 | newLine(); |
2394 | } |
2395 | |
2396 | if (!m_projectDescription.isEmpty()) { |
2397 | m_writer->writeTextElement(namespaceUri: dbNamespace, name: "titleabbrev" , text: m_projectDescription); |
2398 | newLine(); |
2399 | } |
2400 | |
2401 | // Deal with links. |
2402 | // Adapted from HtmlGenerator::generateHeader (output part: no need to update a navigationLinks |
2403 | // or useSeparator field, as this content is only output in the info tag, not in the main |
2404 | // content). |
2405 | if (node && !node->links().empty()) { |
2406 | std::pair<QString, QString> linkPair; |
2407 | std::pair<QString, QString> anchorPair; |
2408 | const Node *linkNode; |
2409 | |
2410 | if (node->links().contains(key: Node::PreviousLink)) { |
2411 | linkPair = node->links()[Node::PreviousLink]; |
2412 | linkNode = m_qdb->findNodeForTarget(target: linkPair.first, relative: node); |
2413 | if (!linkNode || linkNode == node) |
2414 | anchorPair = linkPair; |
2415 | else |
2416 | anchorPair = anchorForNode(node: linkNode); |
2417 | |
2418 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "extendedlink" ); |
2419 | m_writer->writeAttribute(namespaceUri: xlinkNamespace, name: "type" , value: "extended" ); |
2420 | m_writer->writeEmptyElement(namespaceUri: dbNamespace, name: "link" ); |
2421 | m_writer->writeAttribute(namespaceUri: xlinkNamespace, name: "to" , value: anchorPair.first); |
2422 | m_writer->writeAttribute(namespaceUri: xlinkNamespace, name: "type" , value: "arc" ); |
2423 | m_writer->writeAttribute(namespaceUri: xlinkNamespace, name: "arcrole" , value: "prev" ); |
2424 | if (linkPair.first == linkPair.second && !anchorPair.second.isEmpty()) |
2425 | m_writer->writeAttribute(namespaceUri: xlinkNamespace, name: "title" , value: anchorPair.second); |
2426 | else |
2427 | m_writer->writeAttribute(namespaceUri: xlinkNamespace, name: "title" , value: linkPair.second); |
2428 | m_writer->writeEndElement(); // extendedlink |
2429 | newLine(); |
2430 | } |
2431 | if (node->links().contains(key: Node::NextLink)) { |
2432 | linkPair = node->links()[Node::NextLink]; |
2433 | linkNode = m_qdb->findNodeForTarget(target: linkPair.first, relative: node); |
2434 | if (!linkNode || linkNode == node) |
2435 | anchorPair = linkPair; |
2436 | else |
2437 | anchorPair = anchorForNode(node: linkNode); |
2438 | |
2439 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "extendedlink" ); |
2440 | m_writer->writeAttribute(namespaceUri: xlinkNamespace, name: "type" , value: "extended" ); |
2441 | m_writer->writeEmptyElement(namespaceUri: dbNamespace, name: "link" ); |
2442 | m_writer->writeAttribute(namespaceUri: xlinkNamespace, name: "to" , value: anchorPair.first); |
2443 | m_writer->writeAttribute(namespaceUri: xlinkNamespace, name: "type" , value: "arc" ); |
2444 | m_writer->writeAttribute(namespaceUri: xlinkNamespace, name: "arcrole" , value: "next" ); |
2445 | if (linkPair.first == linkPair.second && !anchorPair.second.isEmpty()) |
2446 | m_writer->writeAttribute(namespaceUri: xlinkNamespace, name: "title" , value: anchorPair.second); |
2447 | else |
2448 | m_writer->writeAttribute(namespaceUri: xlinkNamespace, name: "title" , value: linkPair.second); |
2449 | m_writer->writeEndElement(); // extendedlink |
2450 | newLine(); |
2451 | } |
2452 | if (node->links().contains(key: Node::StartLink)) { |
2453 | linkPair = node->links()[Node::StartLink]; |
2454 | linkNode = m_qdb->findNodeForTarget(target: linkPair.first, relative: node); |
2455 | if (!linkNode || linkNode == node) |
2456 | anchorPair = linkPair; |
2457 | else |
2458 | anchorPair = anchorForNode(node: linkNode); |
2459 | |
2460 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "extendedlink" ); |
2461 | m_writer->writeAttribute(namespaceUri: xlinkNamespace, name: "type" , value: "extended" ); |
2462 | m_writer->writeEmptyElement(namespaceUri: dbNamespace, name: "link" ); |
2463 | m_writer->writeAttribute(namespaceUri: xlinkNamespace, name: "to" , value: anchorPair.first); |
2464 | m_writer->writeAttribute(namespaceUri: xlinkNamespace, name: "type" , value: "arc" ); |
2465 | m_writer->writeAttribute(namespaceUri: xlinkNamespace, name: "arcrole" , value: "start" ); |
2466 | if (linkPair.first == linkPair.second && !anchorPair.second.isEmpty()) |
2467 | m_writer->writeAttribute(namespaceUri: xlinkNamespace, name: "title" , value: anchorPair.second); |
2468 | else |
2469 | m_writer->writeAttribute(namespaceUri: xlinkNamespace, name: "title" , value: linkPair.second); |
2470 | m_writer->writeEndElement(); // extendedlink |
2471 | newLine(); |
2472 | } |
2473 | } |
2474 | |
2475 | // Deal with the abstract (what qdoc calls brief). |
2476 | if (node) { |
2477 | // Adapted from HtmlGenerator::generateBrief, without extraction marks. The parameter |
2478 | // addLink is always false. Factoring this function out is not as easy as in HtmlGenerator: |
2479 | // abstracts only happen in the header (info tag), slightly different tags must be used at |
2480 | // other places. Also includes code from HtmlGenerator::generateCppReferencePage to handle |
2481 | // the name spaces. |
2482 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "abstract" ); |
2483 | newLine(); |
2484 | |
2485 | bool generatedSomething = false; |
2486 | |
2487 | Text brief; |
2488 | const NamespaceNode *ns = |
2489 | node->isNamespace() ? static_cast<const NamespaceNode *>(node) : nullptr; |
2490 | if (ns && !ns->hasDoc() && ns->docNode()) { |
2491 | NamespaceNode *NS = ns->docNode(); |
2492 | brief << "The " << ns->name() |
2493 | << " namespace includes the following elements from module " |
2494 | << ns->tree()->camelCaseModuleName() << ". The full namespace is " |
2495 | << "documented in module " << NS->tree()->camelCaseModuleName() |
2496 | << Atom(Atom::LinkNode, fullDocumentLocation(node: NS)) |
2497 | << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK) |
2498 | << Atom(Atom::String, " here." ) |
2499 | << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK); |
2500 | } else { |
2501 | brief = node->doc().briefText(); |
2502 | } |
2503 | |
2504 | if (!brief.isEmpty()) { |
2505 | if (!brief.lastAtom()->string().endsWith(c: '.')) |
2506 | brief << Atom(Atom::String, "." ); |
2507 | |
2508 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "para" ); |
2509 | generateText(text: brief, relative: node); |
2510 | m_writer->writeEndElement(); // para |
2511 | newLine(); |
2512 | |
2513 | generatedSomething = true; |
2514 | } |
2515 | |
2516 | // Generate other paragraphs that should go into the abstract. |
2517 | generatedSomething |= generateStatus(node); |
2518 | generatedSomething |= generateSince(node); |
2519 | generatedSomething |= generateThreadSafeness(node); |
2520 | |
2521 | // An abstract cannot be empty, hence use the project description. |
2522 | if (!generatedSomething) |
2523 | m_writer->writeTextElement(namespaceUri: dbNamespace, name: "para" , text: m_projectDescription + "." ); |
2524 | |
2525 | m_writer->writeEndElement(); // abstract |
2526 | newLine(); |
2527 | } |
2528 | |
2529 | // End of the DocBook header. |
2530 | m_writer->writeEndElement(); // info |
2531 | newLine(); |
2532 | } |
2533 | |
2534 | void DocBookGenerator::closeTextSections() |
2535 | { |
2536 | while (!sectionLevels.isEmpty()) { |
2537 | sectionLevels.pop(); |
2538 | endSection(); |
2539 | } |
2540 | } |
2541 | |
2542 | void DocBookGenerator::() |
2543 | { |
2544 | if (m_closeSectionAfterGeneratedList) { |
2545 | m_closeSectionAfterGeneratedList = false; |
2546 | endSection(); |
2547 | } |
2548 | if (m_closeSectionAfterRawTitle) { |
2549 | m_closeSectionAfterRawTitle = false; |
2550 | endSection(); |
2551 | } |
2552 | |
2553 | closeTextSections(); |
2554 | m_writer->writeEndElement(); // article |
2555 | } |
2556 | |
2557 | void DocBookGenerator::generateSimpleLink(const QString &href, const QString &text) |
2558 | { |
2559 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "link" ); |
2560 | m_writer->writeAttribute(namespaceUri: xlinkNamespace, name: "href" , value: href); |
2561 | m_writer->writeCharacters(text); |
2562 | m_writer->writeEndElement(); // link |
2563 | } |
2564 | |
2565 | void DocBookGenerator::generateObsoleteMembers(const Sections §ions) |
2566 | { |
2567 | // From HtmlGenerator::generateObsoleteMembersFile. |
2568 | SectionPtrVector summary_spv; // Summaries are ignored in DocBook (table of contents). |
2569 | SectionPtrVector details_spv; |
2570 | if (!sections.hasObsoleteMembers(summary_spv: &summary_spv, details_spv: &details_spv)) |
2571 | return; |
2572 | |
2573 | Aggregate *aggregate = sections.aggregate(); |
2574 | startSection(id: "obsolete" , title: "Obsolete Members for " + aggregate->name()); |
2575 | |
2576 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "para" ); |
2577 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "emphasis" ); |
2578 | m_writer->writeAttribute(qualifiedName: "role" , value: "bold" ); |
2579 | m_writer->writeCharacters(text: "The following members of class " ); |
2580 | generateSimpleLink(href: linkForNode(node: aggregate, relative: nullptr), text: aggregate->name()); |
2581 | m_writer->writeCharacters(text: " are deprecated." ); |
2582 | m_writer->writeEndElement(); // emphasis bold |
2583 | m_writer->writeCharacters(text: " We strongly advise against using them in new code." ); |
2584 | m_writer->writeEndElement(); // para |
2585 | newLine(); |
2586 | |
2587 | for (const Section *section : details_spv) { |
2588 | const QString &title = "Obsolete " + section->title(); |
2589 | startSection(id: title.toLower(), title); |
2590 | |
2591 | const NodeVector &members = section->obsoleteMembers(); |
2592 | NodeVector::ConstIterator m = members.constBegin(); |
2593 | while (m != members.constEnd()) { |
2594 | if ((*m)->access() != Access::Private) |
2595 | generateDetailedMember(node: *m, relative: aggregate); |
2596 | ++m; |
2597 | } |
2598 | |
2599 | endSection(); |
2600 | } |
2601 | |
2602 | endSection(); |
2603 | } |
2604 | |
2605 | /*! |
2606 | Generates a separate section where obsolete members of the QML |
2607 | type \a qcn are listed. The \a marker is used to generate |
2608 | the section lists, which are then traversed and output here. |
2609 | |
2610 | Note that this function currently only handles correctly the |
2611 | case where \a status is \c {Section::Deprecated}. |
2612 | */ |
2613 | void DocBookGenerator::generateObsoleteQmlMembers(const Sections §ions) |
2614 | { |
2615 | // From HtmlGenerator::generateObsoleteQmlMembersFile. |
2616 | SectionPtrVector summary_spv; // Summaries are not useful in DocBook. |
2617 | SectionPtrVector details_spv; |
2618 | if (!sections.hasObsoleteMembers(summary_spv: &summary_spv, details_spv: &details_spv)) |
2619 | return; |
2620 | |
2621 | Aggregate *aggregate = sections.aggregate(); |
2622 | startSection(id: "obsolete" , title: "Obsolete Members for " + aggregate->name()); |
2623 | |
2624 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "para" ); |
2625 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "emphasis" ); |
2626 | m_writer->writeAttribute(qualifiedName: "role" , value: "bold" ); |
2627 | m_writer->writeCharacters(text: "The following members of QML type " ); |
2628 | generateSimpleLink(href: linkForNode(node: aggregate, relative: nullptr), text: aggregate->name()); |
2629 | m_writer->writeCharacters(text: " are deprecated." ); |
2630 | m_writer->writeEndElement(); // emphasis bold |
2631 | m_writer->writeCharacters(text: " We strongly advise against using them in new code." ); |
2632 | m_writer->writeEndElement(); // para |
2633 | newLine(); |
2634 | |
2635 | for (const auto *section : details_spv) { |
2636 | const QString &title = "Obsolete " + section->title(); |
2637 | startSection(id: title.toLower(), title); |
2638 | |
2639 | const NodeVector &members = section->obsoleteMembers(); |
2640 | NodeVector::ConstIterator m = members.constBegin(); |
2641 | while (m != members.constEnd()) { |
2642 | if ((*m)->access() != Access::Private) |
2643 | generateDetailedQmlMember(node: *m, relative: aggregate); |
2644 | ++m; |
2645 | } |
2646 | |
2647 | endSection(); |
2648 | } |
2649 | |
2650 | endSection(); |
2651 | } |
2652 | |
2653 | static QString nodeToSynopsisTag(const Node *node) |
2654 | { |
2655 | // Order from Node::nodeTypeString. |
2656 | if (node->isClass() || node->isQmlType()) |
2657 | return QStringLiteral("classsynopsis" ); |
2658 | if (node->isNamespace()) |
2659 | return QStringLiteral("packagesynopsis" ); |
2660 | if (node->isPageNode()) { |
2661 | node->doc().location().warning(message: "Unexpected document node in nodeToSynopsisTag" ); |
2662 | return QString(); |
2663 | } |
2664 | if (node->isEnumType()) |
2665 | return QStringLiteral("enumsynopsis" ); |
2666 | if (node->isTypedef()) |
2667 | return QStringLiteral("typedefsynopsis" ); |
2668 | if (node->isFunction()) { |
2669 | // Signals are also encoded as functions (including QML ones). |
2670 | const auto fn = static_cast<const FunctionNode *>(node); |
2671 | if (fn->isCtor() || fn->isCCtor() || fn->isMCtor()) |
2672 | return QStringLiteral("constructorsynopsis" ); |
2673 | if (fn->isDtor()) |
2674 | return QStringLiteral("destructorsynopsis" ); |
2675 | return QStringLiteral("methodsynopsis" ); |
2676 | } |
2677 | if (node->isProperty() || node->isVariable() || node->isQmlProperty()) |
2678 | return QStringLiteral("fieldsynopsis" ); |
2679 | |
2680 | node->doc().location().warning(message: QString("Unknown node tag %1" ).arg(a: node->nodeTypeString())); |
2681 | return QStringLiteral("synopsis" ); |
2682 | } |
2683 | |
2684 | void DocBookGenerator::generateStartRequisite(const QString &description) |
2685 | { |
2686 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "varlistentry" ); |
2687 | newLine(); |
2688 | m_writer->writeTextElement(namespaceUri: dbNamespace, name: "term" , text: description); |
2689 | newLine(); |
2690 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "listitem" ); |
2691 | newLine(); |
2692 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "para" ); |
2693 | m_inPara = true; |
2694 | } |
2695 | |
2696 | void DocBookGenerator::generateEndRequisite() |
2697 | { |
2698 | m_writer->writeEndElement(); // para |
2699 | m_inPara = false; |
2700 | newLine(); |
2701 | m_writer->writeEndElement(); // listitem |
2702 | newLine(); |
2703 | m_writer->writeEndElement(); // varlistentry |
2704 | newLine(); |
2705 | } |
2706 | |
2707 | void DocBookGenerator::generateRequisite(const QString &description, const QString &value) |
2708 | { |
2709 | generateStartRequisite(description); |
2710 | m_writer->writeCharacters(text: value); |
2711 | generateEndRequisite(); |
2712 | } |
2713 | |
2714 | /*! |
2715 | * \internal |
2716 | * Generates the CMake (\a description) requisites |
2717 | */ |
2718 | void DocBookGenerator::generateCMakeRequisite(const QStringList &values) |
2719 | { |
2720 | const QString description("CMake" ); |
2721 | generateStartRequisite(description); |
2722 | m_writer->writeCharacters(text: values.first()); |
2723 | m_writer->writeEndElement(); // para |
2724 | newLine(); |
2725 | |
2726 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "para" ); |
2727 | m_writer->writeCharacters(text: values.last()); |
2728 | generateEndRequisite(); |
2729 | } |
2730 | |
2731 | void DocBookGenerator::generateSortedNames(const ClassNode *cn, const QList<RelatedClass> &rc) |
2732 | { |
2733 | // From Generator::appendSortedNames. |
2734 | QMap<QString, ClassNode *> classMap; |
2735 | QList<RelatedClass>::ConstIterator r = rc.constBegin(); |
2736 | while (r != rc.constEnd()) { |
2737 | ClassNode *rcn = (*r).m_node; |
2738 | if (rcn && rcn->access() == Access::Public && rcn->status() != Node::Internal |
2739 | && !rcn->doc().isEmpty()) { |
2740 | classMap[rcn->plainFullName(relative: cn).toLower()] = rcn; |
2741 | } |
2742 | ++r; |
2743 | } |
2744 | |
2745 | QStringList classNames = classMap.keys(); |
2746 | classNames.sort(); |
2747 | |
2748 | int index = 0; |
2749 | for (const QString &className : classNames) { |
2750 | generateFullName(node: classMap.value(key: className), relative: cn); |
2751 | m_writer->writeCharacters(text: Utilities::comma(wordPosition: index++, numberOfWords: classNames.size())); |
2752 | } |
2753 | } |
2754 | |
2755 | void DocBookGenerator::generateSortedQmlNames(const Node *base, const NodeList &subs) |
2756 | { |
2757 | // From Generator::appendSortedQmlNames. |
2758 | QMap<QString, Node *> classMap; |
2759 | int index = 0; |
2760 | |
2761 | for (auto sub : subs) |
2762 | if (!base->isQtQuickNode() || !sub->isQtQuickNode() |
2763 | || (base->logicalModuleName() == sub->logicalModuleName())) |
2764 | classMap[sub->plainFullName(relative: base).toLower()] = sub; |
2765 | |
2766 | QStringList names = classMap.keys(); |
2767 | names.sort(); |
2768 | |
2769 | for (const QString &name : names) { |
2770 | generateFullName(node: classMap.value(key: name), relative: base); |
2771 | m_writer->writeCharacters(text: Utilities::comma(wordPosition: index++, numberOfWords: names.size())); |
2772 | } |
2773 | } |
2774 | |
2775 | /*! |
2776 | Lists the required imports and includes. |
2777 | */ |
2778 | void DocBookGenerator::generateRequisites(const Aggregate *aggregate) |
2779 | { |
2780 | // Adapted from HtmlGenerator::generateRequisites, but simplified: no need to store all the |
2781 | // elements, they can be produced one by one. |
2782 | |
2783 | // Generate the requisites first separately: if some of them are generated, output them in a wrapper. |
2784 | // This complexity is required to ensure the DocBook file is valid: an empty list is not valid. It is not easy |
2785 | // to write a truly comprehensive condition. |
2786 | QXmlStreamWriter* oldWriter = m_writer; |
2787 | QString output; |
2788 | m_writer = new QXmlStreamWriter(&output); |
2789 | |
2790 | // Includes. |
2791 | if (aggregate->includeFile()) generateRequisite(description: "Header" , value: *aggregate->includeFile()); |
2792 | |
2793 | // Since and project. |
2794 | if (!aggregate->since().isEmpty()) |
2795 | generateRequisite(description: "Since" , value: formatSince(node: aggregate)); |
2796 | |
2797 | if (aggregate->isClassNode() || aggregate->isNamespace()) { |
2798 | // CMake and QT variable. |
2799 | const CollectionNode *cn = |
2800 | m_qdb->getCollectionNode(name: aggregate->physicalModuleName(), type: Node::Module); |
2801 | if (cn && !cn->qtCMakeComponent().isEmpty()) { |
2802 | const QString qtComponent = "Qt" + QString::number(QT_VERSION_MAJOR); |
2803 | const QString findpackageText = "find_package(" + qtComponent |
2804 | + " REQUIRED COMPONENTS " + cn->qtCMakeComponent() + ")" ; |
2805 | const QString targetLinkLibrariesText = "target_link_libraries(mytarget PRIVATE " |
2806 | + qtComponent + "::" + cn->qtCMakeComponent() + ")" ; |
2807 | const QStringList cmakeInfo { findpackageText, targetLinkLibrariesText }; |
2808 | generateCMakeRequisite(values: cmakeInfo); |
2809 | } |
2810 | if (cn && !cn->qtVariable().isEmpty()) |
2811 | generateRequisite(description: "qmake" , value: "QT += " + cn->qtVariable()); |
2812 | } |
2813 | |
2814 | if (aggregate->nodeType() == Node::Class) { |
2815 | // Instantiated by. |
2816 | auto *classe = const_cast<ClassNode *>(static_cast<const ClassNode *>(aggregate)); |
2817 | if (classe->qmlElement() != nullptr && classe->status() != Node::Internal) { |
2818 | generateStartRequisite(description: "Inherited By" ); |
2819 | generateSortedNames(cn: classe, rc: classe->derivedClasses()); |
2820 | generateEndRequisite(); |
2821 | generateRequisite(description: "Instantiated By" , value: fullDocumentLocation(node: classe->qmlElement())); |
2822 | } |
2823 | |
2824 | // Inherits. |
2825 | QList<RelatedClass>::ConstIterator r; |
2826 | if (!classe->baseClasses().isEmpty()) { |
2827 | generateStartRequisite(description: "Inherits" ); |
2828 | |
2829 | r = classe->baseClasses().constBegin(); |
2830 | int index = 0; |
2831 | while (r != classe->baseClasses().constEnd()) { |
2832 | if ((*r).m_node) { |
2833 | generateFullName(node: (*r).m_node, relative: classe); |
2834 | |
2835 | if ((*r).m_access == Access::Protected) |
2836 | m_writer->writeCharacters(text: " (protected)" ); |
2837 | else if ((*r).m_access == Access::Private) |
2838 | m_writer->writeCharacters(text: " (private)" ); |
2839 | m_writer->writeCharacters( |
2840 | text: Utilities::comma(wordPosition: index++, numberOfWords: classe->baseClasses().size())); |
2841 | } |
2842 | ++r; |
2843 | } |
2844 | |
2845 | generateEndRequisite(); |
2846 | } |
2847 | |
2848 | // Inherited by. |
2849 | if (!classe->derivedClasses().isEmpty()) { |
2850 | generateStartRequisite(description: "Inherited By" ); |
2851 | generateSortedNames(cn: classe, rc: classe->derivedClasses()); |
2852 | generateEndRequisite(); |
2853 | } |
2854 | } |
2855 | |
2856 | // Group. |
2857 | if (!aggregate->groupNames().empty()) { |
2858 | generateStartRequisite(description: "Group" ); |
2859 | generateGroupReferenceText(node: aggregate); |
2860 | generateEndRequisite(); |
2861 | } |
2862 | |
2863 | // Status. |
2864 | if (auto status = formatStatus(node: aggregate, qdb: m_qdb); status) |
2865 | generateRequisite(description: "Status" , value: status.value()); |
2866 | |
2867 | // Write the elements as a list if not empty. |
2868 | delete m_writer; |
2869 | m_writer = oldWriter; |
2870 | |
2871 | if (!output.isEmpty()) { |
2872 | // Namespaces are mangled in this output, because QXmlStreamWriter doesn't know about them. (Letting it know |
2873 | // would imply generating the xmlns declaration one more time.) |
2874 | static const QRegularExpression xmlTag(R"(<(/?)n\d+:)" ); // Only for DocBook tags. |
2875 | static const QRegularExpression xmlnsDocBookDefinition(R"( xmlns:n\d+=")" + QString{dbNamespace} + "\"" ); |
2876 | static const QRegularExpression xmlnsXLinkDefinition(R"( xmlns:n\d+=")" + QString{xlinkNamespace} + "\"" ); |
2877 | static const QRegularExpression xmlAttr(R"( n\d+:)" ); // Only for XLink attributes. |
2878 | // Space at the beginning! |
2879 | const QString cleanOutput = output.replace(re: xmlTag, after: R"(<\1db:)" ) |
2880 | .replace(re: xmlnsDocBookDefinition, after: "" ) |
2881 | .replace(re: xmlnsXLinkDefinition, after: "" ) |
2882 | .replace(re: xmlAttr, after: " xlink:" ); |
2883 | |
2884 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "variablelist" ); |
2885 | if (m_useITS) |
2886 | m_writer->writeAttribute(namespaceUri: itsNamespace, name: "translate" , value: "no" ); |
2887 | newLine(); |
2888 | |
2889 | m_writer->device()->write(data: cleanOutput.toUtf8()); |
2890 | |
2891 | m_writer->writeEndElement(); // variablelist |
2892 | newLine(); |
2893 | } |
2894 | } |
2895 | |
2896 | /*! |
2897 | Lists the required imports and includes. |
2898 | */ |
2899 | void DocBookGenerator::generateQmlRequisites(const QmlTypeNode *qcn) |
2900 | { |
2901 | // From HtmlGenerator::generateQmlRequisites, but simplified: no need to store all the elements, |
2902 | // they can be produced one by one. |
2903 | if (!qcn) |
2904 | return; |
2905 | |
2906 | const CollectionNode *collection = qcn->logicalModule(); |
2907 | |
2908 | NodeList subs; |
2909 | QmlTypeNode::subclasses(base: qcn, subs); |
2910 | |
2911 | QmlTypeNode *base = qcn->qmlBaseNode(); |
2912 | while (base && base->isInternal()) { |
2913 | base = base->qmlBaseNode(); |
2914 | } |
2915 | |
2916 | // Skip import statement for \internal collections |
2917 | const bool generate_import_statement = !qcn->logicalModuleName().isEmpty() && (!collection || !collection->isInternal() || m_showInternal); |
2918 | // Detect if anything is generated in this method. If not, exit early to avoid having an empty list. |
2919 | const bool generates_something = generate_import_statement || !qcn->since().isEmpty() || !subs.isEmpty() || base; |
2920 | |
2921 | if (!generates_something) |
2922 | return; |
2923 | |
2924 | // Start writing the elements as a list. |
2925 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "variablelist" ); |
2926 | if (m_useITS) |
2927 | m_writer->writeAttribute(namespaceUri: itsNamespace, name: "translate" , value: "no" ); |
2928 | newLine(); |
2929 | |
2930 | if (generate_import_statement) { |
2931 | QStringList parts = QStringList() << "import" << qcn->logicalModuleName() << qcn->logicalModuleVersion(); |
2932 | generateRequisite(description: "Import Statement" , value: parts.join(sep: ' ').trimmed()); |
2933 | } |
2934 | |
2935 | // Since and project. |
2936 | if (!qcn->since().isEmpty()) |
2937 | generateRequisite(description: "Since:" , value: formatSince(node: qcn)); |
2938 | |
2939 | // Inherited by. |
2940 | if (!subs.isEmpty()) { |
2941 | generateStartRequisite(description: "Inherited By:" ); |
2942 | generateSortedQmlNames(base: qcn, subs); |
2943 | generateEndRequisite(); |
2944 | } |
2945 | |
2946 | // Inherits. |
2947 | if (base) { |
2948 | const Node *otherNode = nullptr; |
2949 | Atom a = Atom(Atom::LinkNode, CodeMarker::stringForNode(node: base)); |
2950 | QString link = getAutoLink(atom: &a, relative: qcn, node: &otherNode); |
2951 | |
2952 | generateStartRequisite(description: "Inherits:" ); |
2953 | generateSimpleLink(href: link, text: base->name()); |
2954 | generateEndRequisite(); |
2955 | } |
2956 | |
2957 | // Instantiates. |
2958 | ClassNode *cn = (const_cast<QmlTypeNode *>(qcn))->classNode(); |
2959 | if (cn && (cn->status() != Node::Internal)) { |
2960 | Atom a = Atom(Atom::LinkNode, CodeMarker::stringForNode(node: qcn)); |
2961 | |
2962 | generateStartRequisite(description: "Instantiates:" ); |
2963 | generateSimpleLink(href: fullDocumentLocation(node: cn), text: cn->name()); |
2964 | generateEndRequisite(); |
2965 | } |
2966 | |
2967 | // Group. |
2968 | if (!qcn->groupNames().empty()) { |
2969 | generateStartRequisite(description: "Group" ); |
2970 | generateGroupReferenceText(node: qcn); |
2971 | generateEndRequisite(); |
2972 | } |
2973 | |
2974 | // Status. |
2975 | if (auto status = formatStatus(node: qcn, qdb: m_qdb); status) |
2976 | generateRequisite(description: "Status:" , value: status.value()); |
2977 | |
2978 | m_writer->writeEndElement(); // variablelist |
2979 | newLine(); |
2980 | } |
2981 | |
2982 | bool DocBookGenerator::generateStatus(const Node *node) |
2983 | { |
2984 | // From Generator::generateStatus. |
2985 | switch (node->status()) { |
2986 | case Node::Active: |
2987 | // Output the module 'state' description if set. |
2988 | if (node->isModule() || node->isQmlModule()) { |
2989 | const QString &state = static_cast<const CollectionNode*>(node)->state(); |
2990 | if (!state.isEmpty()) { |
2991 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "para" ); |
2992 | m_writer->writeCharacters(text: "This " + typeString(node) + " is in " ); |
2993 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "emphasis" ); |
2994 | m_writer->writeCharacters(text: state); |
2995 | m_writer->writeEndElement(); // emphasis |
2996 | m_writer->writeCharacters(text: " state." ); |
2997 | m_writer->writeEndElement(); // para |
2998 | newLine(); |
2999 | return true; |
3000 | } |
3001 | } |
3002 | return false; |
3003 | case Node::Preliminary: |
3004 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "para" ); |
3005 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "emphasis" ); |
3006 | m_writer->writeAttribute(qualifiedName: "role" , value: "bold" ); |
3007 | m_writer->writeCharacters(text: "This " + typeString(node) |
3008 | + " is under development and is subject to change." ); |
3009 | m_writer->writeEndElement(); // emphasis |
3010 | m_writer->writeEndElement(); // para |
3011 | newLine(); |
3012 | return true; |
3013 | case Node::Deprecated: |
3014 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "para" ); |
3015 | if (node->isAggregate()) { |
3016 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "emphasis" ); |
3017 | m_writer->writeAttribute(qualifiedName: "role" , value: "bold" ); |
3018 | } |
3019 | m_writer->writeCharacters(text: "This " + typeString(node) + " is deprecated" ); |
3020 | if (const QString &version = node->deprecatedSince(); !version.isEmpty()) |
3021 | m_writer->writeCharacters(text: " since " + version); |
3022 | m_writer->writeCharacters(text: ". We strongly advise against using it in new code." ); |
3023 | if (node->isAggregate()) |
3024 | m_writer->writeEndElement(); // emphasis |
3025 | m_writer->writeEndElement(); // para |
3026 | newLine(); |
3027 | return true; |
3028 | case Node::Internal: |
3029 | default: |
3030 | return false; |
3031 | } |
3032 | } |
3033 | |
3034 | /*! |
3035 | Generate a list of function signatures. The function nodes |
3036 | are in \a nodes. |
3037 | */ |
3038 | void DocBookGenerator::generateSignatureList(const NodeList &nodes) |
3039 | { |
3040 | // From Generator::signatureList and Generator::appendSignature. |
3041 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "itemizedlist" ); |
3042 | newLine(); |
3043 | |
3044 | NodeList::ConstIterator n = nodes.constBegin(); |
3045 | while (n != nodes.constEnd()) { |
3046 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "listitem" ); |
3047 | newLine(); |
3048 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "para" ); |
3049 | |
3050 | generateSimpleLink(href: currentGenerator()->fullDocumentLocation(node: *n), |
3051 | text: (*n)->signature(Node::SignaturePlain)); |
3052 | |
3053 | m_writer->writeEndElement(); // para |
3054 | newLine(); |
3055 | m_writer->writeEndElement(); // itemizedlist |
3056 | newLine(); |
3057 | ++n; |
3058 | } |
3059 | |
3060 | m_writer->writeEndElement(); // itemizedlist |
3061 | newLine(); |
3062 | } |
3063 | |
3064 | /*! |
3065 | * Return a string representing a text that exposes information about |
3066 | * the groups that the \a node is part of. |
3067 | */ |
3068 | void DocBookGenerator::generateGroupReferenceText(const Node* node) |
3069 | { |
3070 | // From HtmlGenerator::groupReferenceText |
3071 | |
3072 | if (!node->isAggregate()) |
3073 | return; |
3074 | const auto aggregate = static_cast<const Aggregate *>(node); |
3075 | |
3076 | const QStringList &groups_names{aggregate->groupNames()}; |
3077 | if (!groups_names.empty()) { |
3078 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "para" ); |
3079 | m_writer->writeCharacters(text: aggregate->name() + " is part of " ); |
3080 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "simplelist" ); |
3081 | |
3082 | for (qsizetype index{0}; index < groups_names.size(); ++index) { |
3083 | CollectionNode* group{m_qdb->groups()[groups_names[index]]}; |
3084 | m_qdb->mergeCollections(c: group); |
3085 | |
3086 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "member" ); |
3087 | if (QString target{linkForNode(node: group, relative: nullptr)}; !target.isEmpty()) |
3088 | generateSimpleLink(href: target, text: group->fullTitle()); |
3089 | else |
3090 | m_writer->writeCharacters(text: group->name()); |
3091 | m_writer->writeEndElement(); // member |
3092 | } |
3093 | |
3094 | m_writer->writeEndElement(); // simplelist |
3095 | m_writer->writeEndElement(); // para |
3096 | newLine(); |
3097 | } |
3098 | } |
3099 | |
3100 | /*! |
3101 | Generates text that explains how threadsafe and/or reentrant |
3102 | \a node is. |
3103 | */ |
3104 | bool DocBookGenerator::generateThreadSafeness(const Node *node) |
3105 | { |
3106 | // From Generator::generateThreadSafeness |
3107 | Node::ThreadSafeness ts = node->threadSafeness(); |
3108 | |
3109 | const Node *reentrantNode; |
3110 | Atom reentrantAtom = Atom(Atom::Link, "reentrant" ); |
3111 | QString linkReentrant = getAutoLink(atom: &reentrantAtom, relative: node, node: &reentrantNode); |
3112 | const Node *threadSafeNode; |
3113 | Atom threadSafeAtom = Atom(Atom::Link, "thread-safe" ); |
3114 | QString linkThreadSafe = getAutoLink(atom: &threadSafeAtom, relative: node, node: &threadSafeNode); |
3115 | |
3116 | if (ts == Node::NonReentrant) { |
3117 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "warning" ); |
3118 | newLine(); |
3119 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "para" ); |
3120 | m_writer->writeCharacters(text: "This " + typeString(node) + " is not " ); |
3121 | generateSimpleLink(href: linkReentrant, text: "reentrant" ); |
3122 | m_writer->writeCharacters(text: "." ); |
3123 | m_writer->writeEndElement(); // para |
3124 | newLine(); |
3125 | m_writer->writeEndElement(); // warning |
3126 | |
3127 | return true; |
3128 | } else if (ts == Node::Reentrant || ts == Node::ThreadSafe) { |
3129 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "note" ); |
3130 | newLine(); |
3131 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "para" ); |
3132 | |
3133 | if (node->isAggregate()) { |
3134 | m_writer->writeCharacters(text: "All functions in this " + typeString(node) + " are " ); |
3135 | if (ts == Node::ThreadSafe) |
3136 | generateSimpleLink(href: linkThreadSafe, text: "thread-safe" ); |
3137 | else |
3138 | generateSimpleLink(href: linkReentrant, text: "reentrant" ); |
3139 | |
3140 | NodeList reentrant; |
3141 | NodeList threadsafe; |
3142 | NodeList nonreentrant; |
3143 | bool exceptions = hasExceptions(node, reentrant, threadsafe, nonreentrant); |
3144 | if (!exceptions || (ts == Node::Reentrant && !threadsafe.isEmpty())) { |
3145 | m_writer->writeCharacters(text: "." ); |
3146 | m_writer->writeEndElement(); // para |
3147 | newLine(); |
3148 | } else { |
3149 | m_writer->writeCharacters(text: " with the following exceptions:" ); |
3150 | m_writer->writeEndElement(); // para |
3151 | newLine(); |
3152 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "para" ); |
3153 | |
3154 | if (ts == Node::Reentrant) { |
3155 | if (!nonreentrant.isEmpty()) { |
3156 | m_writer->writeCharacters(text: "These functions are not " ); |
3157 | generateSimpleLink(href: linkReentrant, text: "reentrant" ); |
3158 | m_writer->writeCharacters(text: ":" ); |
3159 | m_writer->writeEndElement(); // para |
3160 | newLine(); |
3161 | generateSignatureList(nodes: nonreentrant); |
3162 | } |
3163 | if (!threadsafe.isEmpty()) { |
3164 | m_writer->writeCharacters(text: "These functions are also " ); |
3165 | generateSimpleLink(href: linkThreadSafe, text: "thread-safe" ); |
3166 | m_writer->writeCharacters(text: ":" ); |
3167 | m_writer->writeEndElement(); // para |
3168 | newLine(); |
3169 | generateSignatureList(nodes: threadsafe); |
3170 | } |
3171 | } else { // thread-safe |
3172 | if (!reentrant.isEmpty()) { |
3173 | m_writer->writeCharacters(text: "These functions are only " ); |
3174 | generateSimpleLink(href: linkReentrant, text: "reentrant" ); |
3175 | m_writer->writeCharacters(text: ":" ); |
3176 | m_writer->writeEndElement(); // para |
3177 | newLine(); |
3178 | generateSignatureList(nodes: reentrant); |
3179 | } |
3180 | if (!nonreentrant.isEmpty()) { |
3181 | m_writer->writeCharacters(text: "These functions are not " ); |
3182 | generateSimpleLink(href: linkReentrant, text: "reentrant" ); |
3183 | m_writer->writeCharacters(text: ":" ); |
3184 | m_writer->writeEndElement(); // para |
3185 | newLine(); |
3186 | generateSignatureList(nodes: nonreentrant); |
3187 | } |
3188 | } |
3189 | } |
3190 | } else { |
3191 | m_writer->writeCharacters(text: "This " + typeString(node) + " is " ); |
3192 | if (ts == Node::ThreadSafe) |
3193 | generateSimpleLink(href: linkThreadSafe, text: "thread-safe" ); |
3194 | else |
3195 | generateSimpleLink(href: linkReentrant, text: "reentrant" ); |
3196 | m_writer->writeCharacters(text: "." ); |
3197 | m_writer->writeEndElement(); // para |
3198 | newLine(); |
3199 | } |
3200 | m_writer->writeEndElement(); // note |
3201 | newLine(); |
3202 | |
3203 | return true; |
3204 | } |
3205 | |
3206 | return false; |
3207 | } |
3208 | |
3209 | /*! |
3210 | Generate the body of the documentation from the qdoc comment |
3211 | found with the entity represented by the \a node. |
3212 | */ |
3213 | void DocBookGenerator::generateBody(const Node *node) |
3214 | { |
3215 | // From Generator::generateBody, without warnings. |
3216 | const FunctionNode *fn = node->isFunction() ? static_cast<const FunctionNode *>(node) : nullptr; |
3217 | |
3218 | if (!node->hasDoc() && !node->hasSharedDoc()) { |
3219 | /* |
3220 | Test for special function, like a destructor or copy constructor, |
3221 | that has no documentation. |
3222 | */ |
3223 | if (fn) { |
3224 | QString t; |
3225 | if (fn->isDtor()) { |
3226 | t = "Destroys the instance of " + fn->parent()->name() + "." ; |
3227 | if (fn->isVirtual()) |
3228 | t += " The destructor is virtual." ; |
3229 | } else if (fn->isCtor()) { |
3230 | t = "Default constructs an instance of " + fn->parent()->name() + "." ; |
3231 | } else if (fn->isCCtor()) { |
3232 | t = "Copy constructor." ; |
3233 | } else if (fn->isMCtor()) { |
3234 | t = "Move-copy constructor." ; |
3235 | } else if (fn->isCAssign()) { |
3236 | t = "Copy-assignment constructor." ; |
3237 | } else if (fn->isMAssign()) { |
3238 | t = "Move-assignment constructor." ; |
3239 | } |
3240 | |
3241 | if (!t.isEmpty()) |
3242 | m_writer->writeTextElement(namespaceUri: dbNamespace, name: "para" , text: t); |
3243 | } |
3244 | } else if (!node->isSharingComment()) { |
3245 | // Reimplements clause and type alias info precede body text |
3246 | if (fn && !fn->overridesThis().isEmpty()) |
3247 | generateReimplementsClause(fn); |
3248 | else if (node->isProperty()) { |
3249 | if (static_cast<const PropertyNode *>(node)->propertyType() != PropertyNode::PropertyType::StandardProperty) |
3250 | generateAddendum(node, type: BindableProperty, marker: nullptr, generateNote: false); |
3251 | } |
3252 | |
3253 | // Generate the body. |
3254 | if (!generateText(text: node->doc().body(), relative: node)) { |
3255 | if (node->isMarkedReimp()) |
3256 | return; |
3257 | } |
3258 | |
3259 | // Output what is after the main body. |
3260 | if (fn) { |
3261 | if (fn->isQmlSignal()) |
3262 | generateAddendum(node, type: QmlSignalHandler, marker: nullptr, generateNote: true); |
3263 | if (fn->isPrivateSignal()) |
3264 | generateAddendum(node, type: PrivateSignal, marker: nullptr, generateNote: true); |
3265 | if (fn->isInvokable()) |
3266 | generateAddendum(node, type: Invokable, marker: nullptr, generateNote: true); |
3267 | if (fn->hasAssociatedProperties()) |
3268 | generateAddendum(node, type: AssociatedProperties, marker: nullptr, generateNote: true); |
3269 | } |
3270 | |
3271 | // Warning generation skipped with respect to Generator::generateBody. |
3272 | } |
3273 | |
3274 | generateRequiredLinks(node); |
3275 | } |
3276 | |
3277 | /*! |
3278 | Generates either a link to the project folder for example \a node, or a list |
3279 | of links files/images if 'url.examples config' variable is not defined. |
3280 | |
3281 | Does nothing for non-example nodes. |
3282 | */ |
3283 | void DocBookGenerator::generateRequiredLinks(const Node *node) |
3284 | { |
3285 | // From Generator::generateRequiredLinks. |
3286 | if (!node->isExample()) |
3287 | return; |
3288 | |
3289 | const auto en = static_cast<const ExampleNode *>(node); |
3290 | QString exampleUrl{Config::instance().get(CONFIG_URL + Config::dot + CONFIG_EXAMPLES).asString()}; |
3291 | |
3292 | if (exampleUrl.isEmpty()) { |
3293 | if (!en->noAutoList()) { |
3294 | generateFileList(en, images: false); // files |
3295 | generateFileList(en, images: true); // images |
3296 | } |
3297 | } else { |
3298 | generateLinkToExample(en, baseUrl: exampleUrl); |
3299 | } |
3300 | } |
3301 | |
3302 | /*! |
3303 | The path to the example replaces a placeholder '\1' character if |
3304 | one is found in the \a baseUrl string. If no such placeholder is found, |
3305 | the path is appended to \a baseUrl, after a '/' character if \a baseUrl did |
3306 | not already end in one. |
3307 | */ |
3308 | void DocBookGenerator::generateLinkToExample(const ExampleNode *en, const QString &baseUrl) |
3309 | { |
3310 | // From Generator::generateLinkToExample. |
3311 | QString exampleUrl(baseUrl); |
3312 | QString link; |
3313 | #ifndef QT_BOOTSTRAPPED |
3314 | link = QUrl(exampleUrl).host(); |
3315 | #endif |
3316 | if (!link.isEmpty()) |
3317 | link.prepend(s: " @ " ); |
3318 | link.prepend(s: "Example project" ); |
3319 | |
3320 | const QLatin1Char separator('/'); |
3321 | const QLatin1Char placeholder('\1'); |
3322 | if (!exampleUrl.contains(c: placeholder)) { |
3323 | if (!exampleUrl.endsWith(c: separator)) |
3324 | exampleUrl += separator; |
3325 | exampleUrl += placeholder; |
3326 | } |
3327 | |
3328 | // Construct a path to the example; <install path>/<example name> |
3329 | QStringList path = QStringList() |
3330 | << Config::instance().get(CONFIG_EXAMPLESINSTALLPATH).asString() << en->name(); |
3331 | path.removeAll(t: QString()); |
3332 | |
3333 | // Write the link to the example. Typically, this link comes after sections, hence |
3334 | // wrap it in a section too. |
3335 | startSection(title: "Example project" ); |
3336 | |
3337 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "para" ); |
3338 | generateSimpleLink(href: exampleUrl.replace(c: placeholder, after: path.join(sep: separator)), text: link); |
3339 | m_writer->writeEndElement(); // para |
3340 | newLine(); |
3341 | |
3342 | endSection(); |
3343 | } |
3344 | |
3345 | // TODO: [multi-purpose-function-with-flag][generate-file-list] |
3346 | |
3347 | /*! |
3348 | This function is called when the documentation for an example is |
3349 | being formatted. It outputs a list of files for the example, which |
3350 | can be the example's source files or the list of images used by the |
3351 | example. The images are copied into a subtree of |
3352 | \c{...doc/html/images/used-in-examples/...} |
3353 | */ |
3354 | void DocBookGenerator::generateFileList(const ExampleNode *en, bool images) |
3355 | { |
3356 | // TODO: [possibly-stale-duplicate-code][generator-insufficient-structural-abstraction] |
3357 | // Review and compare this code with |
3358 | // Generator::generateFileList. |
3359 | // Some subtle changes that might be semantically equivalent are |
3360 | // present between the two. |
3361 | // Supposedly, this version is to be considered stale compared to |
3362 | // Generator's one and it might be possible to remove it in favor |
3363 | // of that as long as the difference in output are taken into consideration. |
3364 | |
3365 | // From Generator::generateFileList |
3366 | QString tag; |
3367 | QStringList paths; |
3368 | if (images) { |
3369 | paths = en->images(); |
3370 | tag = "Images:" ; |
3371 | } else { // files |
3372 | paths = en->files(); |
3373 | tag = "Files:" ; |
3374 | } |
3375 | std::sort(first: paths.begin(), last: paths.end(), comp: Generator::comparePaths); |
3376 | |
3377 | if (paths.isEmpty()) |
3378 | return; |
3379 | |
3380 | startSection(id: "" , title: "List of Files" ); |
3381 | |
3382 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "para" ); |
3383 | m_writer->writeCharacters(text: tag); |
3384 | m_writer->writeEndElement(); // para |
3385 | newLine(); |
3386 | |
3387 | startSection(title: "List of Files" ); |
3388 | |
3389 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "itemizedlist" ); |
3390 | newLine(); |
3391 | |
3392 | for (const auto &path : std::as_const(t&: paths)) { |
3393 | auto maybe_resolved_file{file_resolver.resolve(filename: path)}; |
3394 | if (!maybe_resolved_file) { |
3395 | // TODO: [uncentralized-admonition][failed-resolve-file] |
3396 | QString details = std::transform_reduce( |
3397 | first: file_resolver.get_search_directories().cbegin(), |
3398 | last: file_resolver.get_search_directories().cend(), |
3399 | init: u"Searched directories:"_s , |
3400 | binary_op: std::plus(), |
3401 | unary_op: [](const DirectoryPath &directory_path) -> QString { return u' ' + directory_path.value(); } |
3402 | ); |
3403 | |
3404 | en->location().warning(message: u"Cannot find file to quote from: %1"_s .arg(a: path), details); |
3405 | |
3406 | continue; |
3407 | } |
3408 | |
3409 | auto file{*maybe_resolved_file}; |
3410 | if (images) addImageToCopy(en, resolved_file: file); |
3411 | else generateExampleFilePage(en, resolved_file: file); |
3412 | |
3413 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "listitem" ); |
3414 | newLine(); |
3415 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "para" ); |
3416 | generateSimpleLink(href: file.get_query(), text: file.get_query()); |
3417 | m_writer->writeEndElement(); // para |
3418 | m_writer->writeEndElement(); // listitem |
3419 | newLine(); |
3420 | } |
3421 | |
3422 | m_writer->writeEndElement(); // itemizedlist |
3423 | newLine(); |
3424 | |
3425 | endSection(); |
3426 | } |
3427 | |
3428 | /*! |
3429 | Generate a file with the contents of a C++ or QML source file. |
3430 | */ |
3431 | void DocBookGenerator::generateExampleFilePage(const Node *node, ResolvedFile resolved_file, CodeMarker*) |
3432 | { |
3433 | // TODO: [generator-insufficient-structural-abstraction] |
3434 | |
3435 | // From HtmlGenerator::generateExampleFilePage. |
3436 | if (!node->isExample()) |
3437 | return; |
3438 | |
3439 | // TODO: Understand if this is safe. |
3440 | const auto en = static_cast<const ExampleNode *>(node); |
3441 | |
3442 | // Store current (active) writer |
3443 | QXmlStreamWriter *currentWriter = m_writer; |
3444 | m_writer = startDocument(en, file: resolved_file.get_path()); |
3445 | generateHeader(title: en->fullTitle(), subTitle: en->subtitle(), node: en); |
3446 | |
3447 | Text text; |
3448 | Quoter quoter; |
3449 | Doc::quoteFromFile(location: en->doc().location(), quoter, resolved_file); |
3450 | QString code = quoter.quoteTo(docLocation: en->location(), command: QString(), pattern: QString()); |
3451 | CodeMarker *codeMarker = CodeMarker::markerForFileName(fileName: resolved_file.get_path()); |
3452 | text << Atom(codeMarker->atomType(), code); |
3453 | Atom a(codeMarker->atomType(), code); |
3454 | generateText(text, relative: en); |
3455 | |
3456 | endDocument(); // Delete m_writer. |
3457 | m_writer = currentWriter; // Restore writer. |
3458 | } |
3459 | |
3460 | void DocBookGenerator::generateReimplementsClause(const FunctionNode *fn) |
3461 | { |
3462 | // From Generator::generateReimplementsClause, without warning generation. |
3463 | if (fn->overridesThis().isEmpty() || !fn->parent()->isClassNode()) |
3464 | return; |
3465 | |
3466 | auto cn = static_cast<ClassNode *>(fn->parent()); |
3467 | |
3468 | if (const FunctionNode *overrides = cn->findOverriddenFunction(fn); |
3469 | overrides && !overrides->isPrivate() && !overrides->parent()->isPrivate()) { |
3470 | if (overrides->hasDoc()) { |
3471 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "para" ); |
3472 | m_writer->writeCharacters(text: "Reimplements: " ); |
3473 | QString fullName = |
3474 | overrides->parent()->name() + "::" + overrides->signature(options: Node::SignaturePlain); |
3475 | generateFullName(apparentNode: overrides->parent(), fullName, actualNode: overrides); |
3476 | m_writer->writeCharacters(text: "." ); |
3477 | m_writer->writeEndElement(); // para |
3478 | newLine(); |
3479 | return; |
3480 | } |
3481 | } |
3482 | |
3483 | if (const PropertyNode *sameName = cn->findOverriddenProperty(fn); sameName && sameName->hasDoc()) { |
3484 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "para" ); |
3485 | m_writer->writeCharacters(text: "Reimplements an access function for property: " ); |
3486 | QString fullName = sameName->parent()->name() + "::" + sameName->name(); |
3487 | generateFullName(apparentNode: sameName->parent(), fullName, actualNode: sameName); |
3488 | m_writer->writeCharacters(text: "." ); |
3489 | m_writer->writeEndElement(); // para |
3490 | newLine(); |
3491 | return; |
3492 | } |
3493 | } |
3494 | |
3495 | void DocBookGenerator::generateAlsoList(const Node *node) |
3496 | { |
3497 | // From Generator::generateAlsoList. |
3498 | QList<Text> alsoList = node->doc().alsoList(); |
3499 | supplementAlsoList(node, alsoList); |
3500 | |
3501 | if (!alsoList.isEmpty()) { |
3502 | startSection(title: "See Also" ); |
3503 | |
3504 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "para" ); |
3505 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "emphasis" ); |
3506 | m_writer->writeCharacters(text: "See also " ); |
3507 | m_writer->writeEndElement(); // emphasis |
3508 | newLine(); |
3509 | |
3510 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "simplelist" ); |
3511 | m_writer->writeAttribute(qualifiedName: "type" , value: "vert" ); |
3512 | m_writer->writeAttribute(qualifiedName: "role" , value: "see-also" ); |
3513 | newLine(); |
3514 | |
3515 | for (const Text &text : alsoList) { |
3516 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "member" ); |
3517 | generateText(text, relative: node); |
3518 | m_writer->writeEndElement(); // member |
3519 | newLine(); |
3520 | } |
3521 | |
3522 | m_writer->writeEndElement(); // simplelist |
3523 | newLine(); |
3524 | |
3525 | m_writer->writeEndElement(); // para |
3526 | newLine(); |
3527 | |
3528 | endSection(); |
3529 | } |
3530 | } |
3531 | |
3532 | /*! |
3533 | Open a new file to write XML contents, including the DocBook |
3534 | opening tag. |
3535 | */ |
3536 | QXmlStreamWriter *DocBookGenerator::startGenericDocument(const Node *node, const QString &fileName) |
3537 | { |
3538 | QFile *outFile = openSubPageFile(node, fileName); |
3539 | m_writer = new QXmlStreamWriter(outFile); |
3540 | m_writer->setAutoFormatting(false); // We need a precise handling of line feeds. |
3541 | |
3542 | m_writer->writeStartDocument(); |
3543 | newLine(); |
3544 | m_writer->writeNamespace(namespaceUri: dbNamespace, prefix: "db" ); |
3545 | m_writer->writeNamespace(namespaceUri: xlinkNamespace, prefix: "xlink" ); |
3546 | if (m_useITS) |
3547 | m_writer->writeNamespace(namespaceUri: itsNamespace, prefix: "its" ); |
3548 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "article" ); |
3549 | m_writer->writeAttribute(qualifiedName: "version" , value: "5.2" ); |
3550 | if (!m_naturalLanguage.isEmpty()) |
3551 | m_writer->writeAttribute(qualifiedName: "xml:lang" , value: m_naturalLanguage); |
3552 | newLine(); |
3553 | |
3554 | // Reset the state for the new document. |
3555 | sectionLevels.resize(size: 0); |
3556 | m_inPara = false; |
3557 | m_inList = 0; |
3558 | |
3559 | return m_writer; |
3560 | } |
3561 | |
3562 | QXmlStreamWriter *DocBookGenerator::startDocument(const Node *node) |
3563 | { |
3564 | m_hasSection = false; |
3565 | refMap.clear(); |
3566 | |
3567 | QString fileName = Generator::fileName(node, extension: fileExtension()); |
3568 | return startGenericDocument(node, fileName); |
3569 | } |
3570 | |
3571 | QXmlStreamWriter *DocBookGenerator::startDocument(const ExampleNode *en, const QString &file) |
3572 | { |
3573 | m_hasSection = false; |
3574 | |
3575 | QString fileName = linkForExampleFile(path: file); |
3576 | return startGenericDocument(node: en, fileName); |
3577 | } |
3578 | |
3579 | void DocBookGenerator::endDocument() |
3580 | { |
3581 | m_writer->writeEndElement(); // article |
3582 | m_writer->writeEndDocument(); |
3583 | |
3584 | m_writer->device()->close(); |
3585 | delete m_writer->device(); |
3586 | delete m_writer; |
3587 | m_writer = nullptr; |
3588 | } |
3589 | |
3590 | /*! |
3591 | Generate a reference page for the C++ class, namespace, or |
3592 | header file documented in \a node. |
3593 | */ |
3594 | void DocBookGenerator::generateCppReferencePage(Node *node) |
3595 | { |
3596 | // Based on HtmlGenerator::generateCppReferencePage. |
3597 | Q_ASSERT(node->isAggregate()); |
3598 | const auto aggregate = static_cast<const Aggregate *>(node); |
3599 | |
3600 | QString title; |
3601 | QString rawTitle; |
3602 | QString fullTitle; |
3603 | if (aggregate->isNamespace()) { |
3604 | rawTitle = aggregate->plainName(); |
3605 | fullTitle = aggregate->plainFullName(); |
3606 | title = rawTitle + " Namespace" ; |
3607 | } else if (aggregate->isClass()) { |
3608 | rawTitle = aggregate->plainName(); |
3609 | QString templateDecl = node->templateDecl(); |
3610 | if (!templateDecl.isEmpty()) |
3611 | fullTitle = QString("%1 %2 " ).arg(args&: templateDecl, args: aggregate->typeWord(cap: false)); |
3612 | fullTitle += aggregate->plainFullName(); |
3613 | title = rawTitle + QLatin1Char(' ') + aggregate->typeWord(cap: true); |
3614 | } else if (aggregate->isHeader()) { |
3615 | title = fullTitle = rawTitle = aggregate->fullTitle(); |
3616 | } |
3617 | |
3618 | QString subtitleText; |
3619 | if (rawTitle != fullTitle) |
3620 | subtitleText = fullTitle; |
3621 | |
3622 | // Start producing the DocBook file. |
3623 | m_writer = startDocument(node); |
3624 | |
3625 | // Info container. |
3626 | generateHeader(title, subTitle: subtitleText, node: aggregate); |
3627 | |
3628 | generateRequisites(aggregate); |
3629 | generateStatus(node: aggregate); |
3630 | |
3631 | // Element synopsis. |
3632 | generateDocBookSynopsis(node); |
3633 | |
3634 | // Actual content. |
3635 | if (!aggregate->doc().isEmpty()) { |
3636 | startSection(id: "details" , title: "Detailed Description" ); |
3637 | |
3638 | generateBody(node: aggregate); |
3639 | generateAlsoList(node: aggregate); |
3640 | |
3641 | endSection(); |
3642 | } |
3643 | |
3644 | Sections sections(const_cast<Aggregate *>(aggregate)); |
3645 | SectionVector sectionVector = |
3646 | (aggregate->isNamespace() || aggregate->isHeader()) ? |
3647 | sections.stdDetailsSections() : |
3648 | sections.stdCppClassDetailsSections(); |
3649 | for (const Section §ion : sectionVector) { |
3650 | if (section.members().isEmpty()) |
3651 | continue; |
3652 | |
3653 | startSection(id: section.title().toLower(), title: section.title()); |
3654 | |
3655 | for (const Node *member : section.members()) { |
3656 | if (member->access() == Access::Private) // ### check necessary? |
3657 | continue; |
3658 | |
3659 | if (member->nodeType() != Node::Class) { |
3660 | // This function starts its own section. |
3661 | generateDetailedMember(node: member, relative: aggregate); |
3662 | } else { |
3663 | startSectionBegin(); |
3664 | m_writer->writeCharacters(text: "class " ); |
3665 | generateFullName(node: member, relative: aggregate); |
3666 | startSectionEnd(); |
3667 | |
3668 | generateBrief(node: member); |
3669 | |
3670 | endSection(); |
3671 | } |
3672 | } |
3673 | |
3674 | endSection(); |
3675 | } |
3676 | |
3677 | generateObsoleteMembers(sections); |
3678 | |
3679 | endDocument(); |
3680 | } |
3681 | |
3682 | void DocBookGenerator::generateSynopsisInfo(const QString &key, const QString &value) |
3683 | { |
3684 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "synopsisinfo" ); |
3685 | m_writer->writeAttribute(qualifiedName: "role" , value: key); |
3686 | m_writer->writeCharacters(text: value); |
3687 | m_writer->writeEndElement(); // synopsisinfo |
3688 | newLine(); |
3689 | } |
3690 | |
3691 | void DocBookGenerator::generateModifier(const QString &value) |
3692 | { |
3693 | m_writer->writeTextElement(namespaceUri: dbNamespace, name: "modifier" , text: value); |
3694 | newLine(); |
3695 | } |
3696 | |
3697 | /*! |
3698 | Generate the metadata for the given \a node in DocBook. |
3699 | */ |
3700 | void DocBookGenerator::generateDocBookSynopsis(const Node *node) |
3701 | { |
3702 | if (!node) |
3703 | return; |
3704 | |
3705 | // From Generator::generateStatus, HtmlGenerator::generateRequisites, |
3706 | // Generator::generateThreadSafeness, QDocIndexFiles::generateIndexSection. |
3707 | |
3708 | // This function is the major place where DocBook extensions are used. |
3709 | if (!m_useDocBook52) |
3710 | return; |
3711 | |
3712 | // Nothing to export in some cases. Note that isSharedCommentNode() returns |
3713 | // true also for QML property groups. |
3714 | if (node->isGroup() || node->isSharedCommentNode() || node->isModule() || node->isQmlModule() || node->isPageNode()) |
3715 | return; |
3716 | |
3717 | // Cast the node to several subtypes (null pointer if the node is not of the required type). |
3718 | const Aggregate *aggregate = |
3719 | node->isAggregate() ? static_cast<const Aggregate *>(node) : nullptr; |
3720 | const ClassNode *classNode = node->isClass() ? static_cast<const ClassNode *>(node) : nullptr; |
3721 | const FunctionNode *functionNode = |
3722 | node->isFunction() ? static_cast<const FunctionNode *>(node) : nullptr; |
3723 | const PropertyNode *propertyNode = |
3724 | node->isProperty() ? static_cast<const PropertyNode *>(node) : nullptr; |
3725 | const VariableNode *variableNode = |
3726 | node->isVariable() ? static_cast<const VariableNode *>(node) : nullptr; |
3727 | const EnumNode *enumNode = node->isEnumType() ? static_cast<const EnumNode *>(node) : nullptr; |
3728 | const QmlPropertyNode *qpn = |
3729 | node->isQmlProperty() ? static_cast<const QmlPropertyNode *>(node) : nullptr; |
3730 | const QmlTypeNode *qcn = node->isQmlType() ? static_cast<const QmlTypeNode *>(node) : nullptr; |
3731 | // Typedefs are ignored, as they correspond to enums. |
3732 | // Groups and modules are ignored. |
3733 | // Documents are ignored, they have no interesting metadata. |
3734 | |
3735 | // Start the synopsis tag. |
3736 | QString synopsisTag = nodeToSynopsisTag(node); |
3737 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: synopsisTag); |
3738 | newLine(); |
3739 | |
3740 | // Name and basic properties of each tag (like types and parameters). |
3741 | if (node->isClass()) { |
3742 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "ooclass" ); |
3743 | m_writer->writeTextElement(namespaceUri: dbNamespace, name: "classname" , text: node->plainName()); |
3744 | m_writer->writeEndElement(); // ooclass |
3745 | newLine(); |
3746 | } else if (node->isNamespace()) { |
3747 | m_writer->writeTextElement(namespaceUri: dbNamespace, name: "namespacename" , text: node->plainName()); |
3748 | newLine(); |
3749 | } else if (node->isQmlType()) { |
3750 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "ooclass" ); |
3751 | m_writer->writeTextElement(namespaceUri: dbNamespace, name: "classname" , text: node->plainName()); |
3752 | m_writer->writeEndElement(); // ooclass |
3753 | newLine(); |
3754 | if (!qcn->groupNames().isEmpty()) |
3755 | m_writer->writeAttribute(qualifiedName: "groups" , value: qcn->groupNames().join(sep: QLatin1Char(','))); |
3756 | } else if (node->isProperty()) { |
3757 | m_writer->writeTextElement(namespaceUri: dbNamespace, name: "modifier" , text: "(Qt property)" ); |
3758 | newLine(); |
3759 | m_writer->writeTextElement(namespaceUri: dbNamespace, name: "type" , text: propertyNode->dataType()); |
3760 | newLine(); |
3761 | m_writer->writeTextElement(namespaceUri: dbNamespace, name: "varname" , text: node->plainName()); |
3762 | newLine(); |
3763 | } else if (node->isVariable()) { |
3764 | if (variableNode->isStatic()) { |
3765 | m_writer->writeTextElement(namespaceUri: dbNamespace, name: "modifier" , text: "static" ); |
3766 | newLine(); |
3767 | } |
3768 | m_writer->writeTextElement(namespaceUri: dbNamespace, name: "type" , text: variableNode->dataType()); |
3769 | newLine(); |
3770 | m_writer->writeTextElement(namespaceUri: dbNamespace, name: "varname" , text: node->plainName()); |
3771 | newLine(); |
3772 | } else if (node->isEnumType()) { |
3773 | m_writer->writeTextElement(namespaceUri: dbNamespace, name: "enumname" , text: node->plainName()); |
3774 | newLine(); |
3775 | } else if (node->isQmlProperty()) { |
3776 | QString name = node->name(); |
3777 | if (qpn->isAttached()) |
3778 | name.prepend(s: qpn->element() + QLatin1Char('.')); |
3779 | |
3780 | m_writer->writeTextElement(namespaceUri: dbNamespace, name: "type" , text: qpn->dataType()); |
3781 | newLine(); |
3782 | m_writer->writeTextElement(namespaceUri: dbNamespace, name: "varname" , text: name); |
3783 | newLine(); |
3784 | |
3785 | if (qpn->isAttached()) { |
3786 | m_writer->writeTextElement(namespaceUri: dbNamespace, name: "modifier" , text: "attached" ); |
3787 | newLine(); |
3788 | } |
3789 | if (!(const_cast<QmlPropertyNode *>(qpn))->isReadOnly()) { |
3790 | m_writer->writeTextElement(namespaceUri: dbNamespace, name: "modifier" , text: "writable" ); |
3791 | newLine(); |
3792 | } |
3793 | if ((const_cast<QmlPropertyNode *>(qpn))->isRequired()) { |
3794 | m_writer->writeTextElement(namespaceUri: dbNamespace, name: "modifier" , text: "required" ); |
3795 | newLine(); |
3796 | } |
3797 | if (qpn->isReadOnly()) { |
3798 | generateModifier(value: "[read-only]" ); |
3799 | newLine(); |
3800 | } |
3801 | if (qpn->isDefault()) { |
3802 | generateModifier(value: "[default]" ); |
3803 | newLine(); |
3804 | } |
3805 | } else if (node->isFunction()) { |
3806 | if (functionNode->virtualness() != "non" ) |
3807 | generateModifier(value: "virtual" ); |
3808 | if (functionNode->isConst()) |
3809 | generateModifier(value: "const" ); |
3810 | if (functionNode->isStatic()) |
3811 | generateModifier(value: "static" ); |
3812 | |
3813 | if (!functionNode->isMacro() && !functionNode->isCtor() && |
3814 | !functionNode->isCCtor() && !functionNode->isMCtor() |
3815 | && !functionNode->isDtor()) { |
3816 | if (functionNode->returnType() == "void" ) |
3817 | m_writer->writeEmptyElement(namespaceUri: dbNamespace, name: "void" ); |
3818 | else |
3819 | m_writer->writeTextElement(namespaceUri: dbNamespace, name: "type" , text: functionNode->returnType()); |
3820 | newLine(); |
3821 | } |
3822 | // Remove two characters from the plain name to only get the name |
3823 | // of the method without parentheses (only for functions, not macros). |
3824 | QString name = node->plainName(); |
3825 | if (name.endsWith(s: "()" )) |
3826 | name.chop(n: 2); |
3827 | m_writer->writeTextElement(namespaceUri: dbNamespace, name: "methodname" , text: name); |
3828 | newLine(); |
3829 | |
3830 | if (functionNode->parameters().isEmpty()) { |
3831 | m_writer->writeEmptyElement(namespaceUri: dbNamespace, name: "void" ); |
3832 | newLine(); |
3833 | } |
3834 | |
3835 | const Parameters &lp = functionNode->parameters(); |
3836 | for (int i = 0; i < lp.count(); ++i) { |
3837 | const Parameter ¶meter = lp.at(i); |
3838 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "methodparam" ); |
3839 | newLine(); |
3840 | m_writer->writeTextElement(namespaceUri: dbNamespace, name: "type" , text: parameter.type()); |
3841 | newLine(); |
3842 | m_writer->writeTextElement(namespaceUri: dbNamespace, name: "parameter" , text: parameter.name()); |
3843 | newLine(); |
3844 | if (!parameter.defaultValue().isEmpty()) { |
3845 | m_writer->writeTextElement(namespaceUri: dbNamespace, name: "initializer" , text: parameter.defaultValue()); |
3846 | newLine(); |
3847 | } |
3848 | m_writer->writeEndElement(); // methodparam |
3849 | newLine(); |
3850 | } |
3851 | |
3852 | if (functionNode->isDefault()) |
3853 | generateModifier(value: "default" ); |
3854 | if (functionNode->isFinal()) |
3855 | generateModifier(value: "final" ); |
3856 | if (functionNode->isOverride()) |
3857 | generateModifier(value: "override" ); |
3858 | } else if (node->isTypedef()) { |
3859 | m_writer->writeTextElement(namespaceUri: dbNamespace, name: "typedefname" , text: node->plainName()); |
3860 | newLine(); |
3861 | } else { |
3862 | node->doc().location().warning( |
3863 | QStringLiteral("Unexpected node type in generateDocBookSynopsis: %1" ) |
3864 | .arg(a: node->nodeTypeString())); |
3865 | newLine(); |
3866 | } |
3867 | |
3868 | // Enums and typedefs. |
3869 | if (enumNode) { |
3870 | for (const EnumItem &item : enumNode->items()) { |
3871 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "enumitem" ); |
3872 | newLine(); |
3873 | m_writer->writeTextElement(namespaceUri: dbNamespace, name: "enumidentifier" , text: item.name()); |
3874 | newLine(); |
3875 | m_writer->writeTextElement(namespaceUri: dbNamespace, name: "enumvalue" , text: item.value()); |
3876 | newLine(); |
3877 | m_writer->writeEndElement(); // enumitem |
3878 | newLine(); |
3879 | } |
3880 | |
3881 | if (enumNode->items().isEmpty()) { |
3882 | // If the enumeration is empty (really rare case), still produce |
3883 | // something for the DocBook document to be valid. |
3884 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "enumitem" ); |
3885 | newLine(); |
3886 | m_writer->writeEmptyElement(namespaceUri: dbNamespace, name: "enumidentifier" ); |
3887 | newLine(); |
3888 | m_writer->writeEndElement(); // enumitem |
3889 | newLine(); |
3890 | } |
3891 | } |
3892 | |
3893 | // Below: only synopsisinfo within synopsisTag. These elements must be at |
3894 | // the end of the tag, as per DocBook grammar. |
3895 | |
3896 | // Information for functions that could not be output previously |
3897 | // (synopsisinfo). |
3898 | if (node->isFunction()) { |
3899 | generateSynopsisInfo(key: "meta" , value: functionNode->metanessString()); |
3900 | |
3901 | if (functionNode->isOverload()) { |
3902 | generateSynopsisInfo(key: "overload" , value: "overload" ); |
3903 | generateSynopsisInfo(key: "overload-number" , |
3904 | value: QString::number(functionNode->overloadNumber())); |
3905 | } |
3906 | |
3907 | if (functionNode->isRef()) |
3908 | generateSynopsisInfo(key: "refness" , value: QString::number(1)); |
3909 | else if (functionNode->isRefRef()) |
3910 | generateSynopsisInfo(key: "refness" , value: QString::number(2)); |
3911 | |
3912 | if (functionNode->hasAssociatedProperties()) { |
3913 | QStringList associatedProperties; |
3914 | const auto &nodes = functionNode->associatedProperties(); |
3915 | for (const Node *n : nodes) { |
3916 | const auto pn = static_cast<const PropertyNode *>(n); |
3917 | associatedProperties << pn->name(); |
3918 | } |
3919 | associatedProperties.sort(); |
3920 | generateSynopsisInfo(key: "associated-property" , |
3921 | value: associatedProperties.join(sep: QLatin1Char(','))); |
3922 | } |
3923 | |
3924 | QString signature = functionNode->signature(options: Node::SignatureReturnType); |
3925 | // 'const' is already part of FunctionNode::signature() |
3926 | if (functionNode->isFinal()) |
3927 | signature += " final" ; |
3928 | if (functionNode->isOverride()) |
3929 | signature += " override" ; |
3930 | if (functionNode->isPureVirtual()) |
3931 | signature += " = 0" ; |
3932 | else if (functionNode->isDefault()) |
3933 | signature += " = default" ; |
3934 | generateSynopsisInfo(key: "signature" , value: signature); |
3935 | } |
3936 | |
3937 | // Accessibility status. |
3938 | if (!node->isPageNode() && !node->isCollectionNode()) { |
3939 | switch (node->access()) { |
3940 | case Access::Public: |
3941 | generateSynopsisInfo(key: "access" , value: "public" ); |
3942 | break; |
3943 | case Access::Protected: |
3944 | generateSynopsisInfo(key: "access" , value: "protected" ); |
3945 | break; |
3946 | case Access::Private: |
3947 | generateSynopsisInfo(key: "access" , value: "private" ); |
3948 | break; |
3949 | default: |
3950 | break; |
3951 | } |
3952 | if (node->isAbstract()) |
3953 | generateSynopsisInfo(key: "abstract" , value: "true" ); |
3954 | } |
3955 | |
3956 | // Status. |
3957 | switch (node->status()) { |
3958 | case Node::Active: |
3959 | generateSynopsisInfo(key: "status" , value: "active" ); |
3960 | break; |
3961 | case Node::Preliminary: |
3962 | generateSynopsisInfo(key: "status" , value: "preliminary" ); |
3963 | break; |
3964 | case Node::Deprecated: |
3965 | generateSynopsisInfo(key: "status" , value: "deprecated" ); |
3966 | break; |
3967 | case Node::Internal: |
3968 | generateSynopsisInfo(key: "status" , value: "internal" ); |
3969 | break; |
3970 | default: |
3971 | generateSynopsisInfo(key: "status" , value: "main" ); |
3972 | break; |
3973 | } |
3974 | |
3975 | // C++ classes and name spaces. |
3976 | if (aggregate) { |
3977 | // Includes. |
3978 | if (aggregate->includeFile()) generateSynopsisInfo(key: "headers" , value: *aggregate->includeFile()); |
3979 | |
3980 | // Since and project. |
3981 | if (!aggregate->since().isEmpty()) |
3982 | generateSynopsisInfo(key: "since" , value: formatSince(node: aggregate)); |
3983 | |
3984 | if (aggregate->nodeType() == Node::Class || aggregate->nodeType() == Node::Namespace) { |
3985 | // CMake and QT variable. |
3986 | if (!aggregate->physicalModuleName().isEmpty()) { |
3987 | const CollectionNode *cn = |
3988 | m_qdb->getCollectionNode(name: aggregate->physicalModuleName(), type: Node::Module); |
3989 | if (cn && !cn->qtCMakeComponent().isEmpty()) { |
3990 | const QString qtComponent = "Qt" + QString::number(QT_VERSION_MAJOR); |
3991 | const QString findpackageText = "find_package(" + qtComponent |
3992 | + " REQUIRED COMPONENTS " + cn->qtCMakeComponent() + ")" ; |
3993 | const QString targetLinkLibrariesText = |
3994 | "target_link_libraries(mytarget PRIVATE " + qtComponent + "::" + cn->qtCMakeComponent() |
3995 | + ")" ; |
3996 | generateSynopsisInfo(key: "cmake-find-package" , value: findpackageText); |
3997 | generateSynopsisInfo(key: "cmake-target-link-libraries" , value: targetLinkLibrariesText); |
3998 | } |
3999 | if (cn && !cn->qtVariable().isEmpty()) |
4000 | generateSynopsisInfo(key: "qmake" , value: "QT += " + cn->qtVariable()); |
4001 | } |
4002 | } |
4003 | |
4004 | if (aggregate->nodeType() == Node::Class) { |
4005 | // Instantiated by. |
4006 | auto *classe = const_cast<ClassNode *>(static_cast<const ClassNode *>(aggregate)); |
4007 | if (classe->qmlElement() != nullptr && classe->status() != Node::Internal) { |
4008 | const Node *otherNode = nullptr; |
4009 | Atom a = Atom(Atom::LinkNode, CodeMarker::stringForNode(node: classe->qmlElement())); |
4010 | QString link = getAutoLink(atom: &a, relative: aggregate, node: &otherNode); |
4011 | |
4012 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "synopsisinfo" ); |
4013 | m_writer->writeAttribute(qualifiedName: "role" , value: "instantiatedBy" ); |
4014 | generateSimpleLink(href: link, text: classe->qmlElement()->name()); |
4015 | m_writer->writeEndElement(); // synopsisinfo |
4016 | newLine(); |
4017 | } |
4018 | |
4019 | // Inherits. |
4020 | QList<RelatedClass>::ConstIterator r; |
4021 | if (!classe->baseClasses().isEmpty()) { |
4022 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "synopsisinfo" ); |
4023 | m_writer->writeAttribute(qualifiedName: "role" , value: "inherits" ); |
4024 | |
4025 | r = classe->baseClasses().constBegin(); |
4026 | int index = 0; |
4027 | while (r != classe->baseClasses().constEnd()) { |
4028 | if ((*r).m_node) { |
4029 | generateFullName(node: (*r).m_node, relative: classe); |
4030 | |
4031 | if ((*r).m_access == Access::Protected) { |
4032 | m_writer->writeCharacters(text: " (protected)" ); |
4033 | } else if ((*r).m_access == Access::Private) { |
4034 | m_writer->writeCharacters(text: " (private)" ); |
4035 | } |
4036 | m_writer->writeCharacters( |
4037 | text: Utilities::comma(wordPosition: index++, numberOfWords: classe->baseClasses().size())); |
4038 | } |
4039 | ++r; |
4040 | } |
4041 | |
4042 | m_writer->writeEndElement(); // synopsisinfo |
4043 | newLine(); |
4044 | } |
4045 | |
4046 | // Inherited by. |
4047 | if (!classe->derivedClasses().isEmpty()) { |
4048 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "synopsisinfo" ); |
4049 | m_writer->writeAttribute(qualifiedName: "role" , value: "inheritedBy" ); |
4050 | generateSortedNames(cn: classe, rc: classe->derivedClasses()); |
4051 | m_writer->writeEndElement(); // synopsisinfo |
4052 | newLine(); |
4053 | } |
4054 | } |
4055 | } |
4056 | |
4057 | // QML types. |
4058 | if (qcn) { |
4059 | // Module name and version (i.e. import). |
4060 | QString logicalModuleVersion; |
4061 | const CollectionNode *collection = |
4062 | m_qdb->getCollectionNode(name: qcn->logicalModuleName(), type: qcn->nodeType()); |
4063 | if (collection) |
4064 | logicalModuleVersion = collection->logicalModuleVersion(); |
4065 | else |
4066 | logicalModuleVersion = qcn->logicalModuleVersion(); |
4067 | |
4068 | QStringList importText; |
4069 | importText << "import " + qcn->logicalModuleName(); |
4070 | if (!logicalModuleVersion.isEmpty()) |
4071 | importText << logicalModuleVersion; |
4072 | generateSynopsisInfo(key: "import" , value: importText.join(sep: ' ')); |
4073 | |
4074 | // Since and project. |
4075 | if (!qcn->since().isEmpty()) |
4076 | generateSynopsisInfo(key: "since" , value: formatSince(node: qcn)); |
4077 | |
4078 | // Inherited by. |
4079 | NodeList subs; |
4080 | QmlTypeNode::subclasses(base: qcn, subs); |
4081 | if (!subs.isEmpty()) { |
4082 | m_writer->writeTextElement(qualifiedName: dbNamespace, text: "synopsisinfo" ); |
4083 | m_writer->writeAttribute(qualifiedName: "role" , value: "inheritedBy" ); |
4084 | generateSortedQmlNames(base: qcn, subs); |
4085 | m_writer->writeEndElement(); // synopsisinfo |
4086 | newLine(); |
4087 | } |
4088 | |
4089 | // Inherits. |
4090 | QmlTypeNode *base = qcn->qmlBaseNode(); |
4091 | while (base && base->isInternal()) |
4092 | base = base->qmlBaseNode(); |
4093 | if (base) { |
4094 | const Node *otherNode = nullptr; |
4095 | Atom a = Atom(Atom::LinkNode, CodeMarker::stringForNode(node: base)); |
4096 | QString link = getAutoLink(atom: &a, relative: base, node: &otherNode); |
4097 | |
4098 | m_writer->writeTextElement(qualifiedName: dbNamespace, text: "synopsisinfo" ); |
4099 | m_writer->writeAttribute(qualifiedName: "role" , value: "inherits" ); |
4100 | generateSimpleLink(href: link, text: base->name()); |
4101 | m_writer->writeEndElement(); // synopsisinfo |
4102 | newLine(); |
4103 | } |
4104 | |
4105 | // Instantiates. |
4106 | ClassNode *cn = (const_cast<QmlTypeNode *>(qcn))->classNode(); |
4107 | if (cn && (cn->status() != Node::Internal)) { |
4108 | const Node *otherNode = nullptr; |
4109 | Atom a = Atom(Atom::LinkNode, CodeMarker::stringForNode(node: qcn)); |
4110 | QString link = getAutoLink(atom: &a, relative: cn, node: &otherNode); |
4111 | |
4112 | m_writer->writeTextElement(qualifiedName: dbNamespace, text: "synopsisinfo" ); |
4113 | m_writer->writeAttribute(qualifiedName: "role" , value: "instantiates" ); |
4114 | generateSimpleLink(href: link, text: cn->name()); |
4115 | m_writer->writeEndElement(); // synopsisinfo |
4116 | newLine(); |
4117 | } |
4118 | } |
4119 | |
4120 | // Thread safeness. |
4121 | switch (node->threadSafeness()) { |
4122 | case Node::UnspecifiedSafeness: |
4123 | generateSynopsisInfo(key: "threadsafeness" , value: "unspecified" ); |
4124 | break; |
4125 | case Node::NonReentrant: |
4126 | generateSynopsisInfo(key: "threadsafeness" , value: "non-reentrant" ); |
4127 | break; |
4128 | case Node::Reentrant: |
4129 | generateSynopsisInfo(key: "threadsafeness" , value: "reentrant" ); |
4130 | break; |
4131 | case Node::ThreadSafe: |
4132 | generateSynopsisInfo(key: "threadsafeness" , value: "thread safe" ); |
4133 | break; |
4134 | default: |
4135 | generateSynopsisInfo(key: "threadsafeness" , value: "unspecified" ); |
4136 | break; |
4137 | } |
4138 | |
4139 | // Module. |
4140 | if (!node->physicalModuleName().isEmpty()) |
4141 | generateSynopsisInfo(key: "module" , value: node->physicalModuleName()); |
4142 | |
4143 | // Group. |
4144 | if (classNode && !classNode->groupNames().isEmpty()) { |
4145 | generateSynopsisInfo(key: "groups" , value: classNode->groupNames().join(sep: QLatin1Char(','))); |
4146 | } else if (qcn && !qcn->groupNames().isEmpty()) { |
4147 | generateSynopsisInfo(key: "groups" , value: qcn->groupNames().join(sep: QLatin1Char(','))); |
4148 | } |
4149 | |
4150 | // Properties. |
4151 | if (propertyNode) { |
4152 | for (const Node *fnNode : propertyNode->getters()) { |
4153 | if (fnNode) { |
4154 | const auto funcNode = static_cast<const FunctionNode *>(fnNode); |
4155 | generateSynopsisInfo(key: "getter" , value: funcNode->name()); |
4156 | } |
4157 | } |
4158 | for (const Node *fnNode : propertyNode->setters()) { |
4159 | if (fnNode) { |
4160 | const auto funcNode = static_cast<const FunctionNode *>(fnNode); |
4161 | generateSynopsisInfo(key: "setter" , value: funcNode->name()); |
4162 | } |
4163 | } |
4164 | for (const Node *fnNode : propertyNode->resetters()) { |
4165 | if (fnNode) { |
4166 | const auto funcNode = static_cast<const FunctionNode *>(fnNode); |
4167 | generateSynopsisInfo(key: "resetter" , value: funcNode->name()); |
4168 | } |
4169 | } |
4170 | for (const Node *fnNode : propertyNode->notifiers()) { |
4171 | if (fnNode) { |
4172 | const auto funcNode = static_cast<const FunctionNode *>(fnNode); |
4173 | generateSynopsisInfo(key: "notifier" , value: funcNode->name()); |
4174 | } |
4175 | } |
4176 | } |
4177 | |
4178 | m_writer->writeEndElement(); // nodeToSynopsisTag (like classsynopsis) |
4179 | newLine(); |
4180 | |
4181 | // The typedef associated to this enum. It is output *after* the main tag, |
4182 | // i.e. it must be after the synopsisinfo. |
4183 | if (enumNode && enumNode->flagsType()) { |
4184 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "typedefsynopsis" ); |
4185 | newLine(); |
4186 | |
4187 | m_writer->writeTextElement(namespaceUri: dbNamespace, name: "typedefname" , |
4188 | text: enumNode->flagsType()->fullDocumentName()); |
4189 | newLine(); |
4190 | |
4191 | m_writer->writeEndElement(); // typedefsynopsis |
4192 | newLine(); |
4193 | } |
4194 | } |
4195 | |
4196 | QString taggedNode(const Node *node) |
4197 | { |
4198 | // From CodeMarker::taggedNode, but without the tag part (i.e. only the QML specific case |
4199 | // remaining). |
4200 | // TODO: find a better name for this. |
4201 | if (node->nodeType() == Node::QmlType && node->name().startsWith(s: QLatin1String("QML:" ))) |
4202 | return node->name().mid(position: 4); |
4203 | return node->name(); |
4204 | } |
4205 | |
4206 | /*! |
4207 | Parses a string with method/variable name and (return) type |
4208 | to include type tags. |
4209 | */ |
4210 | void DocBookGenerator::typified(const QString &string, const Node *relative, bool trailingSpace, |
4211 | bool generateType) |
4212 | { |
4213 | // Adapted from CodeMarker::typified and HtmlGenerator::highlightedCode. |
4214 | // Note: CppCodeMarker::markedUpIncludes is not needed for DocBook, as this part is natively |
4215 | // generated as DocBook. Hence, there is no need to reimplement <@headerfile> from |
4216 | // HtmlGenerator::highlightedCode. |
4217 | QString result; |
4218 | QString pendingWord; |
4219 | |
4220 | for (int i = 0; i <= string.size(); ++i) { |
4221 | QChar ch; |
4222 | if (i != string.size()) |
4223 | ch = string.at(i); |
4224 | |
4225 | QChar lower = ch.toLower(); |
4226 | if ((lower >= QLatin1Char('a') && lower <= QLatin1Char('z')) || ch.digitValue() >= 0 |
4227 | || ch == QLatin1Char('_') || ch == QLatin1Char(':')) { |
4228 | pendingWord += ch; |
4229 | } else { |
4230 | if (!pendingWord.isEmpty()) { |
4231 | bool isProbablyType = (pendingWord != QLatin1String("const" )); |
4232 | if (generateType && isProbablyType) { |
4233 | // Flush the current buffer. |
4234 | m_writer->writeCharacters(text: result); |
4235 | result.truncate(pos: 0); |
4236 | |
4237 | // Add the link, logic from HtmlGenerator::highlightedCode. |
4238 | const Node *n = m_qdb->findTypeNode(type: pendingWord, relative, genus: Node::DontCare); |
4239 | QString href; |
4240 | if (!(n && n->isQmlBasicType()) |
4241 | || (relative |
4242 | && (relative->genus() == n->genus() || Node::DontCare == n->genus()))) { |
4243 | href = linkForNode(node: n, relative); |
4244 | } |
4245 | |
4246 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "type" ); |
4247 | if (href.isEmpty()) |
4248 | m_writer->writeCharacters(text: pendingWord); |
4249 | else |
4250 | generateSimpleLink(href, text: pendingWord); |
4251 | m_writer->writeEndElement(); // type |
4252 | } else { |
4253 | result += pendingWord; |
4254 | } |
4255 | } |
4256 | pendingWord.clear(); |
4257 | |
4258 | if (ch.unicode() != '\0') |
4259 | result += ch; |
4260 | } |
4261 | } |
4262 | |
4263 | if (trailingSpace && string.size()) { |
4264 | if (!string.endsWith(c: QLatin1Char('*')) && !string.endsWith(c: QLatin1Char('&'))) |
4265 | result += QLatin1Char(' '); |
4266 | } |
4267 | |
4268 | m_writer->writeCharacters(text: result); |
4269 | } |
4270 | |
4271 | void DocBookGenerator::generateSynopsisName(const Node *node, const Node *relative, |
4272 | bool generateNameLink) |
4273 | { |
4274 | // Implements the rewriting of <@link> from HtmlGenerator::highlightedCode, only due to calls to |
4275 | // CodeMarker::linkTag in CppCodeMarker::markedUpSynopsis. |
4276 | QString name = taggedNode(node); |
4277 | |
4278 | if (!generateNameLink) { |
4279 | m_writer->writeCharacters(text: name); |
4280 | return; |
4281 | } |
4282 | |
4283 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "emphasis" ); |
4284 | m_writer->writeAttribute(qualifiedName: "role" , value: "bold" ); |
4285 | generateSimpleLink(href: linkForNode(node, relative), text: name); |
4286 | m_writer->writeEndElement(); // emphasis |
4287 | } |
4288 | |
4289 | void DocBookGenerator::generateParameter(const Parameter ¶meter, const Node *relative, |
4290 | bool , bool generateType) |
4291 | { |
4292 | const QString &pname = parameter.name(); |
4293 | const QString &ptype = parameter.type(); |
4294 | QString paramName; |
4295 | if (!pname.isEmpty()) { |
4296 | typified(string: ptype, relative, trailingSpace: true, generateType); |
4297 | paramName = pname; |
4298 | } else { |
4299 | paramName = ptype; |
4300 | } |
4301 | if (generateExtra || pname.isEmpty()) { |
4302 | // Look for the _ character in the member name followed by a number (or n): |
4303 | // this is intended to be rendered as a subscript. |
4304 | static const QRegularExpression sub("([a-z]+)_([0-9]+|n)" ); |
4305 | |
4306 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "emphasis" ); |
4307 | auto match = sub.match(subject: paramName); |
4308 | if (match.hasMatch()) { |
4309 | m_writer->writeCharacters(text: match.captured(nth: 0)); |
4310 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "subscript" ); |
4311 | m_writer->writeCharacters(text: match.captured(nth: 1)); |
4312 | m_writer->writeEndElement(); // subscript |
4313 | } else { |
4314 | m_writer->writeCharacters(text: paramName); |
4315 | } |
4316 | m_writer->writeEndElement(); // emphasis |
4317 | } |
4318 | |
4319 | const QString &pvalue = parameter.defaultValue(); |
4320 | if (generateExtra && !pvalue.isEmpty()) |
4321 | m_writer->writeCharacters(text: " = " + pvalue); |
4322 | } |
4323 | |
4324 | void DocBookGenerator::generateSynopsis(const Node *node, const Node *relative, |
4325 | Section::Style style) |
4326 | { |
4327 | // From HtmlGenerator::generateSynopsis (conditions written as booleans). |
4328 | const bool = style != Section::AllMembers; |
4329 | const bool generateType = style != Section::Details; |
4330 | const bool generateNameLink = style != Section::Details; |
4331 | |
4332 | // From CppCodeMarker::markedUpSynopsis, reversed the generation of "extra" and "synopsis". |
4333 | const int MaxEnumValues = 6; |
4334 | |
4335 | if (generateExtra) |
4336 | m_writer->writeCharacters(text: CodeMarker::extraSynopsis(node, style)); |
4337 | |
4338 | // Then generate the synopsis. |
4339 | QString namePrefix {}; |
4340 | if (style == Section::Details) { |
4341 | if (!node->isRelatedNonmember() && !node->isProxyNode() && !node->parent()->name().isEmpty() |
4342 | && !node->parent()->isHeader() && !node->isProperty() && !node->isQmlNode()) { |
4343 | namePrefix = taggedNode(node: node->parent()) + "::" ; |
4344 | } |
4345 | } |
4346 | |
4347 | switch (node->nodeType()) { |
4348 | case Node::Namespace: |
4349 | m_writer->writeCharacters(text: "namespace " ); |
4350 | m_writer->writeCharacters(text: namePrefix); |
4351 | generateSynopsisName(node, relative, generateNameLink); |
4352 | break; |
4353 | case Node::Class: |
4354 | m_writer->writeCharacters(text: "class " ); |
4355 | m_writer->writeCharacters(text: namePrefix); |
4356 | generateSynopsisName(node, relative, generateNameLink); |
4357 | break; |
4358 | case Node::Function: { |
4359 | const auto func = (const FunctionNode *)node; |
4360 | |
4361 | // First, the part coming before the name. |
4362 | if (style == Section::Summary || style == Section::Accessors) { |
4363 | if (!func->isNonvirtual()) |
4364 | m_writer->writeCharacters(QStringLiteral("virtual " )); |
4365 | } |
4366 | |
4367 | // Name and parameters. |
4368 | if (style != Section::AllMembers && !func->returnType().isEmpty()) |
4369 | typified(string: func->returnType(), relative, trailingSpace: true, generateType); |
4370 | m_writer->writeCharacters(text: namePrefix); |
4371 | generateSynopsisName(node, relative, generateNameLink); |
4372 | |
4373 | if (!func->isMacroWithoutParams()) { |
4374 | m_writer->writeCharacters(QStringLiteral("(" )); |
4375 | if (!func->parameters().isEmpty()) { |
4376 | const Parameters ¶meters = func->parameters(); |
4377 | for (int i = 0; i < parameters.count(); i++) { |
4378 | if (i > 0) |
4379 | m_writer->writeCharacters(QStringLiteral(", " )); |
4380 | generateParameter(parameter: parameters.at(i), relative, generateExtra, generateType); |
4381 | } |
4382 | } |
4383 | m_writer->writeCharacters(QStringLiteral(")" )); |
4384 | } |
4385 | |
4386 | if (func->isConst()) |
4387 | m_writer->writeCharacters(QStringLiteral(" const" )); |
4388 | |
4389 | if (style == Section::Summary || style == Section::Accessors) { |
4390 | // virtual is prepended, if needed. |
4391 | QString synopsis; |
4392 | if (func->isFinal()) |
4393 | synopsis += QStringLiteral(" final" ); |
4394 | if (func->isOverride()) |
4395 | synopsis += QStringLiteral(" override" ); |
4396 | if (func->isPureVirtual()) |
4397 | synopsis += QStringLiteral(" = 0" ); |
4398 | if (func->isRef()) |
4399 | synopsis += QStringLiteral(" &" ); |
4400 | else if (func->isRefRef()) |
4401 | synopsis += QStringLiteral(" &&" ); |
4402 | m_writer->writeCharacters(text: synopsis); |
4403 | } else if (style == Section::AllMembers) { |
4404 | if (!func->returnType().isEmpty() && func->returnType() != "void" ) { |
4405 | m_writer->writeCharacters(QStringLiteral(" : " )); |
4406 | typified(string: func->returnType(), relative, trailingSpace: false, generateType); |
4407 | } |
4408 | } else { |
4409 | QString synopsis; |
4410 | if (func->isRef()) |
4411 | synopsis += QStringLiteral(" &" ); |
4412 | else if (func->isRefRef()) |
4413 | synopsis += QStringLiteral(" &&" ); |
4414 | m_writer->writeCharacters(text: synopsis); |
4415 | } |
4416 | } break; |
4417 | case Node::Enum: { |
4418 | const auto enume = static_cast<const EnumNode *>(node); |
4419 | m_writer->writeCharacters(QStringLiteral("enum " )); |
4420 | m_writer->writeCharacters(text: namePrefix); |
4421 | generateSynopsisName(node, relative, generateNameLink); |
4422 | |
4423 | QString synopsis; |
4424 | if (style == Section::Summary) { |
4425 | synopsis += " { " ; |
4426 | |
4427 | QStringList documentedItems = enume->doc().enumItemNames(); |
4428 | if (documentedItems.isEmpty()) { |
4429 | const auto &enumItems = enume->items(); |
4430 | for (const auto &item : enumItems) |
4431 | documentedItems << item.name(); |
4432 | } |
4433 | const QStringList omitItems = enume->doc().omitEnumItemNames(); |
4434 | for (const auto &item : omitItems) |
4435 | documentedItems.removeAll(t: item); |
4436 | |
4437 | if (documentedItems.size() > MaxEnumValues) { |
4438 | // Take the last element and keep it safe, then elide the surplus. |
4439 | const QString last = documentedItems.last(); |
4440 | documentedItems = documentedItems.mid(pos: 0, len: MaxEnumValues - 1); |
4441 | documentedItems += "…" ; // Ellipsis: in HTML, …. |
4442 | documentedItems += last; |
4443 | } |
4444 | synopsis += documentedItems.join(sep: QLatin1String(", " )); |
4445 | |
4446 | if (!documentedItems.isEmpty()) |
4447 | synopsis += QLatin1Char(' '); |
4448 | synopsis += QLatin1Char('}'); |
4449 | } |
4450 | m_writer->writeCharacters(text: synopsis); |
4451 | } break; |
4452 | case Node::TypeAlias: { |
4453 | if (style == Section::Details) { |
4454 | const QString& templateDecl = node->templateDecl(); |
4455 | if (!templateDecl.isEmpty()) |
4456 | m_writer->writeCharacters(text: templateDecl + QLatin1Char(' ')); |
4457 | } |
4458 | m_writer->writeCharacters(text: namePrefix); |
4459 | generateSynopsisName(node, relative, generateNameLink); |
4460 | } break; |
4461 | case Node::Typedef: { |
4462 | if (static_cast<const TypedefNode *>(node)->associatedEnum()) |
4463 | m_writer->writeCharacters(text: "flags " ); |
4464 | m_writer->writeCharacters(text: namePrefix); |
4465 | generateSynopsisName(node, relative, generateNameLink); |
4466 | } break; |
4467 | case Node::Property: { |
4468 | const auto property = static_cast<const PropertyNode *>(node); |
4469 | m_writer->writeCharacters(text: namePrefix); |
4470 | generateSynopsisName(node, relative, generateNameLink); |
4471 | m_writer->writeCharacters(text: " : " ); |
4472 | typified(string: property->qualifiedDataType(), relative, trailingSpace: false, generateType); |
4473 | } break; |
4474 | case Node::Variable: { |
4475 | const auto variable = static_cast<const VariableNode *>(node); |
4476 | if (style == Section::AllMembers) { |
4477 | generateSynopsisName(node, relative, generateNameLink); |
4478 | m_writer->writeCharacters(text: " : " ); |
4479 | typified(string: variable->dataType(), relative, trailingSpace: false, generateType); |
4480 | } else { |
4481 | typified(string: variable->leftType(), relative, trailingSpace: false, generateType); |
4482 | m_writer->writeCharacters(text: " " ); |
4483 | m_writer->writeCharacters(text: namePrefix); |
4484 | generateSynopsisName(node, relative, generateNameLink); |
4485 | m_writer->writeCharacters(text: variable->rightType()); |
4486 | } |
4487 | } break; |
4488 | default: |
4489 | m_writer->writeCharacters(text: namePrefix); |
4490 | generateSynopsisName(node, relative, generateNameLink); |
4491 | } |
4492 | } |
4493 | |
4494 | void DocBookGenerator::generateEnumValue(const QString &enumValue, const Node *relative) |
4495 | { |
4496 | // From CppCodeMarker::markedUpEnumValue, simplifications from Generator::plainCode (removing |
4497 | // <@op>). With respect to CppCodeMarker::markedUpEnumValue, the order of generation of parents |
4498 | // must be reversed so that they are processed in the order |
4499 | if (!relative->isEnumType()) { |
4500 | m_writer->writeCharacters(text: enumValue); |
4501 | return; |
4502 | } |
4503 | |
4504 | QList<const Node *> parents; |
4505 | const Node *node = relative->parent(); |
4506 | while (!node->isHeader() && node->parent()) { |
4507 | parents.prepend(t: node); |
4508 | if (node->parent() == relative || node->parent()->name().isEmpty()) |
4509 | break; |
4510 | node = node->parent(); |
4511 | } |
4512 | if (static_cast<const EnumNode *>(relative)->isScoped()) |
4513 | parents << relative; |
4514 | |
4515 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "code" ); |
4516 | for (auto parent : parents) { |
4517 | generateSynopsisName(node: parent, relative, generateNameLink: true); |
4518 | m_writer->writeCharacters(text: "::" ); |
4519 | } |
4520 | |
4521 | m_writer->writeCharacters(text: enumValue); |
4522 | m_writer->writeEndElement(); // code |
4523 | } |
4524 | |
4525 | /*! |
4526 | If the node is an overloaded signal, and a node with an |
4527 | example on how to connect to it |
4528 | |
4529 | Someone didn't finish writing this comment, and I don't know what this |
4530 | function is supposed to do, so I have not tried to complete the comment |
4531 | yet. |
4532 | */ |
4533 | void DocBookGenerator::generateOverloadedSignal(const Node *node) |
4534 | { |
4535 | // From Generator::generateOverloadedSignal. |
4536 | QString code = getOverloadedSignalCode(node); |
4537 | if (code.isEmpty()) |
4538 | return; |
4539 | |
4540 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "note" ); |
4541 | newLine(); |
4542 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "para" ); |
4543 | m_writer->writeCharacters(text: "Signal " ); |
4544 | m_writer->writeTextElement(namespaceUri: dbNamespace, name: "emphasis" , text: node->name()); |
4545 | m_writer->writeCharacters(text: " is overloaded in this class. To connect to this " |
4546 | "signal by using the function pointer syntax, Qt " |
4547 | "provides a convenient helper for obtaining the " |
4548 | "function pointer as shown in this example:" ); |
4549 | m_writer->writeTextElement(namespaceUri: dbNamespace, name: "code" , text: code); |
4550 | m_writer->writeEndElement(); // para |
4551 | newLine(); |
4552 | m_writer->writeEndElement(); // note |
4553 | newLine(); |
4554 | } |
4555 | |
4556 | /*! |
4557 | Generates an addendum note of type \a type for \a node. \a marker |
4558 | is unused in this generator. |
4559 | */ |
4560 | void DocBookGenerator::generateAddendum(const Node *node, Addendum type, CodeMarker *marker, |
4561 | bool generateNote) |
4562 | { |
4563 | Q_UNUSED(marker) |
4564 | Q_ASSERT(node && !node->name().isEmpty()); |
4565 | if (generateNote) { |
4566 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "note" ); |
4567 | newLine(); |
4568 | } |
4569 | switch (type) { |
4570 | case Invokable: |
4571 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "para" ); |
4572 | m_writer->writeCharacters( |
4573 | text: "This function can be invoked via the meta-object system and from QML. See " ); |
4574 | generateSimpleLink(href: node->url(), text: "Q_INVOKABLE" ); |
4575 | m_writer->writeCharacters(text: "." ); |
4576 | m_writer->writeEndElement(); // para |
4577 | newLine(); |
4578 | break; |
4579 | case PrivateSignal: |
4580 | m_writer->writeTextElement( |
4581 | namespaceUri: dbNamespace, name: "para" , |
4582 | text: "This is a private signal. It can be used in signal connections but " |
4583 | "cannot be emitted by the user." ); |
4584 | break; |
4585 | case QmlSignalHandler: |
4586 | { |
4587 | QString handler(node->name()); |
4588 | int prefixLocation = handler.lastIndexOf(c: '.', from: -2) + 1; |
4589 | handler[prefixLocation] = handler[prefixLocation].toTitleCase(); |
4590 | handler.insert(i: prefixLocation, s: QLatin1String("on" )); |
4591 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "para" ); |
4592 | m_writer->writeCharacters(text: "The corresponding handler is " ); |
4593 | m_writer->writeTextElement(namespaceUri: dbNamespace, name: "code" , text: handler); |
4594 | m_writer->writeCharacters(text: "." ); |
4595 | m_writer->writeEndElement(); // para |
4596 | newLine(); |
4597 | break; |
4598 | } |
4599 | case AssociatedProperties: |
4600 | { |
4601 | if (!node->isFunction()) |
4602 | return; |
4603 | const auto *fn = static_cast<const FunctionNode *>(node); |
4604 | auto propertyNodes = fn->associatedProperties(); |
4605 | if (propertyNodes.isEmpty()) |
4606 | return; |
4607 | std::sort(first: propertyNodes.begin(), last: propertyNodes.end(), comp: Node::nodeNameLessThan); |
4608 | for (const auto propertyNode : std::as_const(t&: propertyNodes)) { |
4609 | QString msg; |
4610 | const auto pn = static_cast<const PropertyNode *>(propertyNode); |
4611 | switch (pn->role(functionNode: fn)) { |
4612 | case PropertyNode::FunctionRole::Getter: |
4613 | msg = QStringLiteral("Getter function" ); |
4614 | break; |
4615 | case PropertyNode::FunctionRole::Setter: |
4616 | msg = QStringLiteral("Setter function" ); |
4617 | break; |
4618 | case PropertyNode::FunctionRole::Resetter: |
4619 | msg = QStringLiteral("Resetter function" ); |
4620 | break; |
4621 | case PropertyNode::FunctionRole::Notifier: |
4622 | msg = QStringLiteral("Notifier signal" ); |
4623 | break; |
4624 | default: |
4625 | continue; |
4626 | } |
4627 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "para" ); |
4628 | m_writer->writeCharacters(text: msg + " for property " ); |
4629 | generateSimpleLink(href: linkForNode(node: pn, relative: nullptr), text: pn->name()); |
4630 | m_writer->writeCharacters(text: ". " ); |
4631 | m_writer->writeEndElement(); // para |
4632 | newLine(); |
4633 | } |
4634 | break; |
4635 | } |
4636 | case BindableProperty: |
4637 | { |
4638 | const Node *linkNode; |
4639 | Atom linkAtom = Atom(Atom::Link, "QProperty" ); |
4640 | QString link = getAutoLink(atom: &linkAtom, relative: node, node: &linkNode); |
4641 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "para" ); |
4642 | m_writer->writeCharacters(text: "This property supports " ); |
4643 | generateSimpleLink(href: link, text: "QProperty" ); |
4644 | m_writer->writeCharacters(text: " bindings." ); |
4645 | m_writer->writeEndElement(); // para |
4646 | newLine(); |
4647 | break; |
4648 | } |
4649 | default: |
4650 | break; |
4651 | } |
4652 | |
4653 | if (generateNote) { |
4654 | m_writer->writeEndElement(); // note |
4655 | newLine(); |
4656 | } |
4657 | } |
4658 | |
4659 | void DocBookGenerator::generateDetailedMember(const Node *node, const PageNode *relative) |
4660 | { |
4661 | // From HtmlGenerator::generateDetailedMember. |
4662 | bool closeSupplementarySection = false; |
4663 | |
4664 | if (node->isSharedCommentNode()) { |
4665 | const auto *scn = reinterpret_cast<const SharedCommentNode *>(node); |
4666 | const QList<Node *> &collective = scn->collective(); |
4667 | |
4668 | bool firstFunction = true; |
4669 | for (const auto *sharedNode : collective) { |
4670 | if (firstFunction) { |
4671 | startSectionBegin(node: sharedNode); |
4672 | } else { |
4673 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "bridgehead" ); |
4674 | m_writer->writeAttribute(qualifiedName: "renderas" , value: "sect2" ); |
4675 | writeXmlId(node: sharedNode); |
4676 | } |
4677 | if (m_useITS) |
4678 | m_writer->writeAttribute(namespaceUri: itsNamespace, name: "translate" , value: "no" ); |
4679 | |
4680 | generateSynopsis(node: sharedNode, relative, style: Section::Details); |
4681 | |
4682 | if (firstFunction) { |
4683 | startSectionEnd(); |
4684 | firstFunction = false; |
4685 | } else { |
4686 | m_writer->writeEndElement(); // bridgehead |
4687 | newLine(); |
4688 | } |
4689 | } |
4690 | } else { |
4691 | const EnumNode *etn; |
4692 | if (node->isEnumType() && (etn = static_cast<const EnumNode *>(node))->flagsType()) { |
4693 | startSectionBegin(node); |
4694 | if (m_useITS) |
4695 | m_writer->writeAttribute(namespaceUri: itsNamespace, name: "translate" , value: "no" ); |
4696 | generateSynopsis(node: etn, relative, style: Section::Details); |
4697 | startSectionEnd(); |
4698 | |
4699 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "bridgehead" ); |
4700 | m_writer->writeAttribute(qualifiedName: "renderas" , value: "sect2" ); |
4701 | generateSynopsis(node: etn->flagsType(), relative, style: Section::Details); |
4702 | m_writer->writeEndElement(); // bridgehead |
4703 | newLine(); |
4704 | } else { |
4705 | startSectionBegin(node); |
4706 | if (m_useITS) |
4707 | m_writer->writeAttribute(namespaceUri: itsNamespace, name: "translate" , value: "no" ); |
4708 | generateSynopsis(node, relative, style: Section::Details); |
4709 | startSectionEnd(); |
4710 | } |
4711 | } |
4712 | Q_ASSERT(m_hasSection); |
4713 | |
4714 | generateDocBookSynopsis(node); |
4715 | |
4716 | generateStatus(node); |
4717 | generateBody(node); |
4718 | |
4719 | // If the body ends with a section, the rest of the description must be wrapped in a section too. |
4720 | if (node->hasDoc() && node->doc().body().firstAtom() && node->doc().body().lastAtom()->type() == Atom::SectionRight) { |
4721 | closeSupplementarySection = true; |
4722 | startSection(id: "" , title: "Notes" ); |
4723 | } |
4724 | |
4725 | generateOverloadedSignal(node); |
4726 | generateThreadSafeness(node); |
4727 | generateSince(node); |
4728 | |
4729 | if (node->isProperty()) { |
4730 | const auto property = static_cast<const PropertyNode *>(node); |
4731 | if (property->propertyType() == PropertyNode::PropertyType::StandardProperty) { |
4732 | Section section("" , "" , "" , "" , Section::Accessors); |
4733 | |
4734 | section.appendMembers(nv: property->getters().toVector()); |
4735 | section.appendMembers(nv: property->setters().toVector()); |
4736 | section.appendMembers(nv: property->resetters().toVector()); |
4737 | |
4738 | if (!section.members().isEmpty()) { |
4739 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "para" ); |
4740 | newLine(); |
4741 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "emphasis" ); |
4742 | m_writer->writeAttribute(qualifiedName: "role" , value: "bold" ); |
4743 | m_writer->writeCharacters(text: "Access functions:" ); |
4744 | newLine(); |
4745 | m_writer->writeEndElement(); // emphasis |
4746 | newLine(); |
4747 | m_writer->writeEndElement(); // para |
4748 | newLine(); |
4749 | generateSectionList(section, relative: node); |
4750 | } |
4751 | |
4752 | Section notifiers("" , "" , "" , "" , Section::Accessors); |
4753 | notifiers.appendMembers(nv: property->notifiers().toVector()); |
4754 | |
4755 | if (!notifiers.members().isEmpty()) { |
4756 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "para" ); |
4757 | newLine(); |
4758 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "emphasis" ); |
4759 | m_writer->writeAttribute(qualifiedName: "role" , value: "bold" ); |
4760 | m_writer->writeCharacters(text: "Notifier signal:" ); |
4761 | newLine(); |
4762 | m_writer->writeEndElement(); // emphasis |
4763 | newLine(); |
4764 | m_writer->writeEndElement(); // para |
4765 | newLine(); |
4766 | generateSectionList(section: notifiers, relative: node); |
4767 | } |
4768 | } |
4769 | } else if (node->isEnumType()) { |
4770 | const auto en = static_cast<const EnumNode *>(node); |
4771 | |
4772 | if (m_qflagsHref.isEmpty()) { |
4773 | Node *qflags = m_qdb->findClassNode(path: QStringList("QFlags" )); |
4774 | if (qflags) |
4775 | m_qflagsHref = linkForNode(node: qflags, relative: nullptr); |
4776 | } |
4777 | |
4778 | if (en->flagsType()) { |
4779 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "para" ); |
4780 | m_writer->writeCharacters(text: "The " ); |
4781 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "code" ); |
4782 | m_writer->writeCharacters(text: en->flagsType()->name()); |
4783 | m_writer->writeEndElement(); // code |
4784 | m_writer->writeCharacters(text: " type is a typedef for " ); |
4785 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "code" ); |
4786 | generateSimpleLink(href: m_qflagsHref, text: "QFlags" ); |
4787 | m_writer->writeCharacters(text: "<" + en->name() + ">. " ); |
4788 | m_writer->writeEndElement(); // code |
4789 | m_writer->writeCharacters(text: "It stores an OR combination of " ); |
4790 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "code" ); |
4791 | m_writer->writeCharacters(text: en->name()); |
4792 | m_writer->writeEndElement(); // code |
4793 | m_writer->writeCharacters(text: " values." ); |
4794 | m_writer->writeEndElement(); // para |
4795 | newLine(); |
4796 | } |
4797 | } |
4798 | |
4799 | if (closeSupplementarySection) |
4800 | endSection(); |
4801 | |
4802 | // The list of linked pages is always in its own section. |
4803 | generateAlsoList(node); |
4804 | |
4805 | // Close the section for this member. |
4806 | endSection(); // section |
4807 | } |
4808 | |
4809 | void DocBookGenerator::generateSectionList(const Section §ion, const Node *relative, |
4810 | bool useObsoleteMembers) |
4811 | { |
4812 | // From HtmlGenerator::generateSectionList, just generating a list (not tables). |
4813 | const NodeVector &members = |
4814 | (useObsoleteMembers ? section.obsoleteMembers() : section.members()); |
4815 | if (!members.isEmpty()) { |
4816 | bool hasPrivateSignals = false; |
4817 | bool isInvokable = false; |
4818 | |
4819 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "itemizedlist" ); |
4820 | if (m_useITS) |
4821 | m_writer->writeAttribute(namespaceUri: itsNamespace, name: "translate" , value: "no" ); |
4822 | newLine(); |
4823 | |
4824 | NodeVector::ConstIterator m = members.constBegin(); |
4825 | while (m != members.constEnd()) { |
4826 | if ((*m)->access() == Access::Private) { |
4827 | ++m; |
4828 | continue; |
4829 | } |
4830 | |
4831 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "listitem" ); |
4832 | newLine(); |
4833 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "para" ); |
4834 | |
4835 | // prefix no more needed. |
4836 | generateSynopsis(node: *m, relative, style: section.style()); |
4837 | if ((*m)->isFunction()) { |
4838 | const auto fn = static_cast<const FunctionNode *>(*m); |
4839 | if (fn->isPrivateSignal()) |
4840 | hasPrivateSignals = true; |
4841 | else if (fn->isInvokable()) |
4842 | isInvokable = true; |
4843 | } |
4844 | |
4845 | m_writer->writeEndElement(); // para |
4846 | newLine(); |
4847 | m_writer->writeEndElement(); // listitem |
4848 | newLine(); |
4849 | |
4850 | ++m; |
4851 | } |
4852 | |
4853 | m_writer->writeEndElement(); // itemizedlist |
4854 | newLine(); |
4855 | |
4856 | if (hasPrivateSignals) |
4857 | generateAddendum(node: relative, type: Generator::PrivateSignal, marker: nullptr, generateNote: true); |
4858 | if (isInvokable) |
4859 | generateAddendum(node: relative, type: Generator::Invokable, marker: nullptr, generateNote: true); |
4860 | } |
4861 | |
4862 | if (!useObsoleteMembers && section.style() == Section::Summary |
4863 | && !section.inheritedMembers().isEmpty()) { |
4864 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "itemizedlist" ); |
4865 | if (m_useITS) |
4866 | m_writer->writeAttribute(namespaceUri: itsNamespace, name: "translate" , value: "no" ); |
4867 | newLine(); |
4868 | |
4869 | generateSectionInheritedList(section, relative); |
4870 | |
4871 | m_writer->writeEndElement(); // itemizedlist |
4872 | newLine(); |
4873 | } |
4874 | } |
4875 | |
4876 | void DocBookGenerator::generateSectionInheritedList(const Section §ion, const Node *relative) |
4877 | { |
4878 | // From HtmlGenerator::generateSectionInheritedList. |
4879 | QList<std::pair<Aggregate *, int>>::ConstIterator p = section.inheritedMembers().constBegin(); |
4880 | while (p != section.inheritedMembers().constEnd()) { |
4881 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "listitem" ); |
4882 | m_writer->writeCharacters(text: QString::number((*p).second) + u' '); |
4883 | if ((*p).second == 1) |
4884 | m_writer->writeCharacters(text: section.singular()); |
4885 | else |
4886 | m_writer->writeCharacters(text: section.plural()); |
4887 | m_writer->writeCharacters(text: " inherited from " ); |
4888 | generateSimpleLink(href: fileName(node: (*p).first) + '#' |
4889 | + Generator::cleanRef(ref: section.title().toLower()), |
4890 | text: (*p).first->plainFullName(relative)); |
4891 | ++p; |
4892 | } |
4893 | } |
4894 | |
4895 | /*! |
4896 | Generate the DocBook page for an entity that doesn't map |
4897 | to any underlying parsable C++ or QML element. |
4898 | */ |
4899 | void DocBookGenerator::generatePageNode(PageNode *pn) |
4900 | { |
4901 | // From HtmlGenerator::generatePageNode, remove anything related to TOCs. |
4902 | Q_ASSERT(m_writer == nullptr); |
4903 | m_writer = startDocument(node: pn); |
4904 | |
4905 | generateHeader(title: pn->fullTitle(), subTitle: pn->subtitle(), node: pn); |
4906 | generateBody(node: pn); |
4907 | generateAlsoList(node: pn); |
4908 | generateFooter(); |
4909 | |
4910 | endDocument(); |
4911 | } |
4912 | |
4913 | /*! |
4914 | Generate the DocBook page for a QML type. \qcn is the QML type. |
4915 | */ |
4916 | void DocBookGenerator::generateQmlTypePage(QmlTypeNode *qcn) |
4917 | { |
4918 | // From HtmlGenerator::generateQmlTypePage. |
4919 | // Start producing the DocBook file. |
4920 | Q_ASSERT(m_writer == nullptr); |
4921 | m_writer = startDocument(node: qcn); |
4922 | |
4923 | Generator::setQmlTypeContext(qcn); |
4924 | QString title = qcn->fullTitle(); |
4925 | if (qcn->isQmlBasicType()) |
4926 | title.append(s: " QML Value Type" ); |
4927 | else |
4928 | title.append(s: " QML Type" ); |
4929 | // TODO: for ITS attribute, only apply translate="no" on qcn->fullTitle(), |
4930 | // not its suffix (which should be translated). generateHeader doesn't |
4931 | // allow this kind of input, the title isn't supposed to be structured. |
4932 | // Ideally, do the same in HTML. |
4933 | |
4934 | generateHeader(title, subTitle: qcn->subtitle(), node: qcn); |
4935 | generateQmlRequisites(qcn); |
4936 | |
4937 | startSection(id: "details" , title: "Detailed Description" ); |
4938 | generateBody(node: qcn); |
4939 | |
4940 | generateAlsoList(node: qcn); |
4941 | |
4942 | endSection(); |
4943 | |
4944 | Sections sections(qcn); |
4945 | for (const auto §ion : sections.stdQmlTypeDetailsSections()) { |
4946 | if (!section.isEmpty()) { |
4947 | startSection(id: section.title().toLower(), title: section.title()); |
4948 | |
4949 | for (const auto &member : section.members()) |
4950 | generateDetailedQmlMember(node: member, relative: qcn); |
4951 | |
4952 | endSection(); |
4953 | } |
4954 | } |
4955 | |
4956 | generateObsoleteQmlMembers(sections); |
4957 | |
4958 | generateFooter(); |
4959 | Generator::setQmlTypeContext(nullptr); |
4960 | |
4961 | endDocument(); |
4962 | } |
4963 | |
4964 | /*! |
4965 | Outputs the DocBook detailed documentation for a section |
4966 | on a QML element reference page. |
4967 | */ |
4968 | void DocBookGenerator::generateDetailedQmlMember(Node *node, const Aggregate *relative) |
4969 | { |
4970 | // From HtmlGenerator::generateDetailedQmlMember, with elements from |
4971 | // CppCodeMarker::markedUpQmlItem and HtmlGenerator::generateQmlItem. |
4972 | std::function<QString(QmlPropertyNode *)> getQmlPropertyTitle = [&](QmlPropertyNode *n) { |
4973 | QString title; |
4974 | QStringList ; |
4975 | if (n->isDefault()) |
4976 | extra << "default" ; |
4977 | else if (n->isReadOnly()) |
4978 | extra << "read-only" ; |
4979 | else if (n->isRequired()) |
4980 | extra << "required" ; |
4981 | else if (!n->defaultValue().isEmpty()) |
4982 | extra << "default: " + n->defaultValue(); |
4983 | |
4984 | if (!n->since().isEmpty()) { |
4985 | if (!extra.isEmpty()) |
4986 | extra.last().append(c: ','); |
4987 | extra << "since " + n->since(); |
4988 | } |
4989 | if (!extra.isEmpty()) |
4990 | title = QString("[%1] " ).arg(a: extra.join(sep: QLatin1Char(' '))); |
4991 | |
4992 | // Finalise generation of name, as per CppCodeMarker::markedUpQmlItem. |
4993 | if (n->isAttached()) |
4994 | title += n->element() + QLatin1Char('.'); |
4995 | title += n->name() + " : " + n->dataType(); |
4996 | |
4997 | return title; |
4998 | }; |
4999 | |
5000 | std::function<void(Node *)> generateQmlMethodTitle = [&](Node *node) { |
5001 | generateSynopsis(node, relative, style: Section::Details); |
5002 | }; |
5003 | |
5004 | if (node->isPropertyGroup()) { |
5005 | const auto *scn = static_cast<const SharedCommentNode *>(node); |
5006 | |
5007 | QString heading; |
5008 | if (!scn->name().isEmpty()) |
5009 | heading = scn->name() + " group" ; |
5010 | else |
5011 | heading = node->name(); |
5012 | startSection(node: scn, title: heading); |
5013 | // This last call creates a title for this section. In other words, |
5014 | // titles are forbidden for the rest of the section, hence the use of |
5015 | // bridgehead. |
5016 | |
5017 | const QList<Node *> sharedNodes = scn->collective(); |
5018 | for (const auto &sharedNode : sharedNodes) { |
5019 | if (sharedNode->isQmlProperty()) { |
5020 | auto *qpn = static_cast<QmlPropertyNode *>(sharedNode); |
5021 | |
5022 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "bridgehead" ); |
5023 | m_writer->writeAttribute(qualifiedName: "renderas" , value: "sect2" ); |
5024 | writeXmlId(node: qpn); |
5025 | m_writer->writeCharacters(text: getQmlPropertyTitle(qpn)); |
5026 | m_writer->writeEndElement(); // bridgehead |
5027 | newLine(); |
5028 | |
5029 | generateDocBookSynopsis(node: qpn); |
5030 | } |
5031 | } |
5032 | } else if (node->isQmlProperty()) { |
5033 | auto qpn = static_cast<QmlPropertyNode *>(node); |
5034 | startSection(node: qpn, title: getQmlPropertyTitle(qpn)); |
5035 | generateDocBookSynopsis(node: qpn); |
5036 | } else if (node->isSharedCommentNode()) { |
5037 | const auto scn = reinterpret_cast<const SharedCommentNode *>(node); |
5038 | const QList<Node *> &sharedNodes = scn->collective(); |
5039 | |
5040 | // In the section, generate a title for the first node, then bridgeheads for |
5041 | // the next ones. |
5042 | int i = 0; |
5043 | for (const auto &sharedNode : sharedNodes) { |
5044 | // Ignore this element if there is nothing to generate. |
5045 | if (!sharedNode->isFunction(g: Node::QML) && !sharedNode->isQmlProperty()) { |
5046 | continue; |
5047 | } |
5048 | |
5049 | // Write the tag containing the title. |
5050 | if (i == 0) { |
5051 | startSectionBegin(node: sharedNode); |
5052 | } else { |
5053 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "bridgehead" ); |
5054 | m_writer->writeAttribute(qualifiedName: "renderas" , value: "sect2" ); |
5055 | } |
5056 | |
5057 | // Write the title. |
5058 | if (sharedNode->isFunction(g: Node::QML)) |
5059 | generateQmlMethodTitle(sharedNode); |
5060 | else if (sharedNode->isQmlProperty()) |
5061 | m_writer->writeCharacters( |
5062 | text: getQmlPropertyTitle(static_cast<QmlPropertyNode *>(sharedNode))); |
5063 | |
5064 | // Complete the title and the synopsis. |
5065 | if (i == 0) |
5066 | startSectionEnd(); |
5067 | else |
5068 | m_writer->writeEndElement(); // bridgehead |
5069 | generateDocBookSynopsis(node: sharedNode); |
5070 | ++i; |
5071 | } |
5072 | |
5073 | // If the list is empty, still generate a section. |
5074 | if (i == 0) { |
5075 | startSectionBegin(id: refForNode(node)); |
5076 | |
5077 | if (node->isFunction(g: Node::QML)) |
5078 | generateQmlMethodTitle(node); |
5079 | else if (node->isQmlProperty()) |
5080 | m_writer->writeCharacters( |
5081 | text: getQmlPropertyTitle(static_cast<QmlPropertyNode *>(node))); |
5082 | |
5083 | startSectionEnd(); |
5084 | } |
5085 | } else { // assume the node is a method/signal handler |
5086 | startSectionBegin(node); |
5087 | generateQmlMethodTitle(node); |
5088 | startSectionEnd(); |
5089 | } |
5090 | |
5091 | generateStatus(node); |
5092 | generateBody(node); |
5093 | generateThreadSafeness(node); |
5094 | generateSince(node); |
5095 | generateAlsoList(node); |
5096 | |
5097 | endSection(); |
5098 | } |
5099 | |
5100 | /*! |
5101 | Recursive writing of DocBook files from the root \a node. |
5102 | */ |
5103 | void DocBookGenerator::generateDocumentation(Node *node) |
5104 | { |
5105 | // Mainly from Generator::generateDocumentation, with parts from |
5106 | // Generator::generateDocumentation and WebXMLGenerator::generateDocumentation. |
5107 | // Don't generate nodes that are already processed, or if they're not |
5108 | // supposed to generate output, ie. external, index or images nodes. |
5109 | if (!node->url().isNull()) |
5110 | return; |
5111 | if (node->isIndexNode()) |
5112 | return; |
5113 | if (node->isInternal() && !m_showInternal) |
5114 | return; |
5115 | if (node->isExternalPage()) |
5116 | return; |
5117 | |
5118 | if (node->parent()) { |
5119 | if (node->isCollectionNode()) { |
5120 | /* |
5121 | A collection node collects: groups, C++ modules, or QML |
5122 | modules. Testing for a CollectionNode must be done |
5123 | before testing for a TextPageNode because a |
5124 | CollectionNode is a PageNode at this point. |
5125 | |
5126 | Don't output an HTML page for the collection node unless |
5127 | the \group, \module, or \qmlmodule command was actually |
5128 | seen by qdoc in the qdoc comment for the node. |
5129 | |
5130 | A key prerequisite in this case is the call to |
5131 | mergeCollections(cn). We must determine whether this |
5132 | group, module, or QML module has members in other |
5133 | modules. We know at this point that cn's members list |
5134 | contains only members in the current module. Therefore, |
5135 | before outputting the page for cn, we must search for |
5136 | members of cn in the other modules and add them to the |
5137 | members list. |
5138 | */ |
5139 | auto cn = static_cast<CollectionNode *>(node); |
5140 | if (cn->wasSeen()) { |
5141 | m_qdb->mergeCollections(c: cn); |
5142 | generateCollectionNode(cn); |
5143 | } else if (cn->isGenericCollection()) { |
5144 | // Currently used only for the module's related orphans page |
5145 | // but can be generalized for other kinds of collections if |
5146 | // other use cases pop up. |
5147 | generateGenericCollectionPage(cn); |
5148 | } |
5149 | } else if (node->isTextPageNode()) { // Pages. |
5150 | generatePageNode(pn: static_cast<PageNode *>(node)); |
5151 | } else if (node->isAggregate()) { // Aggregates. |
5152 | if ((node->isClassNode() || node->isHeader() || node->isNamespace()) |
5153 | && node->docMustBeGenerated()) { |
5154 | generateCppReferencePage(node: static_cast<Aggregate *>(node)); |
5155 | } else if (node->isQmlType()) { // Includes QML value types |
5156 | generateQmlTypePage(qcn: static_cast<QmlTypeNode *>(node)); |
5157 | } else if (node->isProxyNode()) { |
5158 | generateProxyPage(aggregate: static_cast<Aggregate *>(node)); |
5159 | } |
5160 | } |
5161 | } |
5162 | |
5163 | if (node->isAggregate()) { |
5164 | auto *aggregate = static_cast<Aggregate *>(node); |
5165 | for (auto c : aggregate->childNodes()) { |
5166 | if (node->isPageNode() && !node->isPrivate()) |
5167 | generateDocumentation(node: c); |
5168 | } |
5169 | } |
5170 | } |
5171 | |
5172 | void DocBookGenerator::generateProxyPage(Aggregate *aggregate) |
5173 | { |
5174 | // Adapted from HtmlGenerator::generateProxyPage. |
5175 | Q_ASSERT(aggregate->isProxyNode()); |
5176 | |
5177 | // Start producing the DocBook file. |
5178 | Q_ASSERT(m_writer == nullptr); |
5179 | m_writer = startDocument(node: aggregate); |
5180 | |
5181 | // Info container. |
5182 | generateHeader(title: aggregate->plainFullName(), subTitle: "" , node: aggregate); |
5183 | |
5184 | // No element synopsis. |
5185 | |
5186 | // Actual content. |
5187 | if (!aggregate->doc().isEmpty()) { |
5188 | startSection(id: "details" , title: "Detailed Description" ); |
5189 | |
5190 | generateBody(node: aggregate); |
5191 | generateAlsoList(node: aggregate); |
5192 | |
5193 | endSection(); |
5194 | } |
5195 | |
5196 | Sections sections(aggregate); |
5197 | SectionVector *detailsSections = §ions.stdDetailsSections(); |
5198 | |
5199 | for (const auto §ion : std::as_const(t&: *detailsSections)) { |
5200 | if (section.isEmpty()) |
5201 | continue; |
5202 | |
5203 | startSection(id: section.title().toLower(), title: section.title()); |
5204 | |
5205 | const QList<Node *> &members = section.members(); |
5206 | for (const auto &member : members) { |
5207 | if (!member->isPrivate()) { // ### check necessary? |
5208 | if (!member->isClassNode()) { |
5209 | generateDetailedMember(node: member, relative: aggregate); |
5210 | } else { |
5211 | startSectionBegin(); |
5212 | generateFullName(node: member, relative: aggregate); |
5213 | startSectionEnd(); |
5214 | |
5215 | generateBrief(node: member); |
5216 | endSection(); |
5217 | } |
5218 | } |
5219 | } |
5220 | |
5221 | endSection(); |
5222 | } |
5223 | |
5224 | generateFooter(); |
5225 | |
5226 | endDocument(); |
5227 | } |
5228 | |
5229 | /*! |
5230 | Generate the HTML page for a group, module, or QML module. |
5231 | */ |
5232 | void DocBookGenerator::generateCollectionNode(CollectionNode *cn) |
5233 | { |
5234 | // Adapted from HtmlGenerator::generateCollectionNode. |
5235 | // Start producing the DocBook file. |
5236 | Q_ASSERT(m_writer == nullptr); |
5237 | m_writer = startDocument(node: cn); |
5238 | |
5239 | // Info container. |
5240 | generateHeader(title: cn->fullTitle(), subTitle: cn->subtitle(), node: cn); |
5241 | |
5242 | // Element synopsis. |
5243 | generateDocBookSynopsis(node: cn); |
5244 | |
5245 | // Generate brief for C++ modules, status for all modules. |
5246 | if (cn->genus() != Node::DOC && cn->genus() != Node::DontCare) { |
5247 | if (cn->isModule()) |
5248 | generateBrief(node: cn); |
5249 | generateStatus(node: cn); |
5250 | generateSince(node: cn); |
5251 | } |
5252 | |
5253 | // Actual content. |
5254 | if (cn->isModule()) { |
5255 | if (!cn->noAutoList()) { |
5256 | NodeMap nmm; |
5257 | cn->getMemberNamespaces(out&: nmm); |
5258 | if (!nmm.isEmpty()) { |
5259 | startSection(id: "namespaces" , title: "Namespaces" ); |
5260 | generateAnnotatedList(relative: cn, nodeList: nmm.values(), selector: "namespaces" ); |
5261 | endSection(); |
5262 | } |
5263 | nmm.clear(); |
5264 | cn->getMemberClasses(out&: nmm); |
5265 | if (!nmm.isEmpty()) { |
5266 | startSection(id: "classes" , title: "Classes" ); |
5267 | generateAnnotatedList(relative: cn, nodeList: nmm.values(), selector: "classes" ); |
5268 | endSection(); |
5269 | } |
5270 | } |
5271 | } |
5272 | |
5273 | bool generatedTitle = false; |
5274 | if (cn->isModule() && !cn->doc().briefText().isEmpty()) { |
5275 | startSection(id: "details" , title: "Detailed Description" ); |
5276 | generatedTitle = true; |
5277 | } |
5278 | // The anchor is only needed if the node has a body. |
5279 | else if ( |
5280 | // generateBody generates something. |
5281 | (cn->isFunction() && ((!cn->hasDoc() && !cn->hasSharedDoc()) || !cn->isSharingComment())) || |
5282 | cn->isExample() || |
5283 | // generateAlsoList generates something. |
5284 | !cn->doc().alsoList().empty() || |
5285 | // generateAnnotatedList generates something. |
5286 | (!cn->noAutoList() && (cn->isGroup() || cn->isQmlModule()))) { |
5287 | writeAnchor(id: "details" ); |
5288 | } |
5289 | |
5290 | generateBody(node: cn); |
5291 | generateAlsoList(node: cn); |
5292 | |
5293 | if (!cn->noAutoList() && (cn->isGroup() || cn->isQmlModule())) |
5294 | generateAnnotatedList(relative: cn, nodeList: cn->members(), selector: "members" , type: AutoSection); |
5295 | |
5296 | if (generatedTitle) |
5297 | endSection(); |
5298 | |
5299 | generateFooter(); |
5300 | |
5301 | endDocument(); |
5302 | } |
5303 | |
5304 | /*! |
5305 | Generate the HTML page for a generic collection. This is usually |
5306 | a collection of C++ elements that are related to an element in |
5307 | a different module. |
5308 | */ |
5309 | void DocBookGenerator::generateGenericCollectionPage(CollectionNode *cn) |
5310 | { |
5311 | // Adapted from HtmlGenerator::generateGenericCollectionPage. |
5312 | // TODO: factor out this code to generate a file name. |
5313 | QString name = cn->name().toLower(); |
5314 | name.replace(c: QChar(' '), after: QString("-" )); |
5315 | QString filename = cn->tree()->physicalModuleName() + "-" + name + "." + fileExtension(); |
5316 | |
5317 | // Start producing the DocBook file. |
5318 | Q_ASSERT(m_writer == nullptr); |
5319 | m_writer = startGenericDocument(node: cn, fileName: filename); |
5320 | |
5321 | // Info container. |
5322 | generateHeader(title: cn->fullTitle(), subTitle: cn->subtitle(), node: cn); |
5323 | |
5324 | // Element synopsis. |
5325 | generateDocBookSynopsis(node: cn); |
5326 | |
5327 | // Actual content. |
5328 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "para" ); |
5329 | m_writer->writeCharacters(text: "Each function or type documented here is related to a class or " |
5330 | "namespace that is documented in a different module. The reference " |
5331 | "page for that class or namespace will link to the function or type " |
5332 | "on this page." ); |
5333 | m_writer->writeEndElement(); // para |
5334 | |
5335 | const CollectionNode *cnc = cn; |
5336 | const QList<Node *> members = cn->members(); |
5337 | for (const auto &member : members) |
5338 | generateDetailedMember(node: member, relative: cnc); |
5339 | |
5340 | generateFooter(); |
5341 | |
5342 | endDocument(); |
5343 | } |
5344 | |
5345 | void DocBookGenerator::generateFullName(const Node *node, const Node *relative) |
5346 | { |
5347 | Q_ASSERT(node); |
5348 | Q_ASSERT(relative); |
5349 | |
5350 | // From Generator::appendFullName. |
5351 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "link" ); |
5352 | m_writer->writeAttribute(namespaceUri: xlinkNamespace, name: "href" , value: fullDocumentLocation(node)); |
5353 | m_writer->writeAttribute(namespaceUri: xlinkNamespace, name: "role" , value: targetType(node)); |
5354 | m_writer->writeCharacters(text: node->fullName(relative)); |
5355 | m_writer->writeEndElement(); // link |
5356 | } |
5357 | |
5358 | void DocBookGenerator::generateFullName(const Node *apparentNode, const QString &fullName, |
5359 | const Node *actualNode) |
5360 | { |
5361 | Q_ASSERT(apparentNode); |
5362 | Q_ASSERT(actualNode); |
5363 | |
5364 | // From Generator::appendFullName. |
5365 | m_writer->writeStartElement(namespaceUri: dbNamespace, name: "link" ); |
5366 | m_writer->writeAttribute(namespaceUri: xlinkNamespace, name: "href" , value: fullDocumentLocation(node: actualNode)); |
5367 | m_writer->writeAttribute(qualifiedName: "role" , value: targetType(node: actualNode)); |
5368 | m_writer->writeCharacters(text: fullName); |
5369 | m_writer->writeEndElement(); // link |
5370 | } |
5371 | |
5372 | QT_END_NAMESPACE |
5373 | |