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.isConcept = src.isConcept;
253 info.noAutoList = src.noAutoList;
254
255 for (const auto &entry : src.namespaces)
256 info.namespaces.append({entry.name, entry.href, entry.brief});
257 for (const auto &entry : src.classes)
258 info.classes.append({entry.name, entry.href, entry.brief});
259 for (const auto &entry : src.members)
260 info.members.append({entry.name, entry.href, entry.brief});
261
262 ir.collectionInfo = std::move(info);
263 }
264
265 if (pm.cppReferenceData) {
266 const auto &src = *pm.cppReferenceData;
267 CppReferenceInfo info;
268
269 info.headerInclude = src.headerInclude;
270 info.cmakeFindPackage = src.cmakeFindPackage;
271 info.cmakeTargetLinkLibraries = src.cmakeTargetLinkLibraries;
272 info.qmakeVariable = src.qmakeVariable;
273 info.statusText = src.statusText;
274 info.statusCssClass = src.statusCssClass;
275
276 if (src.qmlNativeType)
277 info.qmlNativeType = CppReferenceInfo::QmlNativeTypeLink{
278 src.qmlNativeType->name, src.qmlNativeType->href};
279
280 for (const auto &bc : src.baseClasses)
281 info.baseClasses.append({bc.name, bc.href, bc.access});
282 for (const auto &dc : src.derivedClasses)
283 info.derivedClasses.append({dc.name, dc.href});
284 info.suppressInheritance = src.suppressInheritance;
285
286 info.templateDeclSpans = src.templateDeclSpans;
287 info.referencedConcepts = src.referencedConcepts;
288
289 info.isInnerClass = src.isInnerClass;
290 info.isNamespace = src.isNamespace;
291 info.isHeader = src.isHeader;
292
293 info.isPartialNamespace = src.isPartialNamespace;
294 info.fullNamespaceHref = src.fullNamespaceHref;
295 info.fullNamespaceModuleName = src.fullNamespaceModuleName;
296
297 info.typeWord = src.typeWord;
298 info.ancestorNames = src.ancestorNames;
299
300 info.selfComparisonCategory = src.selfComparisonCategory;
301 for (const auto &ce : src.comparisonEntries)
302 info.comparisonEntries.append({ce.category, ce.comparableTypes, ce.description});
303
304 if (src.threadSafety) {
306 ts.level = src.threadSafety->level;
307 for (const auto &e : src.threadSafety->reentrantExceptions)
308 ts.reentrantExceptions.append({e.name, e.href});
309 for (const auto &e : src.threadSafety->threadSafeExceptions)
310 ts.threadSafeExceptions.append({e.name, e.href});
311 for (const auto &e : src.threadSafety->nonReentrantExceptions)
312 ts.nonReentrantExceptions.append({e.name, e.href});
313 info.threadSafety = std::move(ts);
314 info.threadSafetyAdmonition = buildThreadSafetyAdmonition(
315 *src.threadSafety, src.typeWord);
316 }
317
318 for (const auto &g : src.groups)
319 info.groups.append({g.name, g.href});
320
321 info.hasObsoleteMembers = src.hasObsoleteMembers;
322 // TODO: obsoleteMembersUrl is currently set by the generator
323 // after assembly because it depends on the file extension.
324 // This post-build mutation violates frozen-IR. The Builder
325 // should receive the file extension and compute it here.
326
327 ir.cppReferenceInfo = std::move(info);
328 }
329
330 {
331 const auto &src = pm.navigationData;
332 NavigationInfo info;
333 for (const auto &bc : src.breadcrumbs) {
334 NavigationInfo::BreadcrumbEntry entry;
335 entry.title = bc.title;
336 entry.href = bc.href;
337 switch (bc.state) {
338 case NavigationData::CrumbState::Link:
339 entry.state = NavigationInfo::CrumbState::Link;
340 break;
341 case NavigationData::CrumbState::Current:
342 entry.state = NavigationInfo::CrumbState::Current;
343 break;
344 case NavigationData::CrumbState::Unresolved:
345 entry.state = NavigationInfo::CrumbState::Unresolved;
346 break;
347 }
348 info.breadcrumbs.append(std::move(entry));
349 }
350 if (src.previousLink)
351 info.previousLink = NavigationInfo::LinkEntry{
352 src.previousLink->title, src.previousLink->href};
353 if (src.nextLink)
354 info.nextLink = NavigationInfo::LinkEntry{
355 src.nextLink->title, src.nextLink->href};
356 if (src.startLink)
357 info.startLink = NavigationInfo::LinkEntry{
358 src.startLink->title, src.startLink->href};
359 info.tocDepth = src.tocDepth;
360
361 // Assemble TOC entries from page structure (summary sections +
362 // "Detailed Description" divider + body section-headings +
363 // detail sections). Order reflects the rendered page, so templates
364 // can iterate for a top-to-bottom reading of available anchors.
365 for (const auto &s : ir.summarySections) {
366 if (!s.title.isEmpty() && !s.id.isEmpty())
367 info.tocEntries.append({s.title, s.id, 2});
368 }
369 if ((ir.cppReferenceInfo || ir.qmlTypeInfo) && !ir.body.isEmpty())
370 info.tocEntries.append(
371 {u"Detailed Description"_s, u"details"_s, 2});
372 gatherBodyTocEntries(ir.body, info.tocEntries);
373 for (const auto &s : ir.detailSections) {
374 if (!s.title.isEmpty() && !s.id.isEmpty())
375 info.tocEntries.append({s.title, s.id, 2});
376 }
377
378 const bool hasAnyNavigation = !info.breadcrumbs.isEmpty()
379 || info.previousLink || info.nextLink || info.startLink
380 || !info.tocEntries.isEmpty() || info.tocDepth != -1;
381 if (hasAnyNavigation)
382 ir.navigationInfo = std::move(info);
383 }
384
385 // Plain-text fallback for templates that render unstructured
386 // content. Structured rendering through content.blocks is the
387 // primary path; this key is only populated when there is text
388 // worth showing, so an empty body never produces a hollow
389 // section wrapper in the rendered output.
390 QStringList paragraphs;
391 for (const auto &block : ir.body) {
392 const QString text = block.plainText();
393 if (!text.isEmpty())
394 paragraphs.append(text);
395 }
396 const QString joined = paragraphs.join("\n\n"_L1);
397 if (!joined.isEmpty())
398 ir.contentJson["text"_L1] = joined;
399
400 return ir;
401}
402
403} // namespace IR
404
405QT_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:199
Access access
Definition document.h:204
Status status
Definition document.h:203
NodeType nodeType
Definition document.h:201
Genus genus
Definition document.h:202
LinkData(LinkOrigin o, LinkState s, std::optional< SourceLocation > loc=std::nullopt)
Represents inline content within a documentation block.