Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
helpprojectwriter.cpp
Go to the documentation of this file.
1// Copyright (C) 2021 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3
5
6#include "access.h"
7#include "aggregate.h"
8#include "atom.h"
9#include "classnode.h"
10#include "collectionnode.h"
11#include "config.h"
12#include "enumnode.h"
13#include "functionnode.h"
14#include "htmlgenerator.h"
15#include "node.h"
16#include "qdocdatabase.h"
17#include "typedefnode.h"
18
19#include <QtCore/qhash.h>
20
22
23using namespace Qt::StringLiterals;
24
25HelpProjectWriter::HelpProjectWriter(const QString &defaultFileName, Generator *g)
26{
27 reset(defaultFileName, g);
28}
29
30void HelpProjectWriter::reset(const QString &defaultFileName, Generator *g)
31{
32 m_projects.clear();
33 m_gen = g;
34 /*
35 Get the pointer to the singleton for the qdoc database and
36 store it locally. This replaces all the local accesses to
37 the node tree, which are now private.
38 */
40
41 // The output directory should already have been checked by the calling
42 // generator.
43 Config &config = Config::instance();
44 m_outputDir = config.getOutputDir();
45
46 const QStringList names{config.get(CONFIG_QHP + Config::dot + "projects").asStringList()};
47
48 for (const auto &projectName : names) {
49 HelpProject project;
50 project.m_name = projectName;
51
52 QString prefix = CONFIG_QHP + Config::dot + projectName + Config::dot;
53 project.m_helpNamespace = config.get(prefix + "namespace").asString();
54 project.m_virtualFolder = config.get(prefix + "virtualFolder").asString();
55 project.m_version = config.get(CONFIG_VERSION).asString();
56 project.m_fileName = config.get(prefix + "file").asString();
57 if (project.m_fileName.isEmpty())
58 project.m_fileName = defaultFileName;
59 project.m_extraFiles = config.get(prefix + "extraFiles").asStringSet();
60 project.m_extraFiles += config.get(CONFIG_QHP + Config::dot + "extraFiles").asStringSet();
61 project.m_indexTitle = config.get(prefix + "indexTitle").asString();
62 project.m_indexRoot = config.get(prefix + "indexRoot").asString();
63 project.m_filterAttributes = config.get(prefix + "filterAttributes").asStringSet();
64 project.m_includeIndexNodes = config.get(prefix + "includeIndexNodes").asBool();
65 const QSet<QString> customFilterNames = config.subVars(prefix + "customFilters");
66 for (const auto &filterName : customFilterNames) {
67 QString name{config.get(prefix + "customFilters" + Config::dot + filterName
68 + Config::dot + "name").asString()};
69 project.m_customFilters[name] =
70 config.get(prefix + "customFilters" + Config::dot + filterName
71 + Config::dot + "filterAttributes").asStringSet();
72 }
73
74 const auto excludedPrefixes = config.get(prefix + "excluded").asStringSet();
75 for (auto name : excludedPrefixes)
76 project.m_excluded.insert(name.replace(QLatin1Char('\\'), QLatin1Char('/')));
77
78 const auto subprojectPrefixes{config.get(prefix + "subprojects").asStringList()};
79 for (const auto &name : subprojectPrefixes) {
80 SubProject subproject;
81 QString subprefix = prefix + "subprojects" + Config::dot + name + Config::dot;
82 subproject.m_title = config.get(subprefix + "title").asString();
83 if (subproject.m_title.isEmpty())
84 continue;
85 subproject.m_indexTitle = config.get(subprefix + "indexTitle").asString();
86 subproject.m_sortPages = config.get(subprefix + "sortPages").asBool();
87 subproject.m_type = config.get(subprefix + "type").asString();
88 readSelectors(subproject, config.get(subprefix + "selectors").asStringList());
89 subprefix.chop(1);
90 subproject.m_prefix = subprefix; // Stored for error reporting purposes
91 project.m_subprojects.append(subproject);
92 }
93
94 if (project.m_subprojects.isEmpty()) {
95 SubProject subproject;
96 readSelectors(subproject, config.get(prefix + "selectors").asStringList());
97 project.m_subprojects.insert(0, subproject);
98 }
99
100 m_projects.append(project);
101 }
102}
103
104void HelpProjectWriter::readSelectors(SubProject &subproject, const QStringList &selectors)
105{
106 QHash<QString, Node::NodeType> typeHash;
107 typeHash["namespace"] = Node::Namespace;
108 typeHash["class"] = Node::Class;
109 typeHash["struct"] = Node::Struct;
110 typeHash["union"] = Node::Union;
111 typeHash["header"] = Node::HeaderFile;
112 typeHash["headerfile"] = Node::HeaderFile;
113 typeHash["doc"] = Node::Page; // Unused (supported but ignored as a prefix)
114 typeHash["fake"] = Node::Page; // Unused (supported but ignored as a prefix)
115 typeHash["page"] = Node::Page;
116 typeHash["enum"] = Node::Enum;
117 typeHash["example"] = Node::Example;
118 typeHash["externalpage"] = Node::ExternalPage;
119 typeHash["typedef"] = Node::Typedef;
120 typeHash["typealias"] = Node::TypeAlias;
121 typeHash["function"] = Node::Function;
122 typeHash["property"] = Node::Property;
123 typeHash["variable"] = Node::Variable;
124 typeHash["group"] = Node::Group;
125 typeHash["module"] = Node::Module;
126 typeHash["qmlmodule"] = Node::QmlModule;
127 typeHash["qmlproperty"] = Node::QmlProperty;
128 typeHash["qmlclass"] = Node::QmlType; // Legacy alias for 'qmltype'
129 typeHash["qmltype"] = Node::QmlType;
130 typeHash["qmlbasictype"] = Node::QmlValueType; // Legacy alias for 'qmlvaluetype'
131 typeHash["qmlvaluetype"] = Node::QmlValueType;
132
133 for (const QString &selector : selectors) {
134 QStringList pieces = selector.split(QLatin1Char(':'));
135 // Remove doc: or fake: prefix
136 if (pieces.size() > 1 && typeHash.value(pieces[0].toLower()) == Node::Page)
137 pieces.takeFirst();
138
139 QString typeName = pieces.takeFirst().toLower();
140 if (!typeHash.contains(typeName))
141 continue;
142
143 subproject.m_selectors << typeHash.value(typeName);
144 if (!pieces.isEmpty()) {
145 pieces = pieces[0].split(QLatin1Char(','));
146 for (const auto &piece : std::as_const(pieces)) {
147 if (typeHash[typeName] == Node::Group
148 || typeHash[typeName] == Node::Module
149 || typeHash[typeName] == Node::QmlModule) {
150 subproject.m_groups << piece.toLower();
151 }
152 }
153 }
154 }
155}
156
157void HelpProjectWriter::addExtraFile(const QString &file)
158{
159 for (HelpProject &project : m_projects)
160 project.m_extraFiles.insert(file);
161}
162
163Keyword HelpProjectWriter::keywordDetails(const Node *node) const
164{
165 QString ref = m_gen->fullDocumentLocation(node);
166
167 if (node->parent() && !node->parent()->name().isEmpty()) {
168 QString name = (node->isEnumType() || node->isTypedef())
169 ? node->parent()->name()+"::"+node->name()
170 : node->name();
171 QString id = (!node->isRelatedNonmember())
172 ? node->parent()->name()+"::"+node->name()
173 : node->name();
174 return Keyword(name, id, ref);
175 } else if (node->isQmlType()) {
176 const QString &name = node->name();
177 QString moduleName = node->logicalModuleName();
178 QStringList ids("QML." + name);
179 if (!moduleName.isEmpty()) {
180 QString majorVersion = node->logicalModule()
181 ? node->logicalModule()->logicalModuleVersion().split('.')[0]
182 : QString();
183 ids << "QML." + moduleName + majorVersion + "." + name;
184 }
185 return Keyword(name, ids, ref);
186 } else if (node->isQmlModule()) {
187 const QLatin1Char delim('.');
188 QStringList parts = node->logicalModuleName().split(delim) << "QML";
189 std::reverse(parts.begin(), parts.end());
190 return Keyword(node->logicalModuleName(), parts.join(delim), ref);
191 } else if (node->isTextPageNode()) {
192 const auto *pageNode = static_cast<const PageNode *>(node);
193 return Keyword(pageNode->fullTitle(), pageNode->fullTitle(), ref);
194 } else {
195 return Keyword(node->name(), node->name(), ref);
196 }
197}
198
199bool HelpProjectWriter::generateSection(HelpProject &project, QXmlStreamWriter & /* writer */,
200 const Node *node)
201{
202 if (!node->url().isEmpty() && !(project.m_includeIndexNodes && !node->url().startsWith("http")))
203 return false;
204
205 // Process (members of) unseen group nodes, i.e. nodes that use \ingroup <group_name> where
206 // \group group_name itself is not documented.
207 bool unseenGroup{node->isGroup() && !node->wasSeen()};
208
209 if ((node->isPrivate() || node->isInternal() || node->isDontDocument()) && !unseenGroup)
210 return false;
211
212 if (node->name().isEmpty())
213 return true;
214
215 QString docPath = node->doc().location().filePath();
216 if (!docPath.isEmpty() && project.m_excluded.contains(docPath))
217 return false;
218
219 QString objName = node->isTextPageNode() ? node->fullTitle() : node->fullDocumentName();
220 // Only add nodes to the set for each subproject if they match a selector.
221 // Those that match will be listed in the table of contents.
222
223 for (int i = 0; i < project.m_subprojects.size(); i++) {
224 SubProject subproject = project.m_subprojects[i];
225 // No selectors: accept all nodes.
226 if (subproject.m_selectors.isEmpty()) {
227 project.m_subprojects[i].m_nodes[objName] = node;
228 } else if (subproject.m_selectors.contains(node->nodeType())) {
229 // Add all group members for '[group|module|qmlmodule]:name' selector
230 if (node->isCollectionNode()) {
231 if (project.m_subprojects[i].m_groups.contains(node->name().toLower())) {
232 const auto *cn = static_cast<const CollectionNode *>(node);
233 const auto members = cn->members();
234 for (const Node *m : members) {
235 if (!m->isInAPI())
236 continue;
237 QString memberName =
238 m->isTextPageNode() ? m->fullTitle() : m->fullDocumentName();
239 project.m_subprojects[i].m_nodes[memberName] = m;
240 }
241 continue;
242 } else if (!project.m_subprojects[i].m_groups.isEmpty()) {
243 continue; // Node does not represent specified group(s)
244 }
245 } else if (node->isTextPageNode()) {
246 if (node->isExternalPage() || node->fullTitle().isEmpty())
247 continue;
248 }
249 project.m_subprojects[i].m_nodes[objName] = node;
250 }
251 }
252
253 auto appendDocKeywords = [&](const Node *n) {
254 for (const auto *kw : n->doc().keywords()) {
255 if (!kw->string().isEmpty()) {
256 QStringList ref_parts = m_gen->fullDocumentLocation(n).split('#'_L1);
257 // Use keyword's custom anchor if it has one
258 if (kw->count() > 1) {
259 if (ref_parts.count() > 1)
260 ref_parts.pop_back();
261 ref_parts << kw->string(1);
262 }
263 project.m_keywords.append(Keyword(kw->string(), kw->string(),
264 ref_parts.join('#'_L1)));
265 }
266 }
267 };
268 // Unseen group nodes require no further processing as they have no documentation
269 if (unseenGroup)
270 return false;
271
272 switch (node->nodeType()) {
273
274 case Node::Class:
275 case Node::Struct:
276 case Node::Union:
277 project.m_keywords.append(keywordDetails(node));
278 break;
279 case Node::QmlType:
281 appendDocKeywords(node);
282 project.m_keywords.append(keywordDetails(node));
283 break;
284
285 case Node::Namespace:
286 project.m_keywords.append(keywordDetails(node));
287 break;
288
289 case Node::Enum:
290 project.m_keywords.append(keywordDetails(node));
291 {
292 const auto *enumNode = static_cast<const EnumNode *>(node);
293 const auto items = enumNode->items();
294 for (const auto &item : items) {
295 if (enumNode->itemAccess(item.name()) == Access::Private)
296 continue;
297
298 QString name;
299 QString id;
300 if (!node->parent()->name().isEmpty()) {
301 name = id = node->parent()->name() + "::" + item.name();
302 } else {
303 name = id = item.name();
304 }
305 QString ref = m_gen->fullDocumentLocation(node);
306 project.m_keywords.append(Keyword(name, id, ref));
307 }
308 }
309 break;
310
311 case Node::Group:
312 case Node::Module:
313 case Node::QmlModule: {
314 if (!node->fullTitle().isEmpty()) {
315 appendDocKeywords(node);
316 project.m_keywords.append(keywordDetails(node));
317 }
318 } break;
319
320 case Node::Property:
322 project.m_keywords.append(keywordDetails(node));
323 break;
324
325 case Node::Function: {
326 const auto *funcNode = static_cast<const FunctionNode *>(node);
327
328 /*
329 QML methods, signals, and signal handlers used to be node types,
330 but now they are Function nodes with a Metaness value that specifies
331 what kind of function they are, QmlSignal, QmlMethod, etc.
332 */
333 if (funcNode->isQmlNode()) {
334 project.m_keywords.append(keywordDetails(node));
335 break;
336 }
337 // Only insert keywords for non-constructors. Constructors are covered
338 // by the classes themselves.
339
340 if (!funcNode->isSomeCtor())
341 project.m_keywords.append(keywordDetails(node));
342
343 // Insert member status flags into the entries for the parent
344 // node of the function, or the node it is related to.
345 // Since parent nodes should have already been inserted into
346 // the set of files, we only need to ensure that related nodes
347 // are inserted.
348
349 if (node->parent())
350 project.m_memberStatus[node->parent()].insert(node->status());
351 } break;
352 case Node::TypeAlias:
353 case Node::Typedef: {
354 const auto *typedefNode = static_cast<const TypedefNode *>(node);
355 Keyword typedefDetails = keywordDetails(node);
356 const EnumNode *enumNode = typedefNode->associatedEnum();
357 // Use the location of any associated enum node in preference
358 // to that of the typedef.
359 if (enumNode)
360 typedefDetails.m_ref = m_gen->fullDocumentLocation(enumNode);
361
362 project.m_keywords.append(typedefDetails);
363 } break;
364
365 case Node::Variable: {
366 project.m_keywords.append(keywordDetails(node));
367 } break;
368
369 // Page nodes (such as manual pages) contain subtypes, titles and other
370 // attributes.
371 case Node::Page: {
372 if (!node->fullTitle().isEmpty()) {
373 appendDocKeywords(node);
374 project.m_keywords.append(keywordDetails(node));
375 }
376 break;
377 }
378 default:;
379 }
380
381 // Add all images referenced in the page to the set of files to include.
382 const Atom *atom = node->doc().body().firstAtom();
383 while (atom) {
384 if (atom->type() == Atom::Image || atom->type() == Atom::InlineImage) {
385 // Images are all placed within a single directory regardless of
386 // whether the source images are in a nested directory structure.
387 QStringList pieces = atom->string().split(QLatin1Char('/'));
388 project.m_files.insert("images/" + pieces.last());
389 }
390 atom = atom->next();
391 }
392
393 return true;
394}
395
396void HelpProjectWriter::generateSections(HelpProject &project, QXmlStreamWriter &writer,
397 const Node *node)
398{
399 /*
400 Don't include index nodes in the help file.
401 */
402 if (node->isIndexNode())
403 return;
404 if (!generateSection(project, writer, node))
405 return;
406
407 if (node->isAggregate()) {
408 const auto *aggregate = static_cast<const Aggregate *>(node);
409
410 // Ensure that we don't visit nodes more than once.
411 NodeList childSet;
412 NodeList children = aggregate->childNodes();
413 std::sort(children.begin(), children.end(), Node::nodeNameLessThan);
414 for (auto *child : children) {
415 // Skip related non-members adopted by some other aggregate
416 if (child->parent() != aggregate)
417 continue;
418 // Process unseen group nodes (even though they're marked internal)
419 if (child->isGroup() && !child->wasSeen()) {
420 childSet << child;
421 continue;
422 }
423 if (child->isIndexNode() || child->isPrivate())
424 continue;
425 if (child->isTextPageNode()) {
426 if (!childSet.contains(child))
427 childSet << child;
428 } else {
429 // Store member status of children
430 project.m_memberStatus[node].insert(child->status());
431 if (child->isFunction() && static_cast<const FunctionNode *>(child)->isOverload())
432 continue;
433 if (!childSet.contains(child))
434 childSet << child;
435 }
436 }
437 for (const auto *child : std::as_const(childSet))
438 generateSections(project, writer, child);
439 }
440}
441
443{
444 // Warn if .qhp configuration was expected but not provided
445 if (auto &config = Config::instance(); m_projects.isEmpty() && config.get(CONFIG_QHP).asBool()) {
446 config.location().warning(u"Documentation configuration for '%1' doesn't define a help project (qhp)"_s
447 .arg(config.get(CONFIG_PROJECT).asString()));
448 }
449 for (HelpProject &project : m_projects)
450 generateProject(project);
451}
452
453void HelpProjectWriter::writeSection(QXmlStreamWriter &writer, const QString &path,
454 const QString &value)
455{
456 writer.writeStartElement(QStringLiteral("section"));
457 writer.writeAttribute(QStringLiteral("ref"), path);
458 writer.writeAttribute(QStringLiteral("title"), value);
459 writer.writeEndElement(); // section
460}
461
462/*!
463 Write subsections for all members, compatibility members and obsolete members.
464 */
465void HelpProjectWriter::addMembers(HelpProject &project, QXmlStreamWriter &writer, const Node *node)
466{
467 QString href = m_gen->fullDocumentLocation(node);
468 href = href.left(href.size() - 5);
469 if (href.isEmpty())
470 return;
471
472 bool derivedClass = false;
473 if (node->isClassNode())
474 derivedClass = !(static_cast<const ClassNode *>(node)->baseClasses().isEmpty());
475
476 // Do not generate a 'List of all members' for namespaces or header files,
477 // but always generate it for derived classes and QML types (but not QML value types)
478 if (!node->isNamespace() && !node->isHeader() && !node->isQmlBasicType()
479 && (derivedClass || node->isQmlType() || !project.m_memberStatus[node].isEmpty())) {
480 QString membersPath = href + QStringLiteral("-members.html");
481 writeSection(writer, membersPath, QStringLiteral("List of all members"));
482 }
483 if (project.m_memberStatus[node].contains(Node::Deprecated)) {
484 QString obsoletePath = href + QStringLiteral("-obsolete.html");
485 writeSection(writer, obsoletePath, QStringLiteral("Obsolete members"));
486 }
487}
488
489void HelpProjectWriter::writeNode(HelpProject &project, QXmlStreamWriter &writer, const Node *node)
490{
491 QString href = m_gen->fullDocumentLocation(node);
492 QString objName = node->name();
493
494 switch (node->nodeType()) {
495
496 case Node::Class:
497 case Node::Struct:
498 case Node::Union:
499 case Node::QmlType:
500 case Node::QmlValueType: {
501 QString typeStr = m_gen->typeString(node);
502 if (!typeStr.isEmpty())
503 typeStr[0] = typeStr[0].toTitleCase();
504 writer.writeStartElement("section");
505 writer.writeAttribute("ref", href);
506 if (node->parent() && !node->parent()->name().isEmpty())
507 writer.writeAttribute("title",
508 QStringLiteral("%1::%2 %3 Reference")
509 .arg(node->parent()->name(), objName, typeStr));
510 else
511 writer.writeAttribute("title", QStringLiteral("%1 %2 Reference").arg(objName, typeStr));
512
513 addMembers(project, writer, node);
514 writer.writeEndElement(); // section
515 } break;
516
517 case Node::Namespace:
518 writeSection(writer, href, "%1 Namespace Reference"_L1.arg(objName));
519 break;
520
521 case Node::Example:
522 case Node::HeaderFile:
523 case Node::Page:
524 case Node::Group:
525 case Node::Module:
526 case Node::QmlModule: {
527 writer.writeStartElement("section");
528 writer.writeAttribute("ref", href);
529 writer.writeAttribute("title", node->fullTitle());
530 if (node->nodeType() == Node::HeaderFile)
531 addMembers(project, writer, node);
532 writer.writeEndElement(); // section
533 } break;
534 default:;
535 }
536}
537
538void HelpProjectWriter::generateProject(HelpProject &project)
539{
540 const Node *rootNode;
541
542 // Restrict searching only to the local (primary) tree
543 QList<Tree *> searchOrder = m_qdb->searchOrder();
545
546 if (!project.m_indexRoot.isEmpty())
547 rootNode = m_qdb->findPageNodeByTitle(project.m_indexRoot);
548 else
549 rootNode = m_qdb->primaryTreeRoot();
550
551 if (rootNode == nullptr)
552 return;
553
554 project.m_files.clear();
555 project.m_keywords.clear();
556
557 QFile file(m_outputDir + QDir::separator() + project.m_fileName);
558 if (!file.open(QFile::WriteOnly))
559 return;
560
561 QXmlStreamWriter writer(&file);
562 writer.setAutoFormatting(true);
563 writer.writeStartDocument();
564 writer.writeStartElement("QtHelpProject");
565 writer.writeAttribute("version", "1.0");
566
567 // Write metaData, virtualFolder and namespace elements.
568 writer.writeTextElement("namespace", project.m_helpNamespace);
569 writer.writeTextElement("virtualFolder", project.m_virtualFolder);
570 writer.writeStartElement("metaData");
571 writer.writeAttribute("name", "version");
572 writer.writeAttribute("value", project.m_version);
573 writer.writeEndElement();
574
575 // Write customFilter elements.
576 for (auto it = project.m_customFilters.constBegin(); it != project.m_customFilters.constEnd();
577 ++it) {
578 writer.writeStartElement("customFilter");
579 writer.writeAttribute("name", it.key());
580 QStringList sortedAttributes = it.value().values();
581 sortedAttributes.sort();
582 for (const auto &filter : std::as_const(sortedAttributes))
583 writer.writeTextElement("filterAttribute", filter);
584 writer.writeEndElement(); // customFilter
585 }
586
587 // Start the filterSection.
588 writer.writeStartElement("filterSection");
589
590 // Write filterAttribute elements.
591 QStringList sortedFilterAttributes = project.m_filterAttributes.values();
592 sortedFilterAttributes.sort();
593 for (const auto &filterName : std::as_const(sortedFilterAttributes))
594 writer.writeTextElement("filterAttribute", filterName);
595
596 writer.writeStartElement("toc");
597 writer.writeStartElement("section");
598 const Node *node = m_qdb->findPageNodeByTitle(project.m_indexTitle);
599 if (!node)
600 node = m_qdb->findNodeByNameAndType(QStringList(project.m_indexTitle), &Node::isPageNode);
601 if (!node)
602 node = m_qdb->findNodeByNameAndType(QStringList("index.html"), &Node::isPageNode);
603 QString indexPath;
604 if (node)
605 indexPath = m_gen->fullDocumentLocation(node);
606 else
607 indexPath = "index.html";
608 writer.writeAttribute("ref", indexPath);
609 writer.writeAttribute("title", project.m_indexTitle);
610
611 generateSections(project, writer, rootNode);
612
613 for (int i = 0; i < project.m_subprojects.size(); i++) {
614 SubProject subproject = project.m_subprojects[i];
615
616 if (subproject.m_type == QLatin1String("manual")) {
617
618 const Node *indexPage = m_qdb->findNodeForTarget(subproject.m_indexTitle, nullptr);
619 if (indexPage) {
620 Text indexBody = indexPage->doc().body();
621 const Atom *atom = indexBody.firstAtom();
622 QStack<int> sectionStack;
623 bool inItem = false;
624
625 while (atom) {
626 switch (atom->type()) {
627 case Atom::ListLeft:
628 sectionStack.push(0);
629 break;
630 case Atom::ListRight:
631 if (sectionStack.pop() > 0)
632 writer.writeEndElement(); // section
633 break;
635 inItem = true;
636 break;
638 inItem = false;
639 break;
640 case Atom::Link:
641 if (inItem) {
642 if (sectionStack.top() > 0)
643 writer.writeEndElement(); // section
644
645 const Node *page = m_qdb->findNodeForTarget(atom->string(), nullptr);
646 writer.writeStartElement("section");
647 QString indexPath = m_gen->fullDocumentLocation(page);
648 writer.writeAttribute("ref", indexPath);
649 writer.writeAttribute("title", atom->linkText());
650
651 sectionStack.top() += 1;
652 }
653 break;
654 default:;
655 }
656
657 if (atom == indexBody.lastAtom())
658 break;
659 atom = atom->next();
660 }
661 } else
662 Config::instance().location().warning(
663 "Failed to find %1.indexTitle '%2'"_L1.arg(subproject.m_prefix, subproject.m_indexTitle));
664
665 } else {
666
667 writer.writeStartElement("section");
668 QString indexPath = m_gen->fullDocumentLocation(
669 m_qdb->findNodeForTarget(subproject.m_indexTitle, nullptr));
670 if (indexPath.isEmpty() && !subproject.m_indexTitle.isEmpty())
671 Config::instance().location().warning(
672 "Failed to find %1.indexTitle '%2'"_L1.arg(subproject.m_prefix, subproject.m_indexTitle));
673 writer.writeAttribute("ref", indexPath);
674 writer.writeAttribute("title", subproject.m_title);
675
676 if (subproject.m_sortPages) {
677 QStringList titles = subproject.m_nodes.keys();
678 titles.sort();
679 for (const auto &title : std::as_const(titles)) {
680 writeNode(project, writer, subproject.m_nodes[title]);
681 }
682 } else {
683 // Find a contents node and navigate from there, using the NextLink values.
684 QSet<QString> visited;
685 bool contentsFound = false;
686 for (const auto *node : std::as_const(subproject.m_nodes)) {
687 QString nextTitle = node->links().value(Node::NextLink).first;
688 if (!nextTitle.isEmpty()
689 && node->links().value(Node::ContentsLink).first.isEmpty()) {
690
691 const Node *nextPage = m_qdb->findNodeForTarget(nextTitle, nullptr);
692
693 // Write the contents node.
694 writeNode(project, writer, node);
695 contentsFound = true;
696
697 while (nextPage) {
698 writeNode(project, writer, nextPage);
699 nextTitle = nextPage->links().value(Node::NextLink).first;
700 if (nextTitle.isEmpty() || visited.contains(nextTitle))
701 break;
702 nextPage = m_qdb->findNodeForTarget(nextTitle, nullptr);
703 visited.insert(nextTitle);
704 }
705 break;
706 }
707 }
708 // No contents/nextpage links found, write all nodes unsorted
709 if (!contentsFound) {
710 QList<const Node *> subnodes = subproject.m_nodes.values();
711
712 std::sort(subnodes.begin(), subnodes.end(), Node::nodeNameLessThan);
713
714 for (const auto *node : std::as_const(subnodes))
715 writeNode(project, writer, node);
716 }
717 }
718
719 writer.writeEndElement(); // section
720 }
721 }
722
723 // Restore original search order
724 m_qdb->setSearchOrder(searchOrder);
725
726 writer.writeEndElement(); // section
727 writer.writeEndElement(); // toc
728
729 writer.writeStartElement("keywords");
730 std::sort(project.m_keywords.begin(), project.m_keywords.end());
731 for (const auto &k : std::as_const(project.m_keywords)) {
732 for (const auto &id : std::as_const(k.m_ids)) {
733 writer.writeStartElement("keyword");
734 writer.writeAttribute("name", k.m_name);
735 writer.writeAttribute("id", id);
736 writer.writeAttribute("ref", k.m_ref);
737 writer.writeEndElement(); //keyword
738 }
739 }
740 writer.writeEndElement(); // keywords
741
742 writer.writeStartElement("files");
743
744 // The list of files to write is the union of generated files and
745 // other files (images and extras) included in the project
746 QSet<QString> files =
747 QSet<QString>(m_gen->outputFileNames().cbegin(), m_gen->outputFileNames().cend());
748 files.unite(project.m_files);
749 files.unite(project.m_extraFiles);
750 QStringList sortedFiles = files.values();
751 sortedFiles.sort();
752 for (const auto &usedFile : std::as_const(sortedFiles)) {
753 if (!usedFile.isEmpty())
754 writer.writeTextElement("file", usedFile);
755 }
756 writer.writeEndElement(); // files
757
758 writer.writeEndElement(); // filterSection
759 writer.writeEndElement(); // QtHelpProject
760 writer.writeEndDocument();
761 file.close();
762}
763
764QT_END_NAMESPACE
const NodeList & childNodes() const
Returns a const reference to the child list.
Definition aggregate.h:41
The Atom class is the fundamental unit for representing documents internally.
Definition atom.h:18
AtomType type() const
Return the type of this atom.
Definition atom.h:149
@ InlineImage
Definition atom.h:55
@ ListItemRight
Definition atom.h:67
@ Image
Definition atom.h:51
@ ListItemLeft
Definition atom.h:66
@ ListLeft
Definition atom.h:62
@ ListRight
Definition atom.h:68
@ Link
Definition atom.h:60
const Atom * next() const
Return the next atom in the atom list.
Definition atom.h:146
The ClassNode represents a C++ class.
Definition classnode.h:21
A class for holding the members of a collection of doc pages.
const NodeList & members() const
The Config class contains the configuration variables for controlling how qdoc produces documentation...
Definition config.h:84
const Text & body() const
Definition doc.cpp:115
This node is used to represent any kind of function being documented.
bool isSomeCtor() const
HelpProjectWriter(const QString &defaultFileName, Generator *g)
void addExtraFile(const QString &file)
void reset(const QString &defaultFileName, Generator *g)
bool isExternalPage() const
Returns true if the node type is ExternalPage.
Definition node.h:134
bool isDontDocument() const
Returns true if this node's status is DontDocument.
Definition node.h:131
const Doc & doc() const
Returns a reference to the node's Doc data member.
Definition node.h:271
bool isQmlNode() const
Returns true if this node's Genus value is QML.
Definition node.h:153
bool isGroup() const
Returns true if the node type is Group.
Definition node.h:139
bool isPrivate() const
Returns true if this node's access is Private.
Definition node.h:146
@ Variable
Definition node.h:69
@ Module
Definition node.h:71
@ Struct
Definition node.h:58
@ QmlModule
Definition node.h:73
@ Typedef
Definition node.h:66
@ QmlValueType
Definition node.h:75
@ Function
Definition node.h:65
@ TypeAlias
Definition node.h:67
@ Union
Definition node.h:59
@ Page
Definition node.h:61
@ Group
Definition node.h:70
@ Enum
Definition node.h:62
@ HeaderFile
Definition node.h:60
@ QmlProperty
Definition node.h:74
@ QmlType
Definition node.h:72
@ Namespace
Definition node.h:56
@ Property
Definition node.h:68
@ Class
Definition node.h:57
@ ExternalPage
Definition node.h:64
@ Example
Definition node.h:63
bool isNamespace() const
Returns true if the node type is Namespace.
Definition node.h:143
bool isQmlBasicType() const
Returns true if the node type is QmlBasicType.
Definition node.h:151
bool isQmlType() const
Returns true if the node type is QmlType or QmlValueType.
Definition node.h:155
virtual bool isInternal() const
Returns true if the node's status is Internal, or if its parent is a class with Internal status.
Definition node.cpp:849
bool isHeader() const
Returns true if the node type is HeaderFile.
Definition node.h:140
virtual bool isPageNode() const
Returns true if this node represents something that generates a documentation page.
Definition node.h:182
virtual bool isTextPageNode() const
Returns true if the node is a PageNode but not an Aggregate.
Definition node.h:187
Aggregate * parent() const
Returns the node's parent pointer.
Definition node.h:244
virtual bool isAggregate() const
Returns true if this node is an aggregate, which means it inherits Aggregate and can therefore have c...
Definition node.h:170
NodeType nodeType() const
Returns this node's type.
Definition node.h:121
static bool nodeNameLessThan(const Node *first, const Node *second)
Returns true if the node n1 is less than node n2.
Definition node.cpp:57
virtual bool wasSeen() const
Returns the seen flag data member of this node if it is a NamespaceNode or a CollectionNode.
Definition node.h:225
LinkType
An unsigned char value that probably should be moved out of the Node base class.
Definition node.h:112
virtual bool isClassNode() const
Returns true if this is an instance of ClassNode.
Definition node.h:177
virtual bool isCollectionNode() const
Returns true if this is an instance of CollectionNode.
Definition node.h:178
@ Deprecated
Definition node.h:90
bool isQmlModule() const
Returns true if the node type is QmlModule.
Definition node.h:152
bool isIndexNode() const
Returns true if this node was created from something in an index file.
Definition node.h:141
Status status() const
Returns the node's status value.
Definition node.h:278
A PageNode is a Node that generates a documentation page.
Definition pagenode.h:18
This class provides exclusive access to the qdoc database, which consists of a forrest of trees and a...
static QDocDatabase * qdocDB()
Creates the singleton.
NamespaceNode * primaryTreeRoot()
Returns a pointer to the root node of the primary tree.
void setLocalSearch()
Definition text.h:12
const Atom * firstAtom() const
Definition text.h:34
Atom * firstAtom()
Definition text.h:21
Atom * lastAtom()
Definition text.h:22
const EnumNode * associatedEnum() const
Definition typedefnode.h:26
#define CONFIG_QHP
Definition config.h:375
#define CONFIG_VERSION
Definition config.h:392
#define CONFIG_PROJECT
Definition config.h:373
Combined button and popup list for selecting options.
QList< Node * > NodeList
Definition node.h:41