Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
catalogentrysource.cpp
Go to the documentation of this file.
1// Copyright (C) 2026 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3
4#ifdef QDOC_TEMPLATE_GENERATOR_ENABLED
5
6#include "catalogentrysource.h"
7
8#include "collectionnode.h"
9#include "doc.h"
10#include "hrefresolver.h"
11#include "inclusionfilter.h"
12#include "node.h"
13#include "qdocdatabase.h"
14#include "text.h"
15#include "textutils.h"
16
17#include <algorithm>
18#include <optional>
19#include <variant>
20
21QT_BEGIN_NAMESPACE
22
23using namespace Qt::Literals::StringLiterals;
24
25/*!
26 \class CatalogEntrySource
27 \internal
28 \brief Driver-side extraction surface that reads \\generatelist
29 and \\annotatedlist data from QDocDatabase and returns the results
30 as QDocLib value types.
31
32 CatalogEntrySource holds the driver-layer collaborators: the QDoc
33 database, the href resolver, and the current inclusion policy.
34 Each public method corresponds to one catalog variant — the flat
35 annotated classes list, the per-module grouped examples list, the
36 compact classes list with an optional root-name filter, and the
37 group-members list. All four return CatalogEntry or
38 CatalogEntryGroup values with hrefs pre-resolved and inclusion
39 filtering applied at the boundary, ready for the QDocLib
40 list-expander pass to splice into the block tree.
41*/
42
43namespace {
44
45QString extractBrief(const Node *node)
46{
47 // Mirrors HtmlGenerator::generateAnnotatedList. Text pages prefer
48 // reconstitutedBrief() because their brief usually arrives that
49 // way from index files and there's no entity-name prefix worth
50 // stripping. Non-text pages prefer trimmedBriefText(node->name())
51 // because their authored brief typically opens with the entity's
52 // own name (e.g. "QFoo is a class that...") which the trimmer
53 // removes for cleaner display.
54 if (node->isTextPageNode()) {
55 const QString &reconstituted = node->reconstitutedBrief();
56 if (!reconstituted.isEmpty())
57 return reconstituted;
58 return node->doc().briefText().toString();
59 }
60 const Text brief = node->doc().trimmedBriefText(node->name());
61 if (!brief.isEmpty())
62 return brief.toString();
63 return node->reconstitutedBrief();
64}
65
66QString resolveEntryHref(const HrefResolver &resolver, const Node *target,
67 const Node *relative)
68{
69 // Always go through HrefResolver. It already handles Node::url()
70 // correctly: external URLs (those containing "://") pass through
71 // unchanged, while non-external urls have their baked-in
72 // ../<module>/ prefix stripped and the path recomputed from the
73 // current output layout. Short-circuiting on a non-empty url()
74 // here would re-introduce the cross-module href bug that the
75 // path-based resolver was added to fix.
76 auto result = resolver.hrefForNode(target, relative);
77 if (std::get_if<HrefSuppressReason>(&result))
78 return {};
79 return std::get<QString>(std::move(result));
80}
81
82std::optional<IR::CatalogEntry> buildEntry(const Node *node,
83 const Node *relative,
84 const HrefResolver &resolver,
85 const InclusionPolicy &policy)
86{
87 const NodeContext context = node->createContext();
88 if (!InclusionFilter::isIncluded(policy, context))
89 return std::nullopt;
90
91 const QString href = resolveEntryHref(resolver, node, relative);
92 if (href.isEmpty())
93 return std::nullopt;
94
95 IR::CatalogEntry entry;
96 entry.name = node->fullName(relative);
97 entry.href = href;
98 entry.brief = extractBrief(node);
99 entry.since = node->since();
100 entry.isDeprecated = node->isDeprecated();
101 return entry;
102}
103
104void sortEntries(QList<IR::CatalogEntry> &entries, Qt::SortOrder order)
105{
106 auto ascending = [](const IR::CatalogEntry &a, const IR::CatalogEntry &b) {
107 return QString::localeAwareCompare(a.name, b.name) < 0;
108 };
109 if (order == Qt::DescendingOrder) {
110 std::sort(entries.begin(), entries.end(),
111 [&](const IR::CatalogEntry &a, const IR::CatalogEntry &b) {
112 return ascending(b, a);
113 });
114 } else {
115 std::sort(entries.begin(), entries.end(), ascending);
116 }
117}
118
119} // namespace
120
121CatalogEntrySource::CatalogEntrySource(QDocDatabase &qdb,
122 const HrefResolver &hrefResolver,
123 InclusionPolicy policy)
124 : m_qdb(qdb),
125 m_hrefResolver(hrefResolver),
126 m_inclusionPolicy(std::move(policy))
127{
128}
129
130/*!
131 Returns the catalog entries for the C++ classes index, sorted
132 according to \a sortOrder. The \a relative node is forwarded to
133 each entry so display names can strip common prefixes.
134
135 Suppressed nodes — those denied by the inclusion policy or whose
136 href cannot be resolved — are dropped before sorting so excluded
137 entries never enter the ordered output.
138*/
139QList<IR::CatalogEntry> CatalogEntrySource::collectCppClasses(
140 const Node *relative, Qt::SortOrder sortOrder) const
141{
142 QList<IR::CatalogEntry> entries;
143 const NodeMultiMap &nmm = m_qdb.getCppClasses();
144 entries.reserve(nmm.size());
145
146 for (const Node *node : nmm.values()) {
147 auto entry = buildEntry(node, relative, m_hrefResolver,
148 m_inclusionPolicy);
149 if (entry)
150 entries.append(*entry);
151 }
152
153 sortEntries(entries, sortOrder);
154 return entries;
155}
156
157/*!
158 Returns the example pages grouped by their owning module. Groups
159 appear in alphabetical key order — \c{NodeMultiMap} is a
160 \c{QMultiMap}, so \c{uniqueKeys()} is sorted by definition — and
161 this matches \c{HtmlGenerator::generateAnnotatedLists} which
162 iterates the same multimap's unique keys for the same data.
163 Entries within each group are sorted ascending. Groups that end
164 up empty after inclusion filtering are dropped.
165*/
166QList<IR::CatalogEntryGroup> CatalogEntrySource::collectExamplesGrouped(
167 const Node *relative) const
168{
169 QList<IR::CatalogEntryGroup> groups;
170 const NodeMultiMap &nmm = m_qdb.getExamples();
171 const QList<QString> keys = nmm.uniqueKeys();
172
173 for (const QString &key : keys) {
174 IR::CatalogEntryGroup group;
175 group.label = key;
176 group.anchorId = TextUtils::asAsciiPrintable(key);
177
178 for (const Node *node : nmm.values(key)) {
179 auto entry = buildEntry(node, relative, m_hrefResolver,
180 m_inclusionPolicy);
181 if (entry)
182 group.entries.append(*entry);
183 }
184 sortEntries(group.entries, Qt::AscendingOrder);
185
186 if (!group.entries.isEmpty())
187 groups.append(std::move(group));
188 }
189
190 return groups;
191}
192
193/*!
194 Returns the entries for the compact classes list, optionally
195 filtered by \a rootName. An empty rootName returns every class;
196 a non-empty rootName keeps only classes whose unqualified name
197 begins with the filter, matched case-insensitively to follow the
198 legacy \c{generateCompactList} behavior of bucketing on the last
199 \c{::} segment of each class name.
200
201 Entries are always sorted ascending. The placeholder's sort
202 directive is intentionally ignored here: the legacy compact-list
203 pipeline alphabetizes into 37 paragraph buckets, so descending
204 order has never been a meaningful option for this variant.
205*/
206QList<IR::CatalogEntry> CatalogEntrySource::collectCompactClasses(
207 const Node *relative, const QString &rootName) const
208{
209 QList<IR::CatalogEntry> entries;
210 const NodeMultiMap &nmm = m_qdb.getCppClasses();
211 entries.reserve(nmm.size());
212
213 for (const Node *node : nmm.values()) {
214 if (!rootName.isEmpty()
215 && !node->name().startsWith(rootName, Qt::CaseInsensitive)) {
216 continue;
217 }
218 auto entry = buildEntry(node, relative, m_hrefResolver,
219 m_inclusionPolicy);
220 if (entry)
221 entries.append(*entry);
222 }
223
224 sortEntries(entries, Qt::AscendingOrder);
225 return entries;
226}
227
228/*!
229 Returns the members of the named group, resolved through
230 QDocDatabase::getCollectionNode with NodeType::Group. An unknown
231 or empty group yields an empty list, leaving the caller to warn
232 once rather than raising mid-enumeration. Entries are sorted
233 according to \a sortOrder.
234*/
235QList<IR::CatalogEntry> CatalogEntrySource::collectGroupMembers(
236 const Node *relative, const QString &groupName,
237 Qt::SortOrder sortOrder) const
238{
239 QList<IR::CatalogEntry> entries;
240 if (groupName.isEmpty())
241 return entries;
242
243 // TODO: const_cast — mergeCollections mutates a logical read.
244 // Same shape as the TODO at nodeextractor.cpp's cn->members()
245 // iteration; both fall out of an eager merge pass before
246 // generation begins.
247 auto *cn = const_cast<CollectionNode *>(
248 m_qdb.getCollectionNode(groupName, NodeType::Group));
249 if (!cn)
250 return entries;
251
252 // Without this, cross-module group members don't appear; the
253 // legacy generators do the same merge before reading.
254 m_qdb.mergeCollections(cn);
255 Q_ASSERT(cn->isMerged());
256
257 for (const Node *node : cn->members()) {
258 auto entry = buildEntry(node, relative, m_hrefResolver,
259 m_inclusionPolicy);
260 if (entry)
261 entries.append(*entry);
262 }
263
264 sortEntries(entries, sortOrder);
265 return entries;
266}
267
268QT_END_NAMESPACE
269
270#endif // QDOC_TEMPLATE_GENERATOR_ENABLED