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 // Images are all placed within a single directory regardless of
392 // whether the source images are in a nested directory structure.
393 QStringList pieces = atom->string().split(QLatin1Char('/'));
394 project.m_files.insert("images/" + pieces.last());
395 }
396 atom = atom->next();
397 }
398
399 return true;
400}
401
402void HelpProjectWriter::generateSections(HelpProject &project, QXmlStreamWriter &writer,
403 const Node *node)
404{
405 /*
406 Don't include index nodes in the help file.
407 */
408 if (node->isIndexNode())
409 return;
410 if (!generateSection(project, writer, node))
411 return;
412
413 if (node->isAggregate()) {
414 const auto *aggregate = static_cast<const Aggregate *>(node);
415
416 // Ensure that we don't visit nodes more than once.
417 NodeList childSet;
418 NodeList children = aggregate->childNodes();
419 std::sort(children.begin(), children.end(), Node::nodeNameLessThan);
420 for (auto *child : children) {
421 // Skip related non-members adopted by some other aggregate
422 if (child->parent() != aggregate)
423 continue;
424 // Process unseen group nodes (even though they're marked internal)
425 if (child->isGroup() && !child->wasSeen()) {
426 childSet << child;
427 continue;
428 }
429 if (child->isIndexNode())
430 continue;
431
432 const InclusionPolicy policy = Config::instance().createInclusionPolicy();
433 const NodeContext context = child->createContext();
434 if (!InclusionFilter::isIncluded(policy, context))
435 continue;
436
437 if (child->isTextPageNode()) {
438 if (!childSet.contains(child))
439 childSet << child;
440 } else {
441 // Store member status of children
442 project.m_memberStatus[node].insert(child->status());
443 if (child->isFunction() && static_cast<const FunctionNode *>(child)->isOverload())
444 continue;
445 if (!childSet.contains(child))
446 childSet << child;
447 }
448 }
449 for (const auto *child : std::as_const(childSet))
450 generateSections(project, writer, child);
451 }
452}
453
455{
456 // Warn if .qhp configuration was expected but not provided
457 if (auto &config = Config::instance(); m_projects.isEmpty() && config.get(CONFIG_QHP).asBool()) {
458 config.location().warning(u"Documentation configuration for '%1' doesn't define a help project (qhp)"_s
459 .arg(config.get(CONFIG_PROJECT).asString()));
460 }
461 for (HelpProject &project : m_projects)
462 generateProject(project);
463}
464
465void HelpProjectWriter::writeSection(QXmlStreamWriter &writer, const QString &path,
466 const QString &value)
467{
468 writer.writeStartElement(QStringLiteral("section"));
469 writer.writeAttribute(QStringLiteral("ref"), path);
470 writer.writeAttribute(QStringLiteral("title"), value);
471 writer.writeEndElement(); // section
472}
473
474/*!
475 Write subsections for all members, compatibility members and obsolete members.
476 */
477void HelpProjectWriter::addMembers(HelpProject &project, QXmlStreamWriter &writer, const Node *node)
478{
479 QString href = m_gen->fullDocumentLocation(node);
480 href = href.left(href.size() - 5);
481 if (href.isEmpty())
482 return;
483
484 bool derivedClass = false;
485 if (node->isClassNode())
486 derivedClass = !(static_cast<const ClassNode *>(node)->baseClasses().isEmpty());
487
488 // Do not generate a 'List of all members' for namespaces or header files,
489 // but always generate it for derived classes and QML types (but not QML value types)
490 if (!node->isNamespace() && !node->isHeader() && !node->isQmlBasicType() && !node->isWrapper()
491 && (derivedClass || node->isQmlType() || !project.m_memberStatus[node].isEmpty())) {
492 QString membersPath = href + QStringLiteral("-members.html");
493 writeSection(writer, membersPath, QStringLiteral("List of all members"));
494 }
495 if (project.m_memberStatus[node].contains(Node::Deprecated)) {
496 QString obsoletePath = href + QStringLiteral("-obsolete.html");
497 writeSection(writer, obsoletePath, QStringLiteral("Obsolete members"));
498 }
499}
500
501void HelpProjectWriter::writeNode(HelpProject &project, QXmlStreamWriter &writer, const Node *node)
502{
503 QString href = m_gen->fullDocumentLocation(node);
504 QString objName = node->name();
505
506 switch (node->nodeType()) {
507
508 case NodeType::Class:
509 case NodeType::Struct:
510 case NodeType::Union:
513 QString typeStr = m_gen->typeString(node);
514 if (!typeStr.isEmpty())
515 typeStr[0] = typeStr[0].toTitleCase();
516 writer.writeStartElement("section");
517 writer.writeAttribute("ref", href);
518 if (node->parent() && !node->parent()->name().isEmpty())
519 writer.writeAttribute("title",
520 QStringLiteral("%1::%2 %3 Reference")
521 .arg(node->parent()->name(), objName, typeStr));
522 else
523 writer.writeAttribute("title", QStringLiteral("%1 %2 Reference").arg(objName, typeStr));
524
525 addMembers(project, writer, node);
526 writer.writeEndElement(); // section
527 } break;
528
529 case NodeType::Namespace:
530 writeSection(writer, href, "%1 Namespace Reference"_L1.arg(objName));
531 break;
532
535 case NodeType::Page:
536 case NodeType::Group:
537 case NodeType::Module:
538 case NodeType::QmlModule: {
539 writer.writeStartElement("section");
540 writer.writeAttribute("ref", href);
541 writer.writeAttribute("title", node->fullTitle());
542 if (node->nodeType() == NodeType::HeaderFile)
543 addMembers(project, writer, node);
544 writer.writeEndElement(); // section
545 } break;
546 default:;
547 }
548}
549
550void HelpProjectWriter::generateProject(HelpProject &project)
551{
552 const Node *rootNode;
553
554 // Restrict searching only to the local (primary) tree
555 QList<Tree *> searchOrder = m_qdb->searchOrder();
557
558 if (!project.m_indexRoot.isEmpty())
559 rootNode = m_qdb->findPageNodeByTitle(project.m_indexRoot);
560 else
561 rootNode = m_qdb->primaryTreeRoot();
562
563 if (rootNode == nullptr)
564 return;
565
566 project.m_files.clear();
567 project.m_keywords.clear();
568
569 QFile file(m_outputDir + QDir::separator() + project.m_fileName);
570 if (!file.open(QFile::WriteOnly))
571 return;
572
573 QXmlStreamWriter writer(&file);
574 writer.setAutoFormatting(true);
575 writer.writeStartDocument();
576 writer.writeStartElement("QtHelpProject");
577 writer.writeAttribute("version", "1.0");
578
579 // Write metaData, virtualFolder and namespace elements.
580 writer.writeTextElement("namespace", project.m_helpNamespace);
581 writer.writeTextElement("virtualFolder", project.m_virtualFolder);
582 writer.writeStartElement("metaData");
583 writer.writeAttribute("name", "version");
584 writer.writeAttribute("value", project.m_version);
585 writer.writeEndElement();
586
587 // Write customFilter elements.
588 for (auto it = project.m_customFilters.constBegin(); it != project.m_customFilters.constEnd();
589 ++it) {
590 writer.writeStartElement("customFilter");
591 writer.writeAttribute("name", it.key());
592 QStringList sortedAttributes = it.value().values();
593 sortedAttributes.sort();
594 for (const auto &filter : std::as_const(sortedAttributes))
595 writer.writeTextElement("filterAttribute", filter);
596 writer.writeEndElement(); // customFilter
597 }
598
599 // Start the filterSection.
600 writer.writeStartElement("filterSection");
601
602 // Write filterAttribute elements.
603 QStringList sortedFilterAttributes = project.m_filterAttributes.values();
604 sortedFilterAttributes.sort();
605 for (const auto &filterName : std::as_const(sortedFilterAttributes))
606 writer.writeTextElement("filterAttribute", filterName);
607
608 writer.writeStartElement("toc");
609 writer.writeStartElement("section");
610 const Node *node = m_qdb->findPageNodeByTitle(project.m_indexTitle);
611 if (!node)
612 node = m_qdb->findNodeByNameAndType(QStringList(project.m_indexTitle), &Node::isPageNode);
613 if (!node)
614 node = m_qdb->findNodeByNameAndType(QStringList("index.html"), &Node::isPageNode);
615 QString indexPath;
616 if (node)
617 indexPath = m_gen->fullDocumentLocation(node);
618 else
619 indexPath = "index.html";
620 writer.writeAttribute("ref", indexPath);
621 writer.writeAttribute("title", project.m_indexTitle);
622
623 generateSections(project, writer, rootNode);
624
625 for (int i = 0; i < project.m_subprojects.size(); i++) {
626 SubProject subproject = project.m_subprojects[i];
627
628 if (subproject.m_type == QLatin1String("manual")) {
629
630 const Node *indexPage = m_qdb->findNodeForTarget(subproject.m_indexTitle, nullptr);
631 if (indexPage) {
632 Text indexBody = indexPage->doc().body();
633 const Atom *atom = indexBody.firstAtom();
634 QStack<int> sectionStack;
635 bool inItem = false;
636
637 while (atom) {
638 switch (atom->type()) {
639 case Atom::ListLeft:
640 sectionStack.push(0);
641 break;
642 case Atom::ListRight:
643 if (sectionStack.pop() > 0)
644 writer.writeEndElement(); // section
645 break;
647 inItem = true;
648 break;
650 inItem = false;
651 break;
652 case Atom::Link:
653 if (inItem) {
654 if (sectionStack.top() > 0)
655 writer.writeEndElement(); // section
656
657 const Node *page = m_qdb->findNodeForTarget(atom->string(), nullptr);
658 writer.writeStartElement("section");
659 QString indexPath = m_gen->fullDocumentLocation(page);
660 writer.writeAttribute("ref", indexPath);
661 writer.writeAttribute("title", atom->linkText());
662
663 sectionStack.top() += 1;
664 }
665 break;
666 default:;
667 }
668
669 if (atom == indexBody.lastAtom())
670 break;
671 atom = atom->next();
672 }
673 } else
674 Config::instance().location().warning(
675 "Failed to find %1.indexTitle '%2'"_L1.arg(subproject.m_prefix, subproject.m_indexTitle));
676
677 } else {
678
679 writer.writeStartElement("section");
680 QString indexPath = m_gen->fullDocumentLocation(
681 m_qdb->findNodeForTarget(subproject.m_indexTitle, nullptr));
682 if (indexPath.isEmpty() && !subproject.m_indexTitle.isEmpty())
683 Config::instance().location().warning(
684 "Failed to find %1.indexTitle '%2'"_L1.arg(subproject.m_prefix, subproject.m_indexTitle));
685 writer.writeAttribute("ref", indexPath);
686 writer.writeAttribute("title", subproject.m_title);
687
688 if (subproject.m_sortPages) {
689 QStringList titles = subproject.m_nodes.keys();
690 titles.sort();
691 for (const auto &title : std::as_const(titles)) {
692 writeNode(project, writer, subproject.m_nodes[title]);
693 }
694 } else {
695 // Find a contents node and navigate from there, using the NextLink values.
696 QSet<QString> visited;
697 bool contentsFound = false;
698 for (const auto *node : std::as_const(subproject.m_nodes)) {
699 QString nextTitle = node->links().value(Node::NextLink).first;
700 if (!nextTitle.isEmpty()
701 && node->links().value(Node::ContentsLink).first.isEmpty()) {
702
703 const Node *nextPage = m_qdb->findNodeForTarget(nextTitle, nullptr);
704
705 // Write the contents node.
706 writeNode(project, writer, node);
707 contentsFound = true;
708
709 while (nextPage) {
710 writeNode(project, writer, nextPage);
711 nextTitle = nextPage->links().value(Node::NextLink).first;
712 if (nextTitle.isEmpty() || visited.contains(nextTitle))
713 break;
714 nextPage = m_qdb->findNodeForTarget(nextTitle, nullptr);
715 visited.insert(nextTitle);
716 }
717 break;
718 }
719 }
720 // No contents/nextpage links found, write all nodes unsorted
721 if (!contentsFound) {
722 QList<const Node *> subnodes = subproject.m_nodes.values();
723
724 std::sort(subnodes.begin(), subnodes.end(), Node::nodeNameLessThan);
725
726 for (const auto *node : std::as_const(subnodes))
727 writeNode(project, writer, node);
728 }
729 }
730
731 writer.writeEndElement(); // section
732 }
733 }
734
735 // Restore original search order
736 m_qdb->setSearchOrder(searchOrder);
737
738 writer.writeEndElement(); // section
739 writer.writeEndElement(); // toc
740
741 writer.writeStartElement("keywords");
742 std::sort(project.m_keywords.begin(), project.m_keywords.end());
743 for (const auto &k : std::as_const(project.m_keywords)) {
744 for (const auto &id : std::as_const(k.m_ids)) {
745 writer.writeStartElement("keyword");
746 writer.writeAttribute("name", k.m_name);
747 writer.writeAttribute("id", id);
748 writer.writeAttribute("ref", k.m_ref);
749 writer.writeEndElement(); //keyword
750 }
751 }
752 writer.writeEndElement(); // keywords
753
754 writer.writeStartElement("files");
755
756 // The list of files to write is the union of generated files and
757 // other files (images and extras) included in the project
758 QSet<QString> files =
759 QSet<QString>(m_gen->outputFileNames().cbegin(), m_gen->outputFileNames().cend());
760 files.unite(project.m_files);
761 files.unite(project.m_extraFiles);
762 QStringList sortedFiles = files.values();
763 sortedFiles.sort();
764 for (const auto &usedFile : std::as_const(sortedFiles)) {
765 if (!usedFile.isEmpty())
766 writer.writeTextElement("file", usedFile);
767 }
768 writer.writeEndElement(); // files
769
770 writer.writeEndElement(); // filterSection
771 writer.writeEndElement(); // QtHelpProject
772 writer.writeEndDocument();
773 file.close();
774}
775
776QT_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:21
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:408
#define CONFIG_VERSION
Definition config.h:427
#define CONFIG_PROJECT
Definition config.h:404
NodeType
Definition genustypes.h:150
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:906
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:59
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:116
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