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
builder.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#include "builder.h"
5
6#include "pagemetadata.h"
7
8#include <utility>
9
10QT_BEGIN_NAMESPACE
11
12using namespace Qt::Literals;
13
14namespace IR {
15
17 const QList<ContentBlock> &blocks,
18 QList<NavigationInfo::TocEntry> &out)
19{
20 for (const auto &block : blocks) {
21 if (block.type == BlockType::SectionHeading) {
22 if (!block.attributes.contains("sectionRef"_L1))
23 continue;
24 const QString anchorId = block.attributes.value("sectionRef"_L1).toString();
25 if (anchorId.isEmpty())
26 continue;
27 const int level = block.attributes.value("level"_L1).toInt(3);
28 out.append({block.plainText(), anchorId, level});
29 } else if (block.type == BlockType::Section) {
30 gatherBodyTocEntries(block.children, out);
31 }
32 }
33}
34
35static InlineContent makeTextInline(const QString &text)
36{
39 ic.text = text;
40 return ic;
41}
42
43/*!
44 \internal
45 Synthesizes an unresolved link inline pointing at \a topicName.
46 Used when a topic page is referenced from Builder-generated prose
47 (such as the thread-safety admonition's pointer at the
48 \c reentrant or \c thread-safe topic page) before that page has
49 been resolved. The resulting link carries
50 \c LinkOrigin::Synthesized so link resolution can attribute any
51 broken-link warning to the page that triggered the synthesis,
52 not to a non-existent author source line.
53*/
54static InlineContent makeTopicLink(const QString &topicName)
55{
58 ic.href = topicName;
60 ic.children.append(makeTextInline(topicName));
61 return ic;
62}
63
64/*!
65 \internal
66 Synthesizes a pre-resolved link inline pointing at \a href with
67 \a name as its display text. Used for Builder-generated prose
68 that already knows the target URL (such as exception-list items
69 in the thread-safety admonition). The resulting link carries
70 \c LinkOrigin::Synthesized for parity with \c makeTopicLink even
71 though the link is already in \c LinkState::Resolved and bypasses
72 the broken-link path.
73*/
74static InlineContent makeResolvedLink(const QString &name, const QString &href)
75{
78 ic.href = href;
80 ic.children.append(makeTextInline(name));
81 return ic;
82}
83
85 QList<InlineContent> &inlines,
86 const QString &prefix,
87 const QString &topicName,
88 const QList<CppReferenceData::ThreadSafetyExceptionEntry> &exceptions)
89{
90 if (exceptions.isEmpty())
91 return;
92 inlines.append(makeTextInline(prefix));
93 inlines.append(makeTopicLink(topicName));
94 inlines.append(makeTextInline(": "_L1));
95 for (qsizetype i = 0; i < exceptions.size(); ++i) {
96 if (i > 0)
97 inlines.append(makeTextInline(", "_L1));
98 const auto &exc = exceptions[i];
99 if (!exc.href.isEmpty())
100 inlines.append(makeResolvedLink(exc.name, exc.href));
101 else
102 inlines.append(makeTextInline(exc.name));
103 }
104 inlines.append(makeTextInline("."_L1));
105}
106
109 const QString &typeWord)
110{
111 QList<InlineContent> inlines;
112 const bool hasExceptions = !ts.reentrantExceptions.isEmpty()
113 || ts.threadSafeExceptions.isEmpty() == false
114 || !ts.nonReentrantExceptions.isEmpty();
115
116 if (ts.level == "non-reentrant"_L1) {
117 inlines.append(makeTextInline("This "_L1 + typeWord + " is not "_L1));
118 inlines.append(makeTopicLink("reentrant"_L1));
119 inlines.append(makeTextInline("."_L1));
120 appendExceptionList(inlines, " These functions are "_L1, "reentrant"_L1,
121 ts.reentrantExceptions);
122 } else if (ts.level == "reentrant"_L1) {
123 inlines.append(makeTextInline("All functions in this "_L1 + typeWord + " are "_L1));
124 inlines.append(makeTopicLink("reentrant"_L1));
125 const bool hasThreadSafe = !ts.threadSafeExceptions.isEmpty();
126 if (hasExceptions && !hasThreadSafe)
127 inlines.append(makeTextInline(" with the following exceptions:"_L1));
128 else
129 inlines.append(makeTextInline("."_L1));
130 appendExceptionList(inlines, " These functions are not "_L1, "reentrant"_L1,
131 ts.nonReentrantExceptions);
132 appendExceptionList(inlines, " These functions are also "_L1, "thread-safe"_L1,
133 ts.threadSafeExceptions);
134 } else if (ts.level == "thread-safe"_L1) {
135 inlines.append(makeTextInline("All functions in this "_L1 + typeWord + " are "_L1));
136 inlines.append(makeTopicLink("thread-safe"_L1));
137 if (hasExceptions)
138 inlines.append(makeTextInline(" with the following exceptions:"_L1));
139 else
140 inlines.append(makeTextInline("."_L1));
141 appendExceptionList(inlines, " These functions are only "_L1, "reentrant"_L1,
142 ts.reentrantExceptions);
143 appendExceptionList(inlines, " These functions are not "_L1, "reentrant"_L1,
144 ts.nonReentrantExceptions);
145 }
146
147 ContentBlock block;
149 block.inlineContent = std::move(inlines);
150 return {block};
151}
152
153/*!
154 \class IR::Builder
155 \internal
156 \brief Assembles IR Documents from pre-extracted metadata.
157
158 Builder consumes PageMetadata, a value-type struct populated by the
159 driver-side extraction layer (NodeExtractor). It copies pre-extracted
160 fields into an IR::Document without touching Node subclass headers,
161 Atom chains, or the documentation database.
162
163 This separation means Builder has no dependencies on the legacy Node
164 layer and is eligible for QDocLib migration. Generators receive
165 pre-built IR and focus purely on formatting output.
166
167 \section1 Content Pipeline
168
169 Content arrives pre-built as a list of ContentBlock values in
170 PageMetadata::body. ContentBuilder (called at extraction time)
171 handles the atom-to-block transformation, including brief
172 exclusion. Format-conditional atoms are skipped unconditionally
173 since the IR is format-agnostic. Builder's role is assembly, not
174 transformation.
175
176 \section1 Flat Text Fallback
177
178 Builder computes a flat text representation from the structured body
179 for \c{content.text}. This is transitional — templates will consume
180 \c{content.blocks} directly once content rendering is in place.
181
182 \sa IR::Document, IR::PageMetadata, TemplateGenerator
183*/
184
185
186/*!
187 \internal
188 Assemble an IR Document from pre-extracted PageMetadata.
189
190 Classification, identity, and content fields are moved from
191 \a pm. The body (a list of ContentBlock values built by
192 ContentBuilder at extraction time) is transferred as-is. A flat
193 text fallback is computed until templates consume the structured
194 body directly.
195*/
197{
198 Document ir;
199
200 ir.nodeType = pm.nodeType;
201 ir.genus = pm.genus;
202 ir.status = pm.status;
203 ir.access = pm.access;
204
205 ir.title = std::move(pm.title);
206 ir.fullTitle = std::move(pm.fullTitle);
207 ir.url = std::move(pm.url);
208 ir.since = std::move(pm.since);
209 ir.deprecatedSince = std::move(pm.deprecatedSince);
210 ir.brief = std::move(pm.brief);
211
212 ir.body = std::move(pm.body);
213 ir.summarySections = std::move(pm.summarySections);
214 ir.detailSections = std::move(pm.detailSections);
215
216 if (pm.qmlTypeData) {
217 const auto &src = *pm.qmlTypeData;
218 QmlTypeInfo info;
219 info.importStatement = src.importStatement;
220 info.isSingleton = src.isSingleton;
221 info.isValueType = src.isValueType;
222
223 if (src.inherits) {
224 info.inherits = QmlTypeInfo::InheritsInfo{
225 src.inherits->name, src.inherits->href, src.inherits->moduleName
226 };
227 }
228
229 for (const auto &entry : src.inheritedBy)
230 info.inheritedBy.append({entry.name, entry.href});
231
232 if (src.nativeType)
233 info.nativeType = QmlTypeInfo::NativeTypeInfo{src.nativeType->name, src.nativeType->href};
234
235 ir.qmlTypeInfo = std::move(info);
236 }
237
238 if (pm.collectionData) {
239 const auto &src = *pm.collectionData;
240 CollectionInfo info;
241 info.logicalModuleName = src.logicalModuleName;
242 info.logicalModuleVersion = src.logicalModuleVersion;
243 info.qtVariable = src.qtVariable;
244 info.cmakePackage = src.cmakePackage;
245 info.cmakeComponent = src.cmakeComponent;
246 info.cmakeTargetItem = src.cmakeTargetItem;
247 info.state = src.state;
248
249 info.isModule = src.isModule;
250 info.isQmlModule = src.isQmlModule;
251 info.isGroup = src.isGroup;
252 info.noAutoList = src.noAutoList;
253
254 for (const auto &entry : src.namespaces)
255 info.namespaces.append({entry.name, entry.href, entry.brief});
256 for (const auto &entry : src.classes)
257 info.classes.append({entry.name, entry.href, entry.brief});
258 for (const auto &entry : src.members)
259 info.members.append({entry.name, entry.href, entry.brief});
260
261 ir.collectionInfo = std::move(info);
262 }
263
264 if (pm.cppReferenceData) {
265 const auto &src = *pm.cppReferenceData;
266 CppReferenceInfo info;
267
268 info.headerInclude = src.headerInclude;
269 info.cmakeFindPackage = src.cmakeFindPackage;
270 info.cmakeTargetLinkLibraries = src.cmakeTargetLinkLibraries;
271 info.qmakeVariable = src.qmakeVariable;
272 info.statusText = src.statusText;
273 info.statusCssClass = src.statusCssClass;
274
275 if (src.qmlNativeType)
276 info.qmlNativeType = CppReferenceInfo::QmlNativeTypeLink{
277 src.qmlNativeType->name, src.qmlNativeType->href};
278
279 for (const auto &bc : src.baseClasses)
280 info.baseClasses.append({bc.name, bc.href, bc.access});
281 for (const auto &dc : src.derivedClasses)
282 info.derivedClasses.append({dc.name, dc.href});
283 info.suppressInheritance = src.suppressInheritance;
284
285 info.templateDeclSpans = src.templateDeclSpans;
286
287 info.isInnerClass = src.isInnerClass;
288 info.isNamespace = src.isNamespace;
289 info.isHeader = src.isHeader;
290
291 info.isPartialNamespace = src.isPartialNamespace;
292 info.fullNamespaceHref = src.fullNamespaceHref;
293 info.fullNamespaceModuleName = src.fullNamespaceModuleName;
294
295 info.typeWord = src.typeWord;
296 info.ancestorNames = src.ancestorNames;
297
298 info.selfComparisonCategory = src.selfComparisonCategory;
299 for (const auto &ce : src.comparisonEntries)
300 info.comparisonEntries.append({ce.category, ce.comparableTypes, ce.description});
301
302 if (src.threadSafety) {
304 ts.level = src.threadSafety->level;
305 for (const auto &e : src.threadSafety->reentrantExceptions)
306 ts.reentrantExceptions.append({e.name, e.href});
307 for (const auto &e : src.threadSafety->threadSafeExceptions)
308 ts.threadSafeExceptions.append({e.name, e.href});
309 for (const auto &e : src.threadSafety->nonReentrantExceptions)
310 ts.nonReentrantExceptions.append({e.name, e.href});
311 info.threadSafety = std::move(ts);
312 info.threadSafetyAdmonition = buildThreadSafetyAdmonition(
313 *src.threadSafety, src.typeWord);
314 }
315
316 for (const auto &g : src.groups)
317 info.groups.append({g.name, g.href});
318
319 info.hasObsoleteMembers = src.hasObsoleteMembers;
320 // TODO: obsoleteMembersUrl is currently set by the generator
321 // after assembly because it depends on the file extension.
322 // This post-build mutation violates frozen-IR. The Builder
323 // should receive the file extension and compute it here.
324
325 ir.cppReferenceInfo = std::move(info);
326 }
327
328 {
329 const auto &src = pm.navigationData;
330 NavigationInfo info;
331 for (const auto &bc : src.breadcrumbs) {
332 NavigationInfo::BreadcrumbEntry entry;
333 entry.title = bc.title;
334 entry.href = bc.href;
335 switch (bc.state) {
336 case NavigationData::CrumbState::Link:
337 entry.state = NavigationInfo::CrumbState::Link;
338 break;
339 case NavigationData::CrumbState::Current:
340 entry.state = NavigationInfo::CrumbState::Current;
341 break;
342 case NavigationData::CrumbState::Unresolved:
343 entry.state = NavigationInfo::CrumbState::Unresolved;
344 break;
345 }
346 info.breadcrumbs.append(std::move(entry));
347 }
348 if (src.previousLink)
349 info.previousLink = NavigationInfo::LinkEntry{
350 src.previousLink->title, src.previousLink->href};
351 if (src.nextLink)
352 info.nextLink = NavigationInfo::LinkEntry{
353 src.nextLink->title, src.nextLink->href};
354 if (src.startLink)
355 info.startLink = NavigationInfo::LinkEntry{
356 src.startLink->title, src.startLink->href};
357 info.tocDepth = src.tocDepth;
358
359 // Assemble TOC entries from page structure (summary sections +
360 // "Detailed Description" divider + body section-headings +
361 // detail sections). Order reflects the rendered page, so templates
362 // can iterate for a top-to-bottom reading of available anchors.
363 for (const auto &s : ir.summarySections) {
364 if (!s.title.isEmpty() && !s.id.isEmpty())
365 info.tocEntries.append({s.title, s.id, 2});
366 }
367 if ((ir.cppReferenceInfo || ir.qmlTypeInfo) && !ir.body.isEmpty())
368 info.tocEntries.append(
369 {u"Detailed Description"_s, u"details"_s, 2});
370 gatherBodyTocEntries(ir.body, info.tocEntries);
371 for (const auto &s : ir.detailSections) {
372 if (!s.title.isEmpty() && !s.id.isEmpty())
373 info.tocEntries.append({s.title, s.id, 2});
374 }
375
376 const bool hasAnyNavigation = !info.breadcrumbs.isEmpty()
377 || info.previousLink || info.nextLink || info.startLink
378 || !info.tocEntries.isEmpty() || info.tocDepth != -1;
379 if (hasAnyNavigation)
380 ir.navigationInfo = std::move(info);
381 }
382
383 // Plain-text fallback for templates that render unstructured
384 // content. Structured rendering through content.blocks is the
385 // primary path; this key is only populated when there is text
386 // worth showing, so an empty body never produces a hollow
387 // section wrapper in the rendered output.
388 QStringList paragraphs;
389 for (const auto &block : ir.body) {
390 const QString text = block.plainText();
391 if (!text.isEmpty())
392 paragraphs.append(text);
393 }
394 const QString joined = paragraphs.join("\n\n"_L1);
395 if (!joined.isEmpty())
396 ir.contentJson["text"_L1] = joined;
397
398 return ir;
399}
400
401} // namespace IR
402
403QT_END_NAMESPACE
Assembles IR Documents from pre-extracted metadata.
Definition builder.h:15
Document buildPageIR(PageMetadata pm) const
Definition builder.cpp:196
Definition builder.cpp:14
static QList< ContentBlock > buildThreadSafetyAdmonition(const CppReferenceData::ThreadSafetyInfo &ts, const QString &typeWord)
Definition builder.cpp:107
static InlineContent makeResolvedLink(const QString &name, const QString &href)
Definition builder.cpp:74
LinkState
LinkOrigin
static void appendExceptionList(QList< InlineContent > &inlines, const QString &prefix, const QString &topicName, const QList< CppReferenceData::ThreadSafetyExceptionEntry > &exceptions)
Definition builder.cpp:84
BlockType
static InlineContent makeTopicLink(const QString &topicName)
Definition builder.cpp:54
static InlineContent makeTextInline(const QString &text)
Definition builder.cpp:35
static void gatherBodyTocEntries(const QList< ContentBlock > &blocks, QList< NavigationInfo::TocEntry > &out)
Definition builder.cpp:16
InlineType
Represents a structural block element in documentation.
Intermediate representation for a documentation topic.
Definition document.h:197
Access access
Definition document.h:202
Status status
Definition document.h:201
NodeType nodeType
Definition document.h:199
Genus genus
Definition document.h:200
Represents inline content within a documentation block.