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));
393 QStringList pieces = atom->string().split(QLatin1Char(
'/'));
394 project.m_files.insert(
"images/" + pieces.last());
406
407
410 if (!generateSection(project, writer, node))
414 const auto *aggregate =
static_cast<
const Aggregate *>(node);
420 for (
auto *child : children) {
422 if (child->parent() != aggregate)
425 if (child->isGroup() && !child->wasSeen()) {
429 if (child->isIndexNode())
432 const InclusionPolicy policy = Config::instance().createInclusionPolicy();
433 const NodeContext context = child->createContext();
434 if (!InclusionFilter::isIncluded(policy, context))
437 if (child->isTextPageNode()) {
438 if (!childSet.contains(child))
442 project.m_memberStatus[node].insert(child->status());
443 if (child->isFunction() &&
static_cast<
const FunctionNode *>(child)->isOverload())
445 if (!childSet.contains(child))
449 for (
const auto *child : std::as_const(childSet))
450 generateSections(project, writer, child);
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
461 for (HelpProject &project : m_projects)
462 generateProject(project);
465void HelpProjectWriter::writeSection(QXmlStreamWriter &writer,
const QString &path,
466 const QString &value)
468 writer.writeStartElement(QStringLiteral(
"section"));
469 writer.writeAttribute(QStringLiteral(
"ref"), path);
470 writer.writeAttribute(QStringLiteral(
"title"), value);
471 writer.writeEndElement();
475
476
479 QString href = m_gen->fullDocumentLocation(node);
480 href = href.left(href.size() - 5);
484 bool derivedClass =
false;
486 derivedClass = !(
static_cast<
const ClassNode *>(node)->baseClasses().isEmpty());
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"));
496 QString obsoletePath = href + QStringLiteral(
"-obsolete.html");
497 writeSection(writer, obsoletePath, QStringLiteral(
"Obsolete members"));
503 QString href = m_gen->fullDocumentLocation(node);
504 QString objName = node->name();
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);
519 writer.writeAttribute(
"title",
520 QStringLiteral(
"%1::%2 %3 Reference")
521 .arg(node
->parent()->name(), objName, typeStr));
523 writer.writeAttribute(
"title", QStringLiteral(
"%1 %2 Reference").arg(objName, typeStr));
525 addMembers(project, writer, node);
526 writer.writeEndElement();
529 case NodeType::Namespace:
530 writeSection(writer, href,
"%1 Namespace Reference"_L1.arg(objName));
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();
552 const Node *rootNode;
555 QList<Tree *> searchOrder = m_qdb->searchOrder();
558 if (!project.m_indexRoot.isEmpty())
559 rootNode = m_qdb->findPageNodeByTitle(project.m_indexRoot);
563 if (rootNode ==
nullptr)
566 project.m_files.clear();
567 project.m_keywords.clear();
569 QFile file(m_outputDir + QDir::separator() + project.m_fileName);
570 if (!file.open(QFile::WriteOnly))
573 QXmlStreamWriter writer(&file);
574 writer.setAutoFormatting(
true);
575 writer.writeStartDocument();
576 writer.writeStartElement(
"QtHelpProject");
577 writer.writeAttribute(
"version",
"1.0");
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();
588 for (
auto it = project.m_customFilters.constBegin(); it != project.m_customFilters.constEnd();
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();
600 writer.writeStartElement(
"filterSection");
603 QStringList sortedFilterAttributes = project.m_filterAttributes.values();
604 sortedFilterAttributes.sort();
605 for (
const auto &filterName : std::as_const(sortedFilterAttributes))
606 writer.writeTextElement(
"filterAttribute", filterName);
608 writer.writeStartElement(
"toc");
609 writer.writeStartElement(
"section");
610 const Node *node = m_qdb->findPageNodeByTitle(project.m_indexTitle);
612 node = m_qdb->findNodeByNameAndType(QStringList(project.m_indexTitle), &
Node::isPageNode);
614 node = m_qdb->findNodeByNameAndType(QStringList(
"index.html"), &
Node::isPageNode);
617 indexPath = m_gen->fullDocumentLocation(node);
619 indexPath =
"index.html";
620 writer.writeAttribute(
"ref", indexPath);
621 writer.writeAttribute(
"title", project.m_indexTitle);
623 generateSections(project, writer, rootNode);
625 for (
int i = 0; i < project.m_subprojects.size(); i++) {
626 SubProject subproject = project.m_subprojects[i];
628 if (subproject.m_type == QLatin1String(
"manual")) {
630 const Node *indexPage = m_qdb->findNodeForTarget(subproject.m_indexTitle,
nullptr);
634 QStack<
int> sectionStack;
640 sectionStack.push(0);
643 if (sectionStack.pop() > 0)
644 writer.writeEndElement();
654 if (sectionStack.top() > 0)
655 writer.writeEndElement();
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());
663 sectionStack.top() += 1;
674 Config::instance().location().warning(
675 "Failed to find %1.indexTitle '%2'"_L1.arg(subproject.m_prefix, subproject.m_indexTitle));
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);
689 QStringList titles = subproject.m_nodes.keys();
691 for (
const auto &title : std::as_const(titles)) {
692 writeNode(project, writer, subproject.m_nodes[title]);
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()) {
703 const Node *nextPage = m_qdb->findNodeForTarget(nextTitle,
nullptr);
706 writeNode(project, writer, node);
707 contentsFound =
true;
710 writeNode(project, writer, nextPage);
711 nextTitle = nextPage->links().value(Node::NextLink).first;
712 if (nextTitle.isEmpty() || visited.contains(nextTitle))
714 nextPage = m_qdb->findNodeForTarget(nextTitle,
nullptr);
715 visited.insert(nextTitle);
721 if (!contentsFound) {
722 QList<
const Node *> subnodes = subproject.m_nodes.values();
726 for (
const auto *node : std::as_const(subnodes))
727 writeNode(project, writer, node);
731 writer.writeEndElement();
736 m_qdb->setSearchOrder(searchOrder);
738 writer.writeEndElement();
739 writer.writeEndElement();
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();
752 writer.writeEndElement();
754 writer.writeStartElement(
"files");
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();
764 for (
const auto &usedFile : std::as_const(sortedFiles)) {
765 if (!usedFile.isEmpty())
766 writer.writeTextElement(
"file", usedFile);
768 writer.writeEndElement();
770 writer.writeEndElement();
771 writer.writeEndElement();
772 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
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.