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