22#include <QtCore/qhash.h>
26using namespace Qt::StringLiterals;
30 reset(defaultFileName, g);
38
39
40
41
46 Config &config = Config::instance();
47 m_outputDir = config.getOutputDir();
49 const QStringList names{config.get(
CONFIG_QHP + Config::dot +
"projects").asStringList()};
51 for (
const auto &projectName : names) {
53 project.m_name = projectName;
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();
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();
77 const auto excludedPrefixes = config.get(prefix +
"excluded").asStringSet();
78 for (
auto name : excludedPrefixes)
79 project.m_excluded.insert(name.replace(QLatin1Char(
'\\'), QLatin1Char(
'/')));
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())
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());
93 subproject.m_prefix = std::move(subprefix);
94 project.m_subprojects.append(subproject);
97 if (project.m_subprojects.isEmpty()) {
98 SubProject subproject;
99 readSelectors(subproject, config.get(prefix +
"selectors").asStringList());
100 project.m_subprojects.insert(0, subproject);
103 m_projects.append(project);
109 QHash<QString, NodeType> typeHash;
137 for (
const QString &selector : selectors) {
138 QStringList pieces = selector.split(QLatin1Char(
':'));
140 if (pieces.size() > 1 && typeHash.value(pieces[0].toLower()) == NodeType::Page)
143 QString typeName = pieces.takeFirst().toLower();
144 if (!typeHash.contains(typeName))
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();
163 for (HelpProject &project : m_projects)
164 project.m_extraFiles.insert(file);
169 QString ref = m_gen->fullDocumentLocation(node);
178 return Keyword(name, id, ref);
180 const QString &name = node->name();
181 QString moduleName = node->logicalModuleName();
182 QStringList ids(
"QML." + name);
183 if (!moduleName.isEmpty()) {
187 ids <<
"QML." + moduleName + majorVersion +
"." + name;
189 return Keyword(name, ids, ref);
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);
196 const auto *pageNode =
static_cast<
const PageNode *>(node);
197 return Keyword(pageNode->fullTitle(), pageNode->fullTitle(), ref);
199 return Keyword(node->name(), node->name(), ref);
206 if (!node->url().isEmpty() && !(project
.m_includeIndexNodes && !node->url().startsWith(
"http")))
213 const InclusionPolicy policy = Config::instance().createInclusionPolicy();
218 if (node->name().isEmpty())
222 if (!docPath.isEmpty() && project.m_excluded.contains(docPath))
225 QString objName = node
->isTextPageNode() ? node->fullTitle() : node->fullDocumentName();
229 for (
auto &subproject : project.m_subprojects) {
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()))) {
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) {
243 m->isTextPageNode() ? m->fullTitle() : m->fullDocumentName();
244 subproject.m_nodes.insert(memberName, m);
247 }
else if (!subproject.m_groups.isEmpty()) {
250 }
else if (node->isTextPageNode()) {
251 if (node->isExternalPage() || node->fullTitle().isEmpty())
254 subproject.m_nodes.insert(objName, node);
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);
263 if (kw->count() > 1) {
264 if (ref_parts.count() > 1)
265 ref_parts.pop_back();
266 ref_parts << kw->string(1);
268 project.m_keywords.append(Keyword(kw->string(), kw->string(),
269 ref_parts.join(
'#'_L1)));
282 project.m_keywords.append(keywordDetails(node));
286 appendDocKeywords(node);
287 project.m_keywords.append(keywordDetails(node));
291 project.m_keywords.append(keywordDetails(node));
296 project.m_keywords.append(keywordDetails(node));
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)
306 if (!node->parent()->name().isEmpty()) {
307 name = id = node->parent()->name() +
"::" + item.name();
309 name = id = item.name();
311 QString ref = m_gen->fullDocumentLocation(node);
312 project.m_keywords.append(Keyword(std::move(name), std::move(id), std::move(ref)));
320 if (!node->fullTitle().isEmpty()) {
321 appendDocKeywords(node);
322 project.m_keywords.append(keywordDetails(node));
328 project.m_keywords.append(keywordDetails(node));
332 const auto *funcNode =
static_cast<
const FunctionNode *>(node);
335
336
337
338
340 project.m_keywords.append(keywordDetails(node));
347 project.m_keywords.append(keywordDetails(node));
360 const auto *typedefNode =
static_cast<
const TypedefNode *>(node);
361 Keyword typedefDetails = keywordDetails(node);
366 typedefDetails.m_ref = m_gen->fullDocumentLocation(enumNode);
368 project.m_keywords.append(typedefDetails);
372 project.m_keywords.append(keywordDetails(node));
378 if (!node->fullTitle().isEmpty()) {
379 appendDocKeywords(node);
380 project.m_keywords.append(keywordDetails(node));
391 QStringList pieces = atom->string().split(
'/'_L1);
392 project.m_files.insert(
"%1/%2"_L1.arg(m_gen->imagesOutputDir(), pieces.last()));
404
405
408 if (!generateSection(project, writer, node))
412 const auto *aggregate =
static_cast<
const Aggregate *>(node);
418 for (
auto *child : children) {
420 if (child->parent() != aggregate)
423 if (child->isGroup() && !child->wasSeen()) {
427 if (child->isIndexNode())
430 const InclusionPolicy policy = Config::instance().createInclusionPolicy();
431 const NodeContext context = child->createContext();
432 if (!InclusionFilter::isIncluded(policy, context))
435 if (child->isTextPageNode()) {
436 if (!childSet.contains(child))
440 project.m_memberStatus[node].insert(child->status());
441 if (child->isFunction() &&
static_cast<
const FunctionNode *>(child)->isOverload())
443 if (!childSet.contains(child))
447 for (
const auto *child : std::as_const(childSet))
448 generateSections(project, writer, child);
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
459 for (HelpProject &project : m_projects)
460 generateProject(project);
463void HelpProjectWriter::writeSection(QXmlStreamWriter &writer,
const QString &path,
464 const QString &value)
466 writer.writeStartElement(QStringLiteral(
"section"));
467 writer.writeAttribute(QStringLiteral(
"ref"), path);
468 writer.writeAttribute(QStringLiteral(
"title"), value);
469 writer.writeEndElement();
473
474
477 QString href = m_gen->fullDocumentLocation(node);
478 href = href.left(href.size() - 5);
482 bool derivedClass =
false;
484 derivedClass = !(
static_cast<
const ClassNode *>(node)->baseClasses().isEmpty());
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"));
494 QString obsoletePath = href + QStringLiteral(
"-obsolete.html");
495 writeSection(writer, obsoletePath, QStringLiteral(
"Obsolete members"));
501 QString href = m_gen->fullDocumentLocation(node);
502 QString objName = node->name();
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);
517 writer.writeAttribute(
"title",
518 QStringLiteral(
"%1::%2 %3 Reference")
519 .arg(node
->parent()->name(), objName, typeStr));
521 writer.writeAttribute(
"title", QStringLiteral(
"%1 %2 Reference").arg(objName, typeStr));
523 addMembers(project, writer, node);
524 writer.writeEndElement();
527 case NodeType::Namespace:
528 writeSection(writer, href,
"%1 Namespace Reference"_L1.arg(objName));
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();
550 const Node *rootNode;
553 QList<Tree *> searchOrder = m_qdb->searchOrder();
556 if (!project.m_indexRoot.isEmpty())
557 rootNode = m_qdb->findPageNodeByTitle(project.m_indexRoot);
561 if (rootNode ==
nullptr)
564 project.m_files.clear();
565 project.m_keywords.clear();
567 QFile file(m_outputDir + QDir::separator() + project.m_fileName);
568 if (!file.open(QFile::WriteOnly))
571 QXmlStreamWriter writer(&file);
572 writer.setAutoFormatting(
true);
573 writer.writeStartDocument();
574 writer.writeStartElement(
"QtHelpProject");
575 writer.writeAttribute(
"version",
"1.0");
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();
586 for (
auto it = project.m_customFilters.constBegin(); it != project.m_customFilters.constEnd();
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();
598 writer.writeStartElement(
"filterSection");
601 QStringList sortedFilterAttributes = project.m_filterAttributes.values();
602 sortedFilterAttributes.sort();
603 for (
const auto &filterName : std::as_const(sortedFilterAttributes))
604 writer.writeTextElement(
"filterAttribute", filterName);
606 writer.writeStartElement(
"toc");
607 writer.writeStartElement(
"section");
608 const Node *node = m_qdb->findPageNodeByTitle(project.m_indexTitle);
610 node = m_qdb->findNodeByNameAndType(QStringList(project.m_indexTitle), &
Node::isPageNode);
612 node = m_qdb->findNodeByNameAndType(QStringList(
"index.html"), &
Node::isPageNode);
615 indexPath = m_gen->fullDocumentLocation(node);
617 indexPath =
"index.html";
618 writer.writeAttribute(
"ref", indexPath);
619 writer.writeAttribute(
"title", project.m_indexTitle);
621 generateSections(project, writer, rootNode);
623 for (
int i = 0; i < project.m_subprojects.size(); i++) {
624 SubProject subproject = project.m_subprojects[i];
626 if (subproject.m_type == QLatin1String(
"manual")) {
628 const Node *indexPage = m_qdb->findNodeForTarget(subproject.m_indexTitle,
nullptr);
632 QStack<
int> sectionStack;
638 sectionStack.push(0);
641 if (sectionStack.pop() > 0)
642 writer.writeEndElement();
652 if (sectionStack.top() > 0)
653 writer.writeEndElement();
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());
661 sectionStack.top() += 1;
672 Config::instance().location().warning(
673 "Failed to find %1.indexTitle '%2'"_L1.arg(subproject.m_prefix, subproject.m_indexTitle));
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);
687 QStringList titles = subproject.m_nodes.keys();
689 for (
const auto &title : std::as_const(titles)) {
690 writeNode(project, writer, subproject.m_nodes[title]);
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()) {
701 const Node *nextPage = m_qdb->findNodeForTarget(nextTitle,
nullptr);
704 writeNode(project, writer, node);
705 contentsFound =
true;
708 writeNode(project, writer, nextPage);
709 nextTitle = nextPage->links().value(Node::NextLink).first;
710 if (nextTitle.isEmpty() || visited.contains(nextTitle))
712 nextPage = m_qdb->findNodeForTarget(nextTitle,
nullptr);
713 visited.insert(nextTitle);
719 if (!contentsFound) {
720 QList<
const Node *> subnodes = subproject.m_nodes.values();
724 for (
const auto *node : std::as_const(subnodes))
725 writeNode(project, writer, node);
729 writer.writeEndElement();
734 m_qdb->setSearchOrder(searchOrder);
736 writer.writeEndElement();
737 writer.writeEndElement();
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();
750 writer.writeEndElement();
752 writer.writeStartElement(
"files");
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();
762 for (
const auto &usedFile : std::as_const(sortedFiles)) {
763 if (!usedFile.isEmpty())
764 writer.writeTextElement(
"file", usedFile);
766 writer.writeEndElement();
768 writer.writeEndElement();
769 writer.writeEndElement();
770 writer.writeEndDocument();
const NodeList & childNodes() const
Returns a const reference to the child list.
The Atom class is the fundamental unit for representing documents internally.
AtomType type() const
Return the type of this atom.
const Atom * next() const
Return the next atom in the atom list.
The ClassNode represents a C++ class.
The Config class contains the configuration variables for controlling how qdoc produces documentation...
const Location & location() const
Returns the starting location of a qdoc comment.
const Text & body() const
This node is used to represent any kind of function being documented.
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.
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.
const Atom * firstAtom() const
const EnumNode * associatedEnum() const
Combined button and popup list for selecting options.
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.
const Doc & doc() const
Returns a reference to the node's Doc data member.
bool isQmlNode() const
Returns true if this node's Genus value is QML.
bool isGroup() const
Returns true if the node type is Group.
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...
bool isNamespace() const
Returns true if the node type is Namespace.
bool isTypedef() const
Returns true if the node type is Typedef.
bool isQmlBasicType() const
Returns true if the node type is QmlBasicType.
bool isQmlType() const
Returns true if the node type is QmlType or QmlValueType.
bool isHeader() const
Returns true if the node type is HeaderFile.
NodeType nodeType() const override
Returns this node's type.
virtual bool isPageNode() const
Returns true if this node represents something that generates a documentation page.
bool isEnumType() const
Returns true if the node type is Enum.
virtual Status status() const
Returns the node's status value.
virtual bool isTextPageNode() const
Returns true if the node is a PageNode but not an Aggregate.
Aggregate * parent() const
Returns the node's parent pointer.
virtual bool isAggregate() const
Returns true if this node is an aggregate, which means it inherits Aggregate and can therefore have c...
static bool nodeNameLessThan(const Node *first, const Node *second)
Returns true if the node n1 is less than node n2.
virtual bool wasSeen() const
Returns the seen flag data member of this node if it is a NamespaceNode or a CollectionNode.
NodeContext createContext() const
virtual CollectionNode * logicalModule() const
If this is a QmlTypeNode, a pointer to its QML module is returned, which is a pointer to a Collection...
bool isRelatedNonmember() const
Returns true if this is a related nonmember of something.
virtual bool isClassNode() const
Returns true if this is an instance of ClassNode.
bool isQmlModule() const
Returns true if the node type is QmlModule.
bool isIndexNode() const
Returns true if this node was created from something in an index file.