25#include <QtCore/qhash.h>
29using namespace Qt::StringLiterals;
33 reset(defaultFileName, g);
41
42
43
44
49 Config &config = Config::instance();
50 m_outputDir = config.getOutputDir();
52 const QStringList names{config.get(
CONFIG_QHP + Config::dot +
"projects").asStringList()};
54 for (
const auto &projectName : names) {
56 project.m_name = projectName;
58 QString prefix =
CONFIG_QHP + Config::dot + projectName + Config::dot;
59 project.m_helpNamespace = config.get(prefix +
"namespace").asString();
60 project.m_virtualFolder = config.get(prefix +
"virtualFolder").asString();
62 project.m_fileName = config.get(prefix +
"file").asString();
63 if (project.m_fileName.isEmpty())
64 project.m_fileName = defaultFileName;
65 project.m_extraFiles = config.get(prefix +
"extraFiles").asStringSet();
66 project.m_extraFiles += config.get(
CONFIG_QHP + Config::dot +
"extraFiles").asStringSet();
67 project.m_indexTitle = config.get(prefix +
"indexTitle").asString();
68 project.m_indexRoot = config.get(prefix +
"indexRoot").asString();
69 project.m_filterAttributes = config.get(prefix +
"filterAttributes").asStringSet();
70 project.m_includeIndexNodes = config.get(prefix +
"includeIndexNodes").asBool();
71 const QSet<QString> customFilterNames = config.subVars(prefix +
"customFilters");
72 for (
const auto &filterName : customFilterNames) {
73 QString name{config.get(prefix +
"customFilters" + Config::dot + filterName
74 + Config::dot +
"name").asString()};
75 project.m_customFilters[name] =
76 config.get(prefix +
"customFilters" + Config::dot + filterName
77 + Config::dot +
"filterAttributes").asStringSet();
80 const auto excludedPrefixes = config.get(prefix +
"excluded").asStringSet();
81 for (
auto name : excludedPrefixes)
82 project.m_excluded.insert(name.replace(QLatin1Char(
'\\'), QLatin1Char(
'/')));
84 const auto subprojectPrefixes{config.get(prefix +
"subprojects").asStringList()};
85 for (
const auto &name : subprojectPrefixes) {
86 SubProject subproject;
87 QString subprefix = prefix +
"subprojects" + Config::dot + name + Config::dot;
88 subproject.m_title = config.get(subprefix +
"title").asString();
89 if (subproject.m_title.isEmpty())
91 subproject.m_indexTitle = config.get(subprefix +
"indexTitle").asString();
92 subproject.m_sortPages = config.get(subprefix +
"sortPages").asBool();
93 subproject.m_type = config.get(subprefix +
"type").asString();
94 readSelectors(subproject, config.get(subprefix +
"selectors").asStringList());
96 subproject.m_prefix = std::move(subprefix);
97 project.m_subprojects.append(subproject);
100 if (project.m_subprojects.isEmpty()) {
101 SubProject subproject;
102 readSelectors(subproject, config.get(prefix +
"selectors").asStringList());
103 project.m_subprojects.insert(0, subproject);
106 m_projects.append(project);
112 QHash<QString, NodeType> typeHash;
140 for (
const QString &selector : selectors) {
141 QStringList pieces = selector.split(QLatin1Char(
':'));
143 if (pieces.size() > 1 && typeHash.value(pieces[0].toLower()) == NodeType::Page)
146 QString typeName = pieces.takeFirst().toLower();
147 if (!typeHash.contains(typeName))
150 subproject.m_selectors <<
static_cast<
unsigned char>(typeHash.value(typeName));
151 if (!pieces.isEmpty()) {
152 pieces = pieces[0].split(QLatin1Char(
','));
153 for (
const auto &piece : std::as_const(pieces)) {
154 if (typeHash[typeName] == NodeType::Group
155 || typeHash[typeName] == NodeType::Module
156 || typeHash[typeName] == NodeType::QmlModule) {
157 subproject.m_groups << piece.toLower();
166 for (HelpProject &project : m_projects)
167 project.m_extraFiles.insert(file);
172 QString ref = m_gen->fullDocumentLocation(node);
181 return Keyword(name, id, ref);
183 const QString &name = node->name();
184 QString moduleName = node->logicalModuleName();
185 QStringList ids(
"QML." + name);
186 if (!moduleName.isEmpty()) {
190 ids <<
"QML." + moduleName + majorVersion +
"." + name;
192 return Keyword(name, ids, ref);
194 const QLatin1Char delim(
'.');
195 QStringList parts = node->logicalModuleName().split(delim) <<
"QML";
196 std::reverse(parts.begin(), parts.end());
197 return Keyword(node->logicalModuleName(), parts.join(delim), ref);
199 const auto *pageNode =
static_cast<
const PageNode *>(node);
200 return Keyword(pageNode->fullTitle(), pageNode->fullTitle(), ref);
202 return Keyword(node->name(), node->name(), ref);
209 if (!node->url().isEmpty() && !(project
.m_includeIndexNodes && !node->url().startsWith(
"http")))
216 const InclusionPolicy policy = Config::instance().createInclusionPolicy();
221 if (node->name().isEmpty())
225 if (!docPath.isEmpty() && project.m_excluded.contains(docPath))
228 QString objName = node
->isTextPageNode() ? node->fullTitle() : node->fullDocumentName();
232 for (
auto &subproject : project.m_subprojects) {
234 if (subproject.m_selectors.isEmpty()) {
235 subproject.m_nodes.insert(objName, node);
236 }
else if (subproject.m_selectors.contains(
static_cast<
unsigned char>(node->nodeType()))) {
238 if (node->isCollectionNode()) {
239 if (subproject.m_groups.contains(node->name().toLower())) {
240 const auto *cn =
static_cast<
const CollectionNode *>(node);
241 const auto members = cn->members();
242 for (
const Node *m : members) {
246 m->isTextPageNode() ? m->fullTitle() : m->fullDocumentName();
247 subproject.m_nodes.insert(memberName, m);
250 }
else if (!subproject.m_groups.isEmpty()) {
253 }
else if (node->isTextPageNode()) {
254 if (node->isExternalPage() || node->fullTitle().isEmpty())
257 subproject.m_nodes.insert(objName, node);
261 auto appendDocKeywords = [&](
const Node *n) {
264 for (
const auto *kw : n->doc().keywords()) {
265 if (!kw->string().isEmpty()) {
266 QStringList ref_parts = m_gen->fullDocumentLocation(n).split(
'#'_L1);
268 if (kw->count() > 1) {
269 if (ref_parts.count() > 1)
270 ref_parts.pop_back();
271 ref_parts << kw->string(1);
273 project.m_keywords.append(Keyword(kw->string(), kw->string(),
274 ref_parts.join(
'#'_L1)));
287 project.m_keywords.append(keywordDetails(node));
291 appendDocKeywords(node);
292 project.m_keywords.append(keywordDetails(node));
296 project.m_keywords.append(keywordDetails(node));
301 project.m_keywords.append(keywordDetails(node));
303 const auto *enumNode =
static_cast<
const EnumNode *>(node);
304 const auto items = enumNode->items();
305 for (
const auto &item : items) {
306 if (enumNode->itemAccess(item.name()) == Access::Private)
311 if (!node->parent()->name().isEmpty()) {
312 name = id = node->parent()->name() +
"::" + item.name();
314 name = id = item.name();
316 QString ref = m_gen->fullDocumentLocation(node);
317 project.m_keywords.append(Keyword(std::move(name), std::move(id), std::move(ref)));
325 if (!node->fullTitle().isEmpty()) {
326 appendDocKeywords(node);
327 project.m_keywords.append(keywordDetails(node));
333 project.m_keywords.append(keywordDetails(node));
337 const auto *funcNode =
static_cast<
const FunctionNode *>(node);
340
341
342
343
344 if (funcNode->isQmlNode()) {
345 project.m_keywords.append(keywordDetails(node));
351 if (!funcNode->isSomeCtor())
352 project.m_keywords.append(keywordDetails(node));
365 const auto *typedefNode =
static_cast<
const TypedefNode *>(node);
366 Keyword typedefDetails = keywordDetails(node);
367 const EnumNode *enumNode = typedefNode->associatedEnum();
371 typedefDetails.m_ref = m_gen->fullDocumentLocation(enumNode);
373 project.m_keywords.append(typedefDetails);
377 project.m_keywords.append(keywordDetails(node));
383 if (!node->fullTitle().isEmpty()) {
384 appendDocKeywords(node);
385 project.m_keywords.append(keywordDetails(node));
396 QStringList pieces = atom->string().split(
'/'_L1);
397 project.m_files.insert(
"%1/%2"_L1.arg(m_gen->imagesOutputDir(), pieces.last()));
409
410
413 if (!generateSection(project, writer, node))
417 const auto *aggregate =
static_cast<
const Aggregate *>(node);
421 NodeList children = aggregate->childNodes();
423 for (
auto *child : children) {
425 if (child->parent() != aggregate)
428 if (child->isGroup() && !child->wasSeen()) {
432 if (child->isIndexNode())
435 const InclusionPolicy policy = Config::instance().createInclusionPolicy();
436 const NodeContext context = child->createContext();
437 if (!InclusionFilter::isIncluded(policy, context))
440 if (child->isTextPageNode()) {
441 if (!childSet.contains(child))
445 project.m_memberStatus[node].insert(child->status());
446 if (child->isFunction() &&
static_cast<
const FunctionNode *>(child)->isOverload())
448 if (!childSet.contains(child))
452 for (
const auto *child : std::as_const(childSet))
453 generateSections(project, writer, child);
460 if (
auto &config = Config::instance(); m_projects.isEmpty() && config.get(
CONFIG_QHP).asBool()) {
461 config.location().warning(u"Documentation configuration for '%1' doesn't define a help project (qhp)"_s
464 for (HelpProject &project : m_projects)
465 generateProject(project);
468void HelpProjectWriter::writeSection(QXmlStreamWriter &writer,
const QString &path,
469 const QString &value)
471 writer.writeStartElement(QStringLiteral(
"section"));
472 writer.writeAttribute(QStringLiteral(
"ref"), path);
473 writer.writeAttribute(QStringLiteral(
"title"), value);
474 writer.writeEndElement();
478
479
482 QString href = m_gen->fullDocumentLocation(node);
483 href = href.left(href.size() - 5);
487 bool derivedClass =
false;
488 if (node->isClassNode())
489 derivedClass = !(
static_cast<
const ClassNode *>(node)->baseClasses().isEmpty());
494 && (derivedClass || node
->isQmlType() || !project.m_memberStatus[node].isEmpty())) {
495 QString membersPath = href + QStringLiteral(
"-members.html");
496 writeSection(writer, membersPath, QStringLiteral(
"List of all members"));
499 QString obsoletePath = href + QStringLiteral(
"-obsolete.html");
500 writeSection(writer, obsoletePath, QStringLiteral(
"Obsolete members"));
506 QString href = m_gen->fullDocumentLocation(node);
507 QString objName = node->name();
516 QString typeStr = NodeText::nodeTypeDescription(node);
517 if (!typeStr.isEmpty())
518 typeStr[0] = typeStr[0].toTitleCase();
519 writer.writeStartElement(
"section");
520 writer.writeAttribute(
"ref", href);
522 writer.writeAttribute(
"title",
523 QStringLiteral(
"%1::%2 %3 Reference")
524 .arg(node
->parent()->name(), objName, typeStr));
526 writer.writeAttribute(
"title", QStringLiteral(
"%1 %2 Reference").arg(objName, typeStr));
528 addMembers(project, writer, node);
529 writer.writeEndElement();
532 case NodeType::Namespace:
533 writeSection(writer, href,
"%1 Namespace Reference"_L1.arg(objName));
542 writer.writeStartElement(
"section");
543 writer.writeAttribute(
"ref", href);
544 writer.writeAttribute(
"title", node->fullTitle());
545 if (node->nodeType() == NodeType::HeaderFile)
546 addMembers(project, writer, node);
547 writer.writeEndElement();
555 const Node *rootNode;
558 QList<Tree *> searchOrder = m_qdb->searchOrder();
561 if (!project.m_indexRoot.isEmpty())
562 rootNode = m_qdb->findPageNodeByTitle(project.m_indexRoot);
566 if (rootNode ==
nullptr)
569 project.m_files.clear();
570 project.m_keywords.clear();
573 OutputDirectory::ensure(m_outputDir,
Location());
575 QFile file(outputDir.absoluteFilePath(project.m_fileName));
576 if (!file.open(QFile::WriteOnly)) {
577 Location().error(u"Cannot open '%1' for writing"_s.arg(file.fileName()));
581 QXmlStreamWriter writer(&file);
582 writer.setAutoFormatting(
true);
583 writer.writeStartDocument();
584 writer.writeStartElement(
"QtHelpProject");
585 writer.writeAttribute(
"version",
"1.0");
588 writer.writeTextElement(
"namespace", project.m_helpNamespace);
589 writer.writeTextElement(
"virtualFolder", project.m_virtualFolder);
590 writer.writeStartElement(
"metaData");
591 writer.writeAttribute(
"name",
"version");
592 writer.writeAttribute(
"value", project.m_version);
593 writer.writeEndElement();
596 for (
auto it = project.m_customFilters.constBegin(); it != project.m_customFilters.constEnd();
598 writer.writeStartElement(
"customFilter");
599 writer.writeAttribute(
"name", it.key());
600 QStringList sortedAttributes = it.value().values();
601 sortedAttributes.sort();
602 for (
const auto &filter : std::as_const(sortedAttributes))
603 writer.writeTextElement(
"filterAttribute", filter);
604 writer.writeEndElement();
608 writer.writeStartElement(
"filterSection");
611 QStringList sortedFilterAttributes = project.m_filterAttributes.values();
612 sortedFilterAttributes.sort();
613 for (
const auto &filterName : std::as_const(sortedFilterAttributes))
614 writer.writeTextElement(
"filterAttribute", filterName);
616 writer.writeStartElement(
"toc");
617 writer.writeStartElement(
"section");
618 const Node *node = m_qdb->findPageNodeByTitle(project.m_indexTitle);
620 node = m_qdb->findNodeByNameAndType(QStringList(project.m_indexTitle), &
Node::isPageNode);
622 node = m_qdb->findNodeByNameAndType(QStringList(
"index.html"), &
Node::isPageNode);
625 indexPath = m_gen->fullDocumentLocation(node);
627 indexPath =
"index.html";
628 writer.writeAttribute(
"ref", indexPath);
629 writer.writeAttribute(
"title", project.m_indexTitle);
631 generateSections(project, writer, rootNode);
633 for (
int i = 0; i < project.m_subprojects.size(); i++) {
634 SubProject subproject = project.m_subprojects[i];
636 if (subproject.m_type == QLatin1String(
"manual")) {
638 const Node *indexPage = m_qdb->findNodeForTarget(subproject.m_indexTitle,
nullptr);
642 QStack<
int> sectionStack;
648 sectionStack.push(0);
651 if (sectionStack.pop() > 0)
652 writer.writeEndElement();
663 const Node *page = m_qdb->findNodeForTarget(atom->string(),
nullptr);
667 if (sectionStack.top() > 0)
668 writer.writeEndElement();
670 writer.writeStartElement(
"section");
671 QString indexPath = m_gen->fullDocumentLocation(page);
672 writer.writeAttribute(
"ref", indexPath);
673 writer.writeAttribute(
"title", atom->linkText());
675 sectionStack.top() += 1;
680 if (
const auto *cn = m_qdb->getCollectionNode(atom->string(), NodeType::Group)) {
681 const auto sortOrder{Generator::sortOrder(atom->strings().last())};
685 members.erase(
std::remove_if(members.begin(), members.end(),
689 if (members.isEmpty())
692 if (sortOrder == Qt::DescendingOrder)
697 for (
const auto *m : members) {
698 writer.writeStartElement(
"section");
699 writer.writeAttribute(
"ref", m_gen->fullDocumentLocation(m));
700 writer.writeAttribute(
"title", m->title());
701 writer.writeEndElement();
713 Config::instance().location().warning(
714 "Failed to find %1.indexTitle '%2'"_L1.arg(subproject.m_prefix, subproject.m_indexTitle));
718 writer.writeStartElement(
"section");
719 const Node *indexNode = m_qdb->findNodeForTarget(subproject.m_indexTitle,
nullptr);
720 QString indexPath = m_gen->fullDocumentLocation(indexNode);
721 if (indexPath.isEmpty() && !subproject.m_indexTitle.isEmpty())
722 Config::instance().location().warning(
723 "Failed to find %1.indexTitle '%2'"_L1.arg(subproject.m_prefix, subproject.m_indexTitle));
724 writer.writeAttribute(
"ref", indexPath);
725 writer.writeAttribute(
"title", subproject.m_title);
728 QStringList titles = subproject.m_nodes.keys();
730 for (
const auto &title : std::as_const(titles)) {
731 writeNode(project, writer, subproject.m_nodes[title]);
735 QSet<QString> visited;
736 bool contentsFound =
false;
737 for (
const auto *node : std::as_const(subproject.m_nodes)) {
738 QString nextTitle = node->links().value(Node::NextLink).first;
739 if (!nextTitle.isEmpty()
740 && node->links().value(Node::ContentsLink).first.isEmpty()) {
742 const Node *nextPage = m_qdb->findNodeForTarget(nextTitle,
nullptr);
745 writeNode(project, writer, node);
746 contentsFound =
true;
749 writeNode(project, writer, nextPage);
750 nextTitle = nextPage->links().value(Node::NextLink).first;
751 if (nextTitle.isEmpty() || visited.contains(nextTitle))
753 nextPage = m_qdb->findNodeForTarget(nextTitle,
nullptr);
754 visited.insert(nextTitle);
760 if (!contentsFound) {
761 QList<
const Node *> subnodes = subproject.m_nodes.values();
765 for (
const auto *node : std::as_const(subnodes))
766 writeNode(project, writer, node);
770 writer.writeEndElement();
775 m_qdb->setSearchOrder(searchOrder);
777 writer.writeEndElement();
778 writer.writeEndElement();
780 writer.writeStartElement(
"keywords");
781 std::sort(project.m_keywords.begin(), project.m_keywords.end());
782 for (
const auto &k : std::as_const(project.m_keywords)) {
783 for (
const auto &id : std::as_const(k.m_ids)) {
784 writer.writeStartElement(
"keyword");
785 writer.writeAttribute(
"name", k.m_name);
786 writer.writeAttribute(
"id", id);
787 writer.writeAttribute(
"ref", k.m_ref);
788 writer.writeEndElement();
791 writer.writeEndElement();
793 writer.writeStartElement(
"files");
797 QSet<QString> files =
798 QSet<QString>(m_gen->outputFileNames().cbegin(), m_gen->outputFileNames().cend());
799 files.unite(QSet<QString>(m_gen->exampleImageFileNames().cbegin(),
800 m_gen->exampleImageFileNames().cend()));
801 files.unite(project.m_files);
802 files.unite(project.m_extraFiles);
803 QStringList sortedFiles = files.values();
805 for (
const auto &usedFile : std::as_const(sortedFiles)) {
806 if (!usedFile.isEmpty())
807 writer.writeTextElement(
"file", usedFile);
809 writer.writeEndElement();
811 writer.writeEndElement();
812 writer.writeEndElement();
813 writer.writeEndDocument();
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 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
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)
The Location class provides a way to mark a location in a file.
Location()
Constructs an empty location.
Represents an output directory that has been verified to exist.
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.
Status
Specifies the status of the QQmlIncubator.
const Atom * firstAtom() 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 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 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, that is, the aggregate under which the node is documented.
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.
static bool nodeSortKeyOrNameLessThan(const Node *n1, const Node *n2)
Returns true if node n1 is less than node n2 when comparing the sort keys, defined with.
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.