19#include <QtCore/qhash.h>
23using namespace Qt::StringLiterals;
27 reset(defaultFileName, g);
35
36
37
38
43 Config &config = Config::instance();
44 m_outputDir = config.getOutputDir();
46 const QStringList names{config.get(
CONFIG_QHP + Config::dot +
"projects").asStringList()};
48 for (
const auto &projectName : names) {
50 project.m_name = projectName;
52 QString prefix =
CONFIG_QHP + Config::dot + projectName + Config::dot;
53 project.m_helpNamespace = config.get(prefix +
"namespace").asString();
54 project.m_virtualFolder = config.get(prefix +
"virtualFolder").asString();
56 project.m_fileName = config.get(prefix +
"file").asString();
57 if (project.m_fileName.isEmpty())
58 project.m_fileName = defaultFileName;
59 project.m_extraFiles = config.get(prefix +
"extraFiles").asStringSet();
60 project.m_extraFiles += config.get(
CONFIG_QHP + Config::dot +
"extraFiles").asStringSet();
61 project.m_indexTitle = config.get(prefix +
"indexTitle").asString();
62 project.m_indexRoot = config.get(prefix +
"indexRoot").asString();
63 project.m_filterAttributes = config.get(prefix +
"filterAttributes").asStringSet();
64 project.m_includeIndexNodes = config.get(prefix +
"includeIndexNodes").asBool();
65 const QSet<QString> customFilterNames = config.subVars(prefix +
"customFilters");
66 for (
const auto &filterName : customFilterNames) {
67 QString name{config.get(prefix +
"customFilters" + Config::dot + filterName
68 + Config::dot +
"name").asString()};
69 project.m_customFilters[name] =
70 config.get(prefix +
"customFilters" + Config::dot + filterName
71 + Config::dot +
"filterAttributes").asStringSet();
74 const auto excludedPrefixes = config.get(prefix +
"excluded").asStringSet();
75 for (
auto name : excludedPrefixes)
76 project.m_excluded.insert(name.replace(QLatin1Char(
'\\'), QLatin1Char(
'/')));
78 const auto subprojectPrefixes{config.get(prefix +
"subprojects").asStringList()};
79 for (
const auto &name : subprojectPrefixes) {
80 SubProject subproject;
81 QString subprefix = prefix +
"subprojects" + Config::dot + name + Config::dot;
82 subproject.m_title = config.get(subprefix +
"title").asString();
83 if (subproject.m_title.isEmpty())
85 subproject.m_indexTitle = config.get(subprefix +
"indexTitle").asString();
86 subproject.m_sortPages = config.get(subprefix +
"sortPages").asBool();
87 subproject.m_type = config.get(subprefix +
"type").asString();
88 readSelectors(subproject, config.get(subprefix +
"selectors").asStringList());
90 subproject.m_prefix = subprefix;
91 project.m_subprojects.append(subproject);
94 if (project.m_subprojects.isEmpty()) {
95 SubProject subproject;
96 readSelectors(subproject, config.get(prefix +
"selectors").asStringList());
97 project.m_subprojects.insert(0, subproject);
100 m_projects.append(project);
106 QHash<QString, Node::NodeType> typeHash;
133 for (
const QString &selector : selectors) {
134 QStringList pieces = selector.split(QLatin1Char(
':'));
136 if (pieces.size() > 1 && typeHash.value(pieces[0].toLower()) == Node::Page)
139 QString typeName = pieces.takeFirst().toLower();
140 if (!typeHash.contains(typeName))
143 subproject.m_selectors << typeHash.value(typeName);
144 if (!pieces.isEmpty()) {
145 pieces = pieces[0].split(QLatin1Char(
','));
146 for (
const auto &piece : std::as_const(pieces)) {
147 if (typeHash[typeName] == Node::Group
148 || typeHash[typeName] == Node::Module
149 || typeHash[typeName] == Node::QmlModule) {
150 subproject.m_groups << piece.toLower();
159 for (HelpProject &project : m_projects)
160 project.m_extraFiles.insert(file);
165 QString ref = m_gen->fullDocumentLocation(node);
168 QString name = (node->isEnumType() || node->isTypedef())
169 ? node->parent()->name()+
"::"+node->name()
171 QString id = (!node->isRelatedNonmember())
172 ? node->parent()->name()+
"::"+node->name()
174 return Keyword(name, id, ref);
176 const QString &name = node->name();
177 QString moduleName = node->logicalModuleName();
178 QStringList ids(
"QML." + name);
179 if (!moduleName.isEmpty()) {
180 QString majorVersion = node->logicalModule()
181 ? node->logicalModule()->logicalModuleVersion().split(
'.')[0]
183 ids <<
"QML." + moduleName + majorVersion +
"." + name;
185 return Keyword(name, ids, ref);
187 const QLatin1Char delim(
'.');
188 QStringList parts = node->logicalModuleName().split(delim) <<
"QML";
189 std::reverse(parts.begin(), parts.end());
190 return Keyword(node->logicalModuleName(), parts.join(delim), ref);
192 const auto *pageNode =
static_cast<
const PageNode *>(node);
193 return Keyword(pageNode->fullTitle(), pageNode->fullTitle(), ref);
195 return Keyword(node->name(), node->name(), ref);
202 if (!node->url().isEmpty() && !(project
.m_includeIndexNodes && !node->url().startsWith(
"http")))
212 if (node->name().isEmpty())
215 QString docPath = node->doc().location().filePath();
216 if (!docPath.isEmpty() && project.m_excluded.contains(docPath))
219 QString objName = node->isTextPageNode() ? node->fullTitle() : node->fullDocumentName();
223 for (
int i = 0; i < project.m_subprojects.size(); i++) {
224 SubProject subproject = project.m_subprojects[i];
226 if (subproject.m_selectors.isEmpty()) {
227 project.m_subprojects[i].m_nodes[objName] = node;
228 }
else if (subproject.m_selectors.contains(node
->nodeType())) {
231 if (project.m_subprojects[i].m_groups.contains(node->name().toLower())) {
234 for (
const Node *m : members) {
238 m->isTextPageNode() ? m->fullTitle() : m->fullDocumentName();
239 project.m_subprojects[i].m_nodes[memberName] = m;
242 }
else if (!project.m_subprojects[i].m_groups.isEmpty()) {
249 project.m_subprojects[i].m_nodes[objName] = node;
253 auto appendDocKeywords = [&](
const Node *n) {
254 for (
const auto *kw : n->doc().keywords()) {
255 if (!kw->string().isEmpty()) {
256 QStringList ref_parts = m_gen->fullDocumentLocation(n).split(
'#'_L1);
258 if (kw->count() > 1) {
259 if (ref_parts.count() > 1)
260 ref_parts.pop_back();
261 ref_parts << kw->string(1);
263 project.m_keywords.append(Keyword(kw->string(), kw->string(),
264 ref_parts.join(
'#'_L1)));
277 project.m_keywords.append(keywordDetails(node));
281 appendDocKeywords(node);
282 project.m_keywords.append(keywordDetails(node));
286 project.m_keywords.append(keywordDetails(node));
290 project.m_keywords.append(keywordDetails(node));
292 const auto *enumNode =
static_cast<
const EnumNode *>(node);
293 const auto items = enumNode->items();
294 for (
const auto &item : items) {
295 if (enumNode->itemAccess(item.name()) == Access::Private)
300 if (!node->parent()->name().isEmpty()) {
301 name = id = node->parent()->name() +
"::" + item.name();
303 name = id = item.name();
305 QString ref = m_gen->fullDocumentLocation(node);
306 project.m_keywords.append(Keyword(name, id, ref));
314 if (!node->fullTitle().isEmpty()) {
315 appendDocKeywords(node);
316 project.m_keywords.append(keywordDetails(node));
322 project.m_keywords.append(keywordDetails(node));
326 const auto *funcNode =
static_cast<
const FunctionNode *>(node);
329
330
331
332
334 project.m_keywords.append(keywordDetails(node));
341 project.m_keywords.append(keywordDetails(node));
354 const auto *typedefNode =
static_cast<
const TypedefNode *>(node);
355 Keyword typedefDetails = keywordDetails(node);
360 typedefDetails.m_ref = m_gen->fullDocumentLocation(enumNode);
362 project.m_keywords.append(typedefDetails);
366 project.m_keywords.append(keywordDetails(node));
372 if (!node->fullTitle().isEmpty()) {
373 appendDocKeywords(node);
374 project.m_keywords.append(keywordDetails(node));
387 QStringList pieces = atom->string().split(QLatin1Char(
'/'));
388 project.m_files.insert(
"images/" + pieces.last());
400
401
404 if (!generateSection(project, writer, node))
408 const auto *aggregate =
static_cast<
const Aggregate *>(node);
414 for (
auto *child : children) {
416 if (child->parent() != aggregate)
419 if (child->isGroup() && !child->wasSeen()) {
423 if (child->isIndexNode() || child->isPrivate())
425 if (child->isTextPageNode()) {
426 if (!childSet.contains(child))
430 project.m_memberStatus[node].insert(child->status());
431 if (child->isFunction() &&
static_cast<
const FunctionNode *>(child)->isOverload())
433 if (!childSet.contains(child))
437 for (
const auto *child : std::as_const(childSet))
438 generateSections(project, writer, child);
445 if (
auto &config = Config::instance(); m_projects.isEmpty() && config.get(
CONFIG_QHP).asBool()) {
446 config.location().warning(u"Documentation configuration for '%1' doesn't define a help project (qhp)"_s
449 for (HelpProject &project : m_projects)
450 generateProject(project);
453void HelpProjectWriter::writeSection(QXmlStreamWriter &writer,
const QString &path,
454 const QString &value)
456 writer.writeStartElement(QStringLiteral(
"section"));
457 writer.writeAttribute(QStringLiteral(
"ref"), path);
458 writer.writeAttribute(QStringLiteral(
"title"), value);
459 writer.writeEndElement();
463
464
467 QString href = m_gen->fullDocumentLocation(node);
468 href = href.left(href.size() - 5);
472 bool derivedClass =
false;
474 derivedClass = !(
static_cast<
const ClassNode *>(node)->baseClasses().isEmpty());
479 && (derivedClass || node
->isQmlType() || !project.m_memberStatus[node].isEmpty())) {
480 QString membersPath = href + QStringLiteral(
"-members.html");
481 writeSection(writer, membersPath, QStringLiteral(
"List of all members"));
484 QString obsoletePath = href + QStringLiteral(
"-obsolete.html");
485 writeSection(writer, obsoletePath, QStringLiteral(
"Obsolete members"));
491 QString href = m_gen->fullDocumentLocation(node);
492 QString objName = node->name();
501 QString typeStr = m_gen->typeString(node);
502 if (!typeStr.isEmpty())
503 typeStr[0] = typeStr[0].toTitleCase();
504 writer.writeStartElement(
"section");
505 writer.writeAttribute(
"ref", href);
507 writer.writeAttribute(
"title",
508 QStringLiteral(
"%1::%2 %3 Reference")
509 .arg(node
->parent()->name(), objName, typeStr));
511 writer.writeAttribute(
"title", QStringLiteral(
"%1 %2 Reference").arg(objName, typeStr));
513 addMembers(project, writer, node);
514 writer.writeEndElement();
517 case Node::Namespace:
518 writeSection(writer, href,
"%1 Namespace Reference"_L1.arg(objName));
527 writer.writeStartElement(
"section");
528 writer.writeAttribute(
"ref", href);
529 writer.writeAttribute(
"title", node->fullTitle());
530 if (node->nodeType() == Node::HeaderFile)
531 addMembers(project, writer, node);
532 writer.writeEndElement();
540 const Node *rootNode;
543 QList<Tree *> searchOrder = m_qdb->searchOrder();
546 if (!project.m_indexRoot.isEmpty())
547 rootNode = m_qdb->findPageNodeByTitle(project.m_indexRoot);
551 if (rootNode ==
nullptr)
554 project.m_files.clear();
555 project.m_keywords.clear();
557 QFile file(m_outputDir + QDir::separator() + project.m_fileName);
558 if (!file.open(QFile::WriteOnly))
561 QXmlStreamWriter writer(&file);
562 writer.setAutoFormatting(
true);
563 writer.writeStartDocument();
564 writer.writeStartElement(
"QtHelpProject");
565 writer.writeAttribute(
"version",
"1.0");
568 writer.writeTextElement(
"namespace", project.m_helpNamespace);
569 writer.writeTextElement(
"virtualFolder", project.m_virtualFolder);
570 writer.writeStartElement(
"metaData");
571 writer.writeAttribute(
"name",
"version");
572 writer.writeAttribute(
"value", project.m_version);
573 writer.writeEndElement();
576 for (
auto it = project.m_customFilters.constBegin(); it != project.m_customFilters.constEnd();
578 writer.writeStartElement(
"customFilter");
579 writer.writeAttribute(
"name", it.key());
580 QStringList sortedAttributes = it.value().values();
581 sortedAttributes.sort();
582 for (
const auto &filter : std::as_const(sortedAttributes))
583 writer.writeTextElement(
"filterAttribute", filter);
584 writer.writeEndElement();
588 writer.writeStartElement(
"filterSection");
591 QStringList sortedFilterAttributes = project.m_filterAttributes.values();
592 sortedFilterAttributes.sort();
593 for (
const auto &filterName : std::as_const(sortedFilterAttributes))
594 writer.writeTextElement(
"filterAttribute", filterName);
596 writer.writeStartElement(
"toc");
597 writer.writeStartElement(
"section");
598 const Node *node = m_qdb->findPageNodeByTitle(project.m_indexTitle);
600 node = m_qdb->findNodeByNameAndType(QStringList(project.m_indexTitle), &
Node::isPageNode);
602 node = m_qdb->findNodeByNameAndType(QStringList(
"index.html"), &
Node::isPageNode);
605 indexPath = m_gen->fullDocumentLocation(node);
607 indexPath =
"index.html";
608 writer.writeAttribute(
"ref", indexPath);
609 writer.writeAttribute(
"title", project.m_indexTitle);
611 generateSections(project, writer, rootNode);
613 for (
int i = 0; i < project.m_subprojects.size(); i++) {
614 SubProject subproject = project.m_subprojects[i];
616 if (subproject.m_type == QLatin1String(
"manual")) {
618 const Node *indexPage = m_qdb->findNodeForTarget(subproject.m_indexTitle,
nullptr);
622 QStack<
int> sectionStack;
628 sectionStack.push(0);
631 if (sectionStack.pop() > 0)
632 writer.writeEndElement();
642 if (sectionStack.top() > 0)
643 writer.writeEndElement();
645 const Node *page = m_qdb->findNodeForTarget(atom->string(),
nullptr);
646 writer.writeStartElement(
"section");
647 QString indexPath = m_gen->fullDocumentLocation(page);
648 writer.writeAttribute(
"ref", indexPath);
649 writer.writeAttribute(
"title", atom->linkText());
651 sectionStack.top() += 1;
662 Config::instance().location().warning(
663 "Failed to find %1.indexTitle '%2'"_L1.arg(subproject.m_prefix, subproject.m_indexTitle));
667 writer.writeStartElement(
"section");
668 QString indexPath = m_gen->fullDocumentLocation(
669 m_qdb->findNodeForTarget(subproject.m_indexTitle,
nullptr));
670 if (indexPath.isEmpty() && !subproject.m_indexTitle.isEmpty())
671 Config::instance().location().warning(
672 "Failed to find %1.indexTitle '%2'"_L1.arg(subproject.m_prefix, subproject.m_indexTitle));
673 writer.writeAttribute(
"ref", indexPath);
674 writer.writeAttribute(
"title", subproject.m_title);
677 QStringList titles = subproject.m_nodes.keys();
679 for (
const auto &title : std::as_const(titles)) {
680 writeNode(project, writer, subproject.m_nodes[title]);
684 QSet<QString> visited;
685 bool contentsFound =
false;
686 for (
const auto *node : std::as_const(subproject.m_nodes)) {
687 QString nextTitle = node->links().value(Node::NextLink).first;
688 if (!nextTitle.isEmpty()
689 && node->links().value(Node::ContentsLink).first.isEmpty()) {
691 const Node *nextPage = m_qdb->findNodeForTarget(nextTitle,
nullptr);
694 writeNode(project, writer, node);
695 contentsFound =
true;
698 writeNode(project, writer, nextPage);
699 nextTitle = nextPage->links().value(Node::NextLink).first;
700 if (nextTitle.isEmpty() || visited.contains(nextTitle))
702 nextPage = m_qdb->findNodeForTarget(nextTitle,
nullptr);
703 visited.insert(nextTitle);
709 if (!contentsFound) {
710 QList<
const Node *> subnodes = subproject.m_nodes.values();
714 for (
const auto *node : std::as_const(subnodes))
715 writeNode(project, writer, node);
719 writer.writeEndElement();
724 m_qdb->setSearchOrder(searchOrder);
726 writer.writeEndElement();
727 writer.writeEndElement();
729 writer.writeStartElement(
"keywords");
730 std::sort(project.m_keywords.begin(), project.m_keywords.end());
731 for (
const auto &k : std::as_const(project.m_keywords)) {
732 for (
const auto &id : std::as_const(k.m_ids)) {
733 writer.writeStartElement(
"keyword");
734 writer.writeAttribute(
"name", k.m_name);
735 writer.writeAttribute(
"id", id);
736 writer.writeAttribute(
"ref", k.m_ref);
737 writer.writeEndElement();
740 writer.writeEndElement();
742 writer.writeStartElement(
"files");
746 QSet<QString> files =
747 QSet<QString>(m_gen->outputFileNames().cbegin(), m_gen->outputFileNames().cend());
748 files.unite(project.m_files);
749 files.unite(project.m_extraFiles);
750 QStringList sortedFiles = files.values();
752 for (
const auto &usedFile : std::as_const(sortedFiles)) {
753 if (!usedFile.isEmpty())
754 writer.writeTextElement(
"file", usedFile);
756 writer.writeEndElement();
758 writer.writeEndElement();
759 writer.writeEndElement();
760 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.
A class for holding the members of a collection of doc pages.
const NodeList & members() const
The Config class contains the configuration variables for controlling how qdoc produces documentation...
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)
bool isExternalPage() const
Returns true if the node type is ExternalPage.
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.
bool isPrivate() const
Returns true if this node's access is Private.
bool isNamespace() const
Returns true if the node type is Namespace.
bool isQmlBasicType() const
Returns true if the node type is QmlBasicType.
bool isQmlType() const
Returns true if the node type is QmlType or QmlValueType.
virtual bool isInternal() const
Returns true if the node's status is Internal, or if its parent is a class with Internal status.
bool isHeader() const
Returns true if the node type is HeaderFile.
virtual bool isPageNode() const
Returns true if this node represents something that generates a documentation page.
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...
NodeType nodeType() const
Returns this node's type.
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.
LinkType
An unsigned char value that probably should be moved out of the Node base class.
virtual bool isClassNode() const
Returns true if this is an instance of ClassNode.
virtual bool isCollectionNode() const
Returns true if this is an instance of CollectionNode.
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.
Status status() const
Returns the node's status value.
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.