24#include <QtCore/qhash.h>
28using namespace Qt::StringLiterals;
32 reset(defaultFileName, g);
40
41
42
43
48 Config &config = Config::instance();
49 m_outputDir = config.getOutputDir();
51 const QStringList names{config.get(
CONFIG_QHP + Config::dot +
"projects").asStringList()};
53 for (
const auto &projectName : names) {
55 project.m_name = projectName;
57 QString prefix =
CONFIG_QHP + Config::dot + projectName + Config::dot;
58 project.m_helpNamespace = config.get(prefix +
"namespace").asString();
59 project.m_virtualFolder = config.get(prefix +
"virtualFolder").asString();
61 project.m_fileName = config.get(prefix +
"file").asString();
62 if (project.m_fileName.isEmpty())
63 project.m_fileName = defaultFileName;
64 project.m_extraFiles = config.get(prefix +
"extraFiles").asStringSet();
65 project.m_extraFiles += config.get(
CONFIG_QHP + Config::dot +
"extraFiles").asStringSet();
66 project.m_indexTitle = config.get(prefix +
"indexTitle").asString();
67 project.m_indexRoot = config.get(prefix +
"indexRoot").asString();
68 project.m_filterAttributes = config.get(prefix +
"filterAttributes").asStringSet();
69 project.m_includeIndexNodes = config.get(prefix +
"includeIndexNodes").asBool();
70 const QSet<QString> customFilterNames = config.subVars(prefix +
"customFilters");
71 for (
const auto &filterName : customFilterNames) {
72 QString name{config.get(prefix +
"customFilters" + Config::dot + filterName
73 + Config::dot +
"name").asString()};
74 project.m_customFilters[name] =
75 config.get(prefix +
"customFilters" + Config::dot + filterName
76 + Config::dot +
"filterAttributes").asStringSet();
79 const auto excludedPrefixes = config.get(prefix +
"excluded").asStringSet();
80 for (
auto name : excludedPrefixes)
81 project.m_excluded.insert(name.replace(QLatin1Char(
'\\'), QLatin1Char(
'/')));
83 const auto subprojectPrefixes{config.get(prefix +
"subprojects").asStringList()};
84 for (
const auto &name : subprojectPrefixes) {
85 SubProject subproject;
86 QString subprefix = prefix +
"subprojects" + Config::dot + name + Config::dot;
87 subproject.m_title = config.get(subprefix +
"title").asString();
88 if (subproject.m_title.isEmpty())
90 subproject.m_indexTitle = config.get(subprefix +
"indexTitle").asString();
91 subproject.m_sortPages = config.get(subprefix +
"sortPages").asBool();
92 subproject.m_type = config.get(subprefix +
"type").asString();
93 readSelectors(subproject, config.get(subprefix +
"selectors").asStringList());
95 subproject.m_prefix = std::move(subprefix);
96 project.m_subprojects.append(subproject);
99 if (project.m_subprojects.isEmpty()) {
100 SubProject subproject;
101 readSelectors(subproject, config.get(prefix +
"selectors").asStringList());
102 project.m_subprojects.insert(0, subproject);
105 m_projects.append(project);
111 QHash<QString, NodeType> typeHash;
139 for (
const QString &selector : selectors) {
140 QStringList pieces = selector.split(QLatin1Char(
':'));
142 if (pieces.size() > 1 && typeHash.value(pieces[0].toLower()) == NodeType::Page)
145 QString typeName = pieces.takeFirst().toLower();
146 if (!typeHash.contains(typeName))
149 subproject.m_selectors <<
static_cast<
unsigned char>(typeHash.value(typeName));
150 if (!pieces.isEmpty()) {
151 pieces = pieces[0].split(QLatin1Char(
','));
152 for (
const auto &piece : std::as_const(pieces)) {
153 if (typeHash[typeName] == NodeType::Group
154 || typeHash[typeName] == NodeType::Module
155 || typeHash[typeName] == NodeType::QmlModule) {
156 subproject.m_groups << piece.toLower();
165 for (HelpProject &project : m_projects)
166 project.m_extraFiles.insert(file);
171 QString ref = m_gen->fullDocumentLocation(node);
180 return Keyword(name, id, ref);
182 const QString &name = node->name();
183 QString moduleName = node->logicalModuleName();
184 QStringList ids(
"QML." + name);
185 if (!moduleName.isEmpty()) {
189 ids <<
"QML." + moduleName + majorVersion +
"." + name;
191 return Keyword(name, ids, ref);
193 const QLatin1Char delim(
'.');
194 QStringList parts = node->logicalModuleName().split(delim) <<
"QML";
195 std::reverse(parts.begin(), parts.end());
196 return Keyword(node->logicalModuleName(), parts.join(delim), ref);
198 const auto *pageNode =
static_cast<
const PageNode *>(node);
199 return Keyword(pageNode->fullTitle(), pageNode->fullTitle(), ref);
201 return Keyword(node->name(), node->name(), ref);
208 if (!node->url().isEmpty() && !(project
.m_includeIndexNodes && !node->url().startsWith(
"http")))
215 const InclusionPolicy policy = Config::instance().createInclusionPolicy();
220 if (node->name().isEmpty())
224 if (!docPath.isEmpty() && project.m_excluded.contains(docPath))
227 QString objName = node
->isTextPageNode() ? node->fullTitle() : node->fullDocumentName();
231 for (
auto &subproject : project.m_subprojects) {
233 if (subproject.m_selectors.isEmpty()) {
234 subproject.m_nodes.insert(objName, node);
235 }
else if (subproject.m_selectors.contains(
static_cast<
unsigned char>(node->nodeType()))) {
237 if (node->isCollectionNode()) {
238 if (subproject.m_groups.contains(node->name().toLower())) {
239 const auto *cn =
static_cast<
const CollectionNode *>(node);
240 const auto members = cn->members();
241 for (
const Node *m : members) {
245 m->isTextPageNode() ? m->fullTitle() : m->fullDocumentName();
246 subproject.m_nodes.insert(memberName, m);
249 }
else if (!subproject.m_groups.isEmpty()) {
252 }
else if (node->isTextPageNode()) {
253 if (node->isExternalPage() || node->fullTitle().isEmpty())
256 subproject.m_nodes.insert(objName, node);
260 auto appendDocKeywords = [&](
const Node *n) {
263 for (
const auto *kw : n->doc().keywords()) {
264 if (!kw->string().isEmpty()) {
265 QStringList ref_parts = m_gen->fullDocumentLocation(n).split(
'#'_L1);
267 if (kw->count() > 1) {
268 if (ref_parts.count() > 1)
269 ref_parts.pop_back();
270 ref_parts << kw->string(1);
272 project.m_keywords.append(Keyword(kw->string(), kw->string(),
273 ref_parts.join(
'#'_L1)));
286 project.m_keywords.append(keywordDetails(node));
290 appendDocKeywords(node);
291 project.m_keywords.append(keywordDetails(node));
295 project.m_keywords.append(keywordDetails(node));
300 project.m_keywords.append(keywordDetails(node));
302 const auto *enumNode =
static_cast<
const EnumNode *>(node);
303 const auto items = enumNode->items();
304 for (
const auto &item : items) {
305 if (enumNode->itemAccess(item.name()) == Access::Private)
310 if (!node->parent()->name().isEmpty()) {
311 name = id = node->parent()->name() +
"::" + item.name();
313 name = id = item.name();
315 QString ref = m_gen->fullDocumentLocation(node);
316 project.m_keywords.append(Keyword(std::move(name), std::move(id), std::move(ref)));
324 if (!node->fullTitle().isEmpty()) {
325 appendDocKeywords(node);
326 project.m_keywords.append(keywordDetails(node));
332 project.m_keywords.append(keywordDetails(node));
336 const auto *funcNode =
static_cast<
const FunctionNode *>(node);
339
340
341
342
343 if (funcNode->isQmlNode()) {
344 project.m_keywords.append(keywordDetails(node));
350 if (!funcNode->isSomeCtor())
351 project.m_keywords.append(keywordDetails(node));
364 const auto *typedefNode =
static_cast<
const TypedefNode *>(node);
365 Keyword typedefDetails = keywordDetails(node);
366 const EnumNode *enumNode = typedefNode->associatedEnum();
370 typedefDetails.m_ref = m_gen->fullDocumentLocation(enumNode);
372 project.m_keywords.append(typedefDetails);
376 project.m_keywords.append(keywordDetails(node));
382 if (!node->fullTitle().isEmpty()) {
383 appendDocKeywords(node);
384 project.m_keywords.append(keywordDetails(node));
395 QStringList pieces = atom->string().split(
'/'_L1);
396 project.m_files.insert(
"%1/%2"_L1.arg(m_gen->imagesOutputDir(), pieces.last()));
408
409
412 if (!generateSection(project, writer, node))
416 const auto *aggregate =
static_cast<
const Aggregate *>(node);
420 NodeList children = aggregate->childNodes();
422 for (
auto *child : children) {
424 if (child->parent() != aggregate)
427 if (child->isGroup() && !child->wasSeen()) {
431 if (child->isIndexNode())
434 const InclusionPolicy policy = Config::instance().createInclusionPolicy();
435 const NodeContext context = child->createContext();
436 if (!InclusionFilter::isIncluded(policy, context))
439 if (child->isTextPageNode()) {
440 if (!childSet.contains(child))
444 project.m_memberStatus[node].insert(child->status());
445 if (child->isFunction() &&
static_cast<
const FunctionNode *>(child)->isOverload())
447 if (!childSet.contains(child))
451 for (
const auto *child : std::as_const(childSet))
452 generateSections(project, writer, child);
459 if (
auto &config = Config::instance(); m_projects.isEmpty() && config.get(
CONFIG_QHP).asBool()) {
460 config.location().warning(u"Documentation configuration for '%1' doesn't define a help project (qhp)"_s
463 for (HelpProject &project : m_projects)
464 generateProject(project);
467void HelpProjectWriter::writeSection(QXmlStreamWriter &writer,
const QString &path,
468 const QString &value)
470 writer.writeStartElement(QStringLiteral(
"section"));
471 writer.writeAttribute(QStringLiteral(
"ref"), path);
472 writer.writeAttribute(QStringLiteral(
"title"), value);
473 writer.writeEndElement();
477
478
481 QString href = m_gen->fullDocumentLocation(node);
482 href = href.left(href.size() - 5);
486 bool derivedClass =
false;
487 if (node->isClassNode())
488 derivedClass = !(
static_cast<
const ClassNode *>(node)->baseClasses().isEmpty());
493 && (derivedClass || node
->isQmlType() || !project.m_memberStatus[node].isEmpty())) {
494 QString membersPath = href + QStringLiteral(
"-members.html");
495 writeSection(writer, membersPath, QStringLiteral(
"List of all members"));
498 QString obsoletePath = href + QStringLiteral(
"-obsolete.html");
499 writeSection(writer, obsoletePath, QStringLiteral(
"Obsolete members"));
505 QString href = m_gen->fullDocumentLocation(node);
506 QString objName = node->name();
515 QString typeStr = m_gen->typeString(node);
516 if (!typeStr.isEmpty())
517 typeStr[0] = typeStr[0].toTitleCase();
518 writer.writeStartElement(
"section");
519 writer.writeAttribute(
"ref", href);
521 writer.writeAttribute(
"title",
522 QStringLiteral(
"%1::%2 %3 Reference")
523 .arg(node
->parent()->name(), objName, typeStr));
525 writer.writeAttribute(
"title", QStringLiteral(
"%1 %2 Reference").arg(objName, typeStr));
527 addMembers(project, writer, node);
528 writer.writeEndElement();
531 case NodeType::Namespace:
532 writeSection(writer, href,
"%1 Namespace Reference"_L1.arg(objName));
541 writer.writeStartElement(
"section");
542 writer.writeAttribute(
"ref", href);
543 writer.writeAttribute(
"title", node->fullTitle());
544 if (node->nodeType() == NodeType::HeaderFile)
545 addMembers(project, writer, node);
546 writer.writeEndElement();
554 const Node *rootNode;
557 QList<Tree *> searchOrder = m_qdb->searchOrder();
560 if (!project.m_indexRoot.isEmpty())
561 rootNode = m_qdb->findPageNodeByTitle(project.m_indexRoot);
565 if (rootNode ==
nullptr)
568 project.m_files.clear();
569 project.m_keywords.clear();
572 OutputDirectory::ensure(m_outputDir,
Location());
574 QFile file(outputDir.absoluteFilePath(project.m_fileName));
575 if (!file.open(QFile::WriteOnly)) {
576 Location().error(u"Cannot open '%1' for writing"_s.arg(file.fileName()));
580 QXmlStreamWriter writer(&file);
581 writer.setAutoFormatting(
true);
582 writer.writeStartDocument();
583 writer.writeStartElement(
"QtHelpProject");
584 writer.writeAttribute(
"version",
"1.0");
587 writer.writeTextElement(
"namespace", project.m_helpNamespace);
588 writer.writeTextElement(
"virtualFolder", project.m_virtualFolder);
589 writer.writeStartElement(
"metaData");
590 writer.writeAttribute(
"name",
"version");
591 writer.writeAttribute(
"value", project.m_version);
592 writer.writeEndElement();
595 for (
auto it = project.m_customFilters.constBegin(); it != project.m_customFilters.constEnd();
597 writer.writeStartElement(
"customFilter");
598 writer.writeAttribute(
"name", it.key());
599 QStringList sortedAttributes = it.value().values();
600 sortedAttributes.sort();
601 for (
const auto &filter : std::as_const(sortedAttributes))
602 writer.writeTextElement(
"filterAttribute", filter);
603 writer.writeEndElement();
607 writer.writeStartElement(
"filterSection");
610 QStringList sortedFilterAttributes = project.m_filterAttributes.values();
611 sortedFilterAttributes.sort();
612 for (
const auto &filterName : std::as_const(sortedFilterAttributes))
613 writer.writeTextElement(
"filterAttribute", filterName);
615 writer.writeStartElement(
"toc");
616 writer.writeStartElement(
"section");
617 const Node *node = m_qdb->findPageNodeByTitle(project.m_indexTitle);
619 node = m_qdb->findNodeByNameAndType(QStringList(project.m_indexTitle), &
Node::isPageNode);
621 node = m_qdb->findNodeByNameAndType(QStringList(
"index.html"), &
Node::isPageNode);
624 indexPath = m_gen->fullDocumentLocation(node);
626 indexPath =
"index.html";
627 writer.writeAttribute(
"ref", indexPath);
628 writer.writeAttribute(
"title", project.m_indexTitle);
630 generateSections(project, writer, rootNode);
632 for (
int i = 0; i < project.m_subprojects.size(); i++) {
633 SubProject subproject = project.m_subprojects[i];
635 if (subproject.m_type == QLatin1String(
"manual")) {
637 const Node *indexPage = m_qdb->findNodeForTarget(subproject.m_indexTitle,
nullptr);
641 QStack<
int> sectionStack;
647 sectionStack.push(0);
650 if (sectionStack.pop() > 0)
651 writer.writeEndElement();
662 const Node *page = m_qdb->findNodeForTarget(atom->string(),
nullptr);
666 if (sectionStack.top() > 0)
667 writer.writeEndElement();
669 writer.writeStartElement(
"section");
670 QString indexPath = m_gen->fullDocumentLocation(page);
671 writer.writeAttribute(
"ref", indexPath);
672 writer.writeAttribute(
"title", atom->linkText());
674 sectionStack.top() += 1;
679 if (
const auto *cn = m_qdb->getCollectionNode(atom->string(), NodeType::Group)) {
680 const auto sortOrder{Generator::sortOrder(atom->strings().last())};
684 members.erase(
std::remove_if(members.begin(), members.end(),
688 if (members.isEmpty())
691 if (sortOrder == Qt::DescendingOrder)
696 for (
const auto *m : members) {
697 writer.writeStartElement(
"section");
698 writer.writeAttribute(
"ref", m_gen->fullDocumentLocation(m));
699 writer.writeAttribute(
"title", m->title());
700 writer.writeEndElement();
712 Config::instance().location().warning(
713 "Failed to find %1.indexTitle '%2'"_L1.arg(subproject.m_prefix, subproject.m_indexTitle));
717 writer.writeStartElement(
"section");
718 const Node *indexNode = m_qdb->findNodeForTarget(subproject.m_indexTitle,
nullptr);
719 QString indexPath = m_gen->fullDocumentLocation(indexNode);
720 if (indexPath.isEmpty() && !subproject.m_indexTitle.isEmpty())
721 Config::instance().location().warning(
722 "Failed to find %1.indexTitle '%2'"_L1.arg(subproject.m_prefix, subproject.m_indexTitle));
723 writer.writeAttribute(
"ref", indexPath);
724 writer.writeAttribute(
"title", subproject.m_title);
727 QStringList titles = subproject.m_nodes.keys();
729 for (
const auto &title : std::as_const(titles)) {
730 writeNode(project, writer, subproject.m_nodes[title]);
734 QSet<QString> visited;
735 bool contentsFound =
false;
736 for (
const auto *node : std::as_const(subproject.m_nodes)) {
737 QString nextTitle = node->links().value(Node::NextLink).first;
738 if (!nextTitle.isEmpty()
739 && node->links().value(Node::ContentsLink).first.isEmpty()) {
741 const Node *nextPage = m_qdb->findNodeForTarget(nextTitle,
nullptr);
744 writeNode(project, writer, node);
745 contentsFound =
true;
748 writeNode(project, writer, nextPage);
749 nextTitle = nextPage->links().value(Node::NextLink).first;
750 if (nextTitle.isEmpty() || visited.contains(nextTitle))
752 nextPage = m_qdb->findNodeForTarget(nextTitle,
nullptr);
753 visited.insert(nextTitle);
759 if (!contentsFound) {
760 QList<
const Node *> subnodes = subproject.m_nodes.values();
764 for (
const auto *node : std::as_const(subnodes))
765 writeNode(project, writer, node);
769 writer.writeEndElement();
774 m_qdb->setSearchOrder(searchOrder);
776 writer.writeEndElement();
777 writer.writeEndElement();
779 writer.writeStartElement(
"keywords");
780 std::sort(project.m_keywords.begin(), project.m_keywords.end());
781 for (
const auto &k : std::as_const(project.m_keywords)) {
782 for (
const auto &id : std::as_const(k.m_ids)) {
783 writer.writeStartElement(
"keyword");
784 writer.writeAttribute(
"name", k.m_name);
785 writer.writeAttribute(
"id", id);
786 writer.writeAttribute(
"ref", k.m_ref);
787 writer.writeEndElement();
790 writer.writeEndElement();
792 writer.writeStartElement(
"files");
796 QSet<QString> files =
797 QSet<QString>(m_gen->outputFileNames().cbegin(), m_gen->outputFileNames().cend());
798 files.unite(project.m_files);
799 files.unite(project.m_extraFiles);
800 QStringList sortedFiles = files.values();
802 for (
const auto &usedFile : std::as_const(sortedFiles)) {
803 if (!usedFile.isEmpty())
804 writer.writeTextElement(
"file", usedFile);
806 writer.writeEndElement();
808 writer.writeEndElement();
809 writer.writeEndElement();
810 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.
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.