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