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
contentbuilder.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
5
7#include "sorting.h"
8
9#include "../atom.h"
10#include "../tree.h"
11
13
14using namespace Qt::Literals::StringLiterals;
15
16static QString stripCodeMarkerTags(const QString &markedCode)
17{
18 static const QRegularExpression tag(u"</?@[^>]*>"_s);
19 QString t = markedCode;
20 t.replace(tag, QString());
21 t.replace(u"&quot;"_s, u"\""_s);
22 t.replace(u"&gt;"_s, u">"_s);
23 t.replace(u"&lt;"_s, u"<"_s);
24 t.replace(u"&amp;"_s, u"&"_s);
25 return t;
26}
27
28static QString genusToString(Genus genus)
29{
30 switch (genus) {
31 case Genus::CPP: return u"cpp"_s;
32 case Genus::QML: return u"qml"_s;
33 case Genus::DOC: return u"doc"_s;
34 case Genus::API: return u"api"_s;
35 case Genus::DontCare: return {};
36 }
37 return {};
38}
39
41{
42 // DocParser is the only producer of Atom::AnnotatedList and
43 // Atom::GeneratedList, and both build them with at most two
44 // strings: the command argument at index 0 and the optional
45 // bracketed sort directive at index 1. Reading args.at(1) is
46 // therefore the sort slot, not "the last string."
47 const QStringList &args = atom->strings();
48 const QString directive = args.size() > 1 ? args.at(1) : QString();
49 return Sorting::parseSortOrder(directive) == Qt::DescendingOrder
50 ? u"descending"_s : u"ascending"_s;
51}
52
53namespace IR {
54
55/*!
56 \class IR::ContentBuilder
57 \internal
58 \brief Converts Atom chains to QList<IR::ContentBlock> trees.
59
60 ContentBuilder walks a linked list of Atom nodes (QDoc's internal
61 documentation representation) and produces a structured tree of
62 ContentBlock and InlineContent values suitable for template rendering.
63
64 Handled atom types:
65 \list
66 \li ParaLeft, ParaRight -- Paragraph blocks.
67 \li String -- Text inline content.
68 \li C -- Inline code spans.
69 \li Code, CodeBad, Qml -- Code blocks with language attribute.
70 \li SectionLeft, SectionRight -- Section containers.
71 \li SectionHeadingLeft, SectionHeadingRight -- Section headings with
72 level.
73 \li FormattingLeft, FormattingRight -- Bold, italic, teletype,
74 underline, subscript, superscript, parameter, uicontrol, trademark,
75 link, index, notranslate, span.
76 \li ListLeft, ListRight -- Ordered and unordered lists.
77 \li ListItemLeft, ListItemRight -- List items.
78 \li ListItemNumber -- List start number metadata.
79 \li NoteLeft, NoteRight -- Note admonition blocks.
80 \li WarningLeft, WarningRight -- Warning admonition blocks.
81 \li BriefLeft, BriefRight -- Brief exclusion (skipped by default,
82 or emitted as Paragraph with BriefHandling::Include).
83 \li Link, NavLink -- Explicit links with unresolved target.
84 \li AutoLink, NavAutoLink -- Auto-linked type names with unresolved
85 target.
86 \li BR -- Line break inline.
87 \li HR -- Horizontal rule block.
88 \li Nop -- No-operation (skipped).
89 \li BaseName -- No-operation (skipped).
90 \li TableLeft, TableRight -- Table containers with style attribute.
91 \li TableHeaderLeft, TableHeaderRight -- Table header rows.
92 \li TableRowLeft, TableRowRight -- Table data rows.
93 \li TableItemLeft, TableItemRight -- Table cells with optional
94 colspan/rowspan.
95 \li Image -- Block-level image (wrapping Paragraph with centerAlign).
96 \li InlineImage -- Inline image within a paragraph.
97 \li ImageText -- Alt text consumed by the preceding Image or
98 InlineImage handler.
99 \li ListTagLeft, ListTagRight -- Value list tag items (ListItem
100 blocks).
101 \li SinceTagLeft, SinceTagRight -- Version tag items (skipped).
102 \li AnnotatedList -- Typed ListPlaceholder block carrying a
103 \c{ListPlaceholderVariant} discriminator, the group
104 argument, and a parsed sort directive.
105 \li GeneratedList -- Typed ListPlaceholder block for the
106 recognized variants (annotatedexamples, annotatedclasses,
107 classes, classes \e {<rootname>}); a Div fallback retains
108 the legacy attribute bag for unknown variants.
109 \endlist
110
111 Format-conditional atoms (FormatIf, FormatElse, FormatEndif) are
112 skipped unconditionally. The template generator builds a
113 format-agnostic IR that serves all output formats from a single
114 build pass.
115
116 The optional BriefHandling parameter controls whether brief
117 content (between BriefLeft and BriefRight atoms) is included in
118 the output. The default is BriefHandling::Skip, which suppresses
119 brief content. BriefHandling::Include causes brief content to be
120 emitted as a Paragraph block.
121
122 ContentBuilder depends only on Atom (for reading the chain) and IR types
123 (for producing output).
124
125 \sa ContentBlock, InlineContent
126*/
127
128/*!
129 Constructs a ContentBuilder.
130
131 The \a briefHandling parameter controls whether content between
132 BriefLeft and BriefRight atoms is emitted as a Paragraph block
133 (BriefHandling::Include) or suppressed (BriefHandling::Skip).
134
135 The \a headingOffset parameter shifts section heading levels to
136 account for the page structure. QDoc's \\section1 maps to level 1,
137 but pages already use \c{<h1>} for the title and \c{<h2>} for
138 major sections. The legacy generators apply an offset derived from
139 the node type; callers pass that same offset here so the IR
140 produces correct heading levels without depending on \b Node.
141*/
142ContentBuilder::ContentBuilder(BriefHandling briefHandling, int headingOffset,
143 DiagnosticHandler diagnosticHandler)
144 : m_briefHandling(briefHandling), m_headingOffset(headingOffset),
146{
147}
148
149static InlineType formattingToInlineType(const QString &formatting)
150{
151 if (formatting == ATOM_FORMATTING_BOLD)
152 return InlineType::Bold;
153 if (formatting == ATOM_FORMATTING_ITALIC)
154 return InlineType::Italic;
155 if (formatting == ATOM_FORMATTING_TELETYPE)
157 if (formatting == ATOM_FORMATTING_UNDERLINE)
159 if (formatting == ATOM_FORMATTING_SUBSCRIPT)
161 if (formatting == ATOM_FORMATTING_SUPERSCRIPT)
163 if (formatting == ATOM_FORMATTING_PARAMETER)
165 return InlineType::Text;
166}
167
168/*!
169 Walks the atom chain starting at \a firstAtom and returns a list
170 of ContentBlock trees representing the structured documentation body.
171
172 Returns an empty list if \a firstAtom is \nullptr.
173
174 The builder is reset before processing, so a single ContentBuilder
175 instance can be reused for multiple build() calls.
176*/
178{
179 m_result.clear();
180 m_blockPath.clear();
181 m_inlinePath.clear();
182 m_inlineBaseDepths.clear();
183 m_inBrief = false;
184 m_inLink = false;
185
186 if (!firstAtom)
187 return {};
188
189 processAtoms(firstAtom);
190
191 // Malformed atom chain recovery: auto-close remaining blocks in
192 // release builds, assert in debug to surface the source error.
193 while (!m_blockPath.isEmpty())
194 closeBlock();
195 m_inlinePath.clear();
196 m_inlineBaseDepths.clear();
197 m_inLink = false;
198 m_inBrief = false;
199
200 return m_result;
201}
202
203/*!
204 Walks the full atom chain starting at \a atom, building the
205 content tree. FormatIf..FormatEndif blocks are skipped
206 unconditionally via skipFormatIfBlock(). Stray FormatElse and
207 FormatEndif atoms outside any FormatIf context are ignored.
208*/
209void ContentBuilder::processAtoms(const Atom *atom)
210{
211 while (atom) {
212 if (atom->type() == Atom::FormatIf) {
213 atom = skipFormatIfBlock(atom);
214 continue;
215 }
217 atom = atom->next();
218 continue;
219 }
220 atom = dispatchAtom(atom);
221 if (!atom)
222 return;
223 atom = atom->next();
224 }
225}
226
227/*!
228 Skips an entire FormatIf..FormatEndif block, including any nested
229 FormatIf blocks and FormatElse branches. The scan only tracks
230 FormatIf and FormatEndif for depth counting; all other atom types
231 (including FormatElse) are treated as inert content and walked
232 past without dispatch.
233
234 The template generator builds a format-agnostic IR, so
235 format-conditional content is unconditionally excluded.
236
237 Returns a pointer to the atom after FormatEndif, or \nullptr if
238 the chain ends before FormatEndif is found.
239*/
240const Atom *ContentBuilder::skipFormatIfBlock(const Atom *atom)
241{
242 Q_ASSERT(atom->type() == Atom::FormatIf);
243 int depth = 1;
244 atom = atom->next();
245 while (atom && depth > 0) {
246 if (atom->type() == Atom::FormatIf)
247 ++depth;
248 else if (atom->type() == Atom::FormatEndif)
249 --depth;
250 atom = atom->next();
251 }
252 return atom;
253}
254
255/*!
256 Dispatches a single atom to the content model. Returns the last
257 atom consumed — usually \a atom itself, but some atom types may
258 consume subsequent atoms.
259*/
260const Atom *ContentBuilder::dispatchAtom(const Atom *atom)
261{
262 if (atom->type() == Atom::BriefLeft) {
263 m_inBrief = true;
264 if (m_briefHandling == BriefHandling::Include)
265 openBlock(BlockType::Paragraph);
266 return atom;
267 }
268 if (atom->type() == Atom::BriefRight) {
269 if (m_briefHandling == BriefHandling::Include)
270 closeBlock();
271 m_inBrief = false;
272 return atom;
273 }
274 if (m_inBrief && m_briefHandling != BriefHandling::Include)
275 return atom;
276
277 switch (atom->type()) {
278
279 case Atom::ParaLeft:
280 openBlock(BlockType::Paragraph);
281 break;
282
283 case Atom::ParaRight:
284 closeBlock();
285 break;
286
287 case Atom::String:
288 addLeafInline(InlineType::Text, atom->string());
289 break;
290
291 case Atom::C:
292 addLeafInline(InlineType::Code, stripCodeMarkerTags(atom->string()));
293 break;
294
295 case Atom::Code:
296 case Atom::CodeBad:
297 case Atom::Qml: {
298 QJsonObject attrs;
299 if (atom->type() == Atom::Qml) {
300 attrs["language"_L1] = u"qml"_s;
301 } else if (atom->type() == Atom::CodeBad) {
302 attrs["language"_L1] = u"cpp"_s;
303 attrs["bad"_L1] = true;
304 } else if (atom->count() >= 2 && !atom->string(1).isEmpty()) {
305 attrs["language"_L1] = atom->string(1);
306 } else {
307 attrs["language"_L1] = u"cpp"_s;
308 }
309
310 openBlock(BlockType::CodeBlock, attrs);
311 addLeafInline(InlineType::Text, stripCodeMarkerTags(atom->string()));
312 closeBlock();
313 break;
314 }
315
316 case Atom::AutoLink:
317 case Atom::NavAutoLink: {
318 // href values are not author-controlled. They are produced by QDoc
319 // link resolution (\l, autolinks) against the node tree, or by
320 // \image path handling. They don't contain arbitrary schemes (e.g.
321 // javascript:). Only link text originates from user-authored docs
322 // and must be HTML-escaped in templates.
323 InlineContent link;
325 link.href = atom->string();
327 link.children.append({ InlineType::Text, atom->string(), {}, {}, {}, {}, {} });
328 addInline(std::move(link));
329 break;
330 }
331
332 case Atom::Link:
333 case Atom::NavLink: {
334 if (m_blockPath.isEmpty())
335 openBlock(BlockType::Paragraph);
336
337 m_inLink = true;
338
339 InlineContent link;
341 link.href = atom->string();
343
344 // Extract genus and module scope from LinkAtom if available.
345 if (atom->isLinkAtom()) {
346 // genus() and domain() are non-const but non-mutating;
347 // the same const_cast pattern is used in qdocdatabase.cpp.
348 auto *mutableAtom = const_cast<Atom *>(atom);
349 Genus genus = mutableAtom->genus();
350 QString genusStr = genusToString(genus);
351 if (!genusStr.isEmpty())
352 link.attributes["linkGenus"_L1] = genusStr;
353 if (Tree *domain = mutableAtom->domain())
354 link.attributes["linkModule"_L1] = domain->physicalModuleName();
355 }
356
357 pushInlineContainer(std::move(link));
358
359 // Link atoms are always followed by FormattingLeft("link");
360 // skip it to avoid double-processing.
362 && atom->next()->string() == ATOM_FORMATTING_LINK) {
363 return atom->next();
364 }
365 break;
366 }
367
369 const QString &fmt = atom->string();
370
371 if (fmt == ATOM_FORMATTING_INDEX || fmt.startsWith(u"span "_s))
372 break;
373
374 if (fmt == ATOM_FORMATTING_LINK)
375 break;
376
378 break;
379
380 if (fmt == ATOM_FORMATTING_UICONTROL) {
381 InlineContent bold;
383 pushInlineContainer(std::move(bold));
384 break;
385 }
386
387 InlineType type = formattingToInlineType(fmt);
388 if (type == InlineType::Text)
389 break;
390
391 InlineContent container;
392 container.type = type;
393 pushInlineContainer(std::move(container));
394 break;
395 }
396
398 const QString &fmt = atom->string();
399
400 if (fmt == ATOM_FORMATTING_LINK) {
401 if (m_inLink) {
402 const qsizetype base = m_inlineBaseDepths.isEmpty() ? 0 : m_inlineBaseDepths.last();
403 if (m_inlinePath.size() > base) {
404 Q_ASSERT(resolveInline()->type == InlineType::Link);
405 m_inlinePath.removeLast();
406 }
407 m_inLink = false;
408 }
409 break;
410 }
411
413 || fmt.startsWith(u"span "_s) || fmt == ATOM_FORMATTING_TRADEMARK) {
414 break;
415 }
416
417 const qsizetype base = m_inlineBaseDepths.isEmpty() ? 0 : m_inlineBaseDepths.last();
418 if (m_inlinePath.size() > base)
419 m_inlinePath.removeLast();
420 break;
421 }
422
423 case Atom::SectionLeft:
424 openBlock(BlockType::Section);
425 break;
426
428 closeBlock();
429 break;
430
432 QJsonObject attrs;
433 attrs["level"_L1] = atom->string().toInt() + m_headingOffset;
434 // SectionHeadingLeft carries the heading level at string(0) and the
435 // anchor identifier at string(1). The second payload is populated
436 // only when a heading-level macro (\sectionN) emits an explicit ref.
437 if (atom->count() >= 2)
438 attrs["sectionRef"_L1] = atom->string(1);
439 openBlock(BlockType::SectionHeading, attrs);
440 break;
441 }
442
444 closeBlock();
445 break;
446
447 case Atom::ListLeft: {
448 QJsonObject attrs;
449 const QString &listType = atom->string();
450 attrs["listType"_L1] = listType;
451 if (listType == ATOM_LIST_TAG || listType == ATOM_LIST_VALUE)
452 openBlock(BlockType::DefinitionList, attrs);
453 else
454 openBlock(BlockType::List, attrs);
455 break;
456 }
457
458 case Atom::ListRight:
459 closeBlock();
460 break;
461
462 case Atom::ListItemLeft: {
463 ContentBlock *parent = resolveBlock();
464 if (parent->type == BlockType::DefinitionList)
465 openBlock(BlockType::DefinitionDescription);
466 else
467 openBlock(BlockType::ListItem);
468 break;
469 }
470
472 closeBlock();
473 break;
474
476 // Start-number metadata is not yet represented in the IR.
477 break;
478
479 case Atom::NoteLeft:
480 openBlock(BlockType::Note);
481 break;
482
483 case Atom::NoteRight:
484 closeBlock();
485 break;
486
487 case Atom::WarningLeft:
488 openBlock(BlockType::Warning);
489 break;
490
492 closeBlock();
493 break;
494
495 case Atom::BR:
496 addLeafInline(InlineType::LineBreak, {});
497 break;
498
499 case Atom::HR:
500 openBlock(BlockType::HorizontalRule);
501 closeBlock();
502 break;
503
504 case Atom::AnnotatedList: {
505 QJsonObject attrs;
506 attrs["variant"_L1] = toString(ListPlaceholderVariant::AnnotatedGroup);
507 attrs["argument"_L1] = atom->string();
508 attrs["sort"_L1] = placeholderSortAttribute(atom);
509 openBlock(BlockType::ListPlaceholder, attrs);
510 closeBlock();
511 break;
512 }
513
514 case Atom::GeneratedList: {
515 const QString arg = atom->string();
516 QJsonObject attrs;
517 attrs["argument"_L1] = arg;
518 attrs["sort"_L1] = placeholderSortAttribute(atom);
519
520 static constexpr auto classesPrefix = "classes "_L1;
521 if (arg == "annotatedexamples"_L1) {
522 attrs["variant"_L1] = toString(ListPlaceholderVariant::AnnotatedExamples);
523 } else if (arg == "annotatedclasses"_L1) {
524 attrs["variant"_L1] = toString(ListPlaceholderVariant::AnnotatedClasses);
525 } else if (arg == "classes"_L1 || arg.startsWith(classesPrefix)) {
526 attrs["variant"_L1] = toString(ListPlaceholderVariant::CompactClasses);
527 if (arg.startsWith(classesPrefix)) {
528 const QString rootName = arg.sliced(classesPrefix.size()).trimmed();
529 if (!rootName.isEmpty())
530 attrs["rootName"_L1] = rootName;
531 }
532 } else {
533 // Unknown \generatelist variant: keep the legacy attribute-bag
534 // Div so deferred variants continue to render as they do today.
535 // Remove this branch when those variants acquire their own
536 // ListPlaceholderVariant entries in a follow-up phase.
537 QJsonObject fallback;
538 fallback["generatedList"_L1] = arg;
539 openBlock(BlockType::Div, fallback);
540 closeBlock();
541 break;
542 }
543
544 openBlock(BlockType::ListPlaceholder, attrs);
545 closeBlock();
546 break;
547 }
548
549 case Atom::TableLeft: {
550 QJsonObject attrs;
551 QString tableStyle = u"generic"_s;
552 QString width;
553
554 for (int i = 0; i < atom->count(); ++i) {
555 const QString &arg = atom->string(i);
556 if (arg == "borderless"_L1)
557 tableStyle = arg;
558 else if (arg.contains('%'_L1))
559 width = arg;
560 }
561
562 // Handle "100 %" (space before percent) — the percent arrives
563 // as a separate atom argument, reconstruct the width value.
564 if (width == "%"_L1) {
565 bool ok = false;
566 int pct = atom->string(0).toInt(&ok);
567 width = ok ? QString::number(pct) + '%'_L1 : QString();
568 }
569
570 attrs["style"_L1] = tableStyle;
571 if (!width.isEmpty())
572 attrs["width"_L1] = width;
573 openBlock(BlockType::Table, attrs);
574 break;
575 }
576
577 case Atom::TableRight:
578 closeBlock();
579 break;
580
581 case Atom::TableHeaderLeft:
582 openBlock(BlockType::TableHeaderRow);
583 break;
584
586 closeBlock();
587 break;
588
589 case Atom::TableRowLeft:
590 openBlock(BlockType::TableRow);
591 break;
592
594 closeBlock();
595 break;
596
597 case Atom::TableItemLeft: {
598 QJsonObject attrs;
599 const QString &spec = atom->string();
600 if (!spec.isEmpty()) {
601 const auto parts = QStringView{spec}.split(u',');
602 if (parts.size() >= 2) {
603 int colspan = qMax(1, parts[0].toInt());
604 int rowspan = qMax(1, parts[1].toInt());
605 if (colspan > 1)
606 attrs["colspan"_L1] = colspan;
607 if (rowspan > 1)
608 attrs["rowspan"_L1] = rowspan;
609 }
610 }
611 openBlock(BlockType::TableCell, attrs);
612 break;
613 }
614
616 closeBlock();
617 break;
618
619 case Atom::ListTagLeft:
620 openBlock(BlockType::DefinitionTerm);
621 break;
622
624 closeBlock();
625 break;
626
627 case Atom::Image: {
628 QJsonObject attrs;
629 attrs["class"_L1] = u"centerAlign"_s;
630 openBlock(BlockType::Paragraph, attrs);
631
632 InlineContent img;
634 img.href = atom->string();
635 if (atom->next() && atom->next()->type() == Atom::ImageText)
636 img.title = atom->next()->string();
637 addInline(std::move(img));
638
639 closeBlock();
640
641 if (atom->next() && atom->next()->type() == Atom::ImageText)
642 return atom->next();
643 break;
644 }
645
646 case Atom::InlineImage: {
647 if (Q_UNLIKELY(m_blockPath.isEmpty()))
648 break;
649
650 InlineContent img;
652 img.href = atom->string();
653 if (atom->next() && atom->next()->type() == Atom::ImageText)
654 img.title = atom->next()->string();
655 addInline(std::move(img));
656
657 if (atom->next() && atom->next()->type() == Atom::ImageText)
658 return atom->next();
659 break;
660 }
661
662 case Atom::ImageText:
663 break;
664
667 break;
668
669 case Atom::Nop:
670 case Atom::BaseName:
671 break;
672
673 default:
674 break;
675 }
676 return atom;
677}
678
679/*!
680 Opens a new block of type \a type with optional \a attrs.
681
682 If the block path is empty, the block is added to the top-level
683 result list. Otherwise it is added as a child of the current
684 container block.
685*/
686void ContentBuilder::openBlock(BlockType type, QJsonObject attrs)
687{
688 ContentBlock block;
689 block.type = type;
690 block.attributes = std::move(attrs);
691
692 m_inlineBaseDepths.append(m_inlinePath.size());
693
694 if (m_blockPath.isEmpty()) {
695 m_result.append(std::move(block));
696 m_blockPath.append(m_result.size() - 1);
697 } else {
698 auto *parent = resolveBlock();
699 parent->children.append(std::move(block));
700 m_blockPath.append(parent->children.size() - 1);
701 }
702}
703
704/*!
705 Closes the current block by popping it from the block path.
706
707 Verifies that the inline path depth matches the depth recorded
708 when this block was opened (all formatting pairs balanced).
709 In release builds, the inline path is restored to the expected
710 depth as a safety measure against malformed atom chains.
711*/
712void ContentBuilder::closeBlock()
713{
714 if (!m_blockPath.isEmpty()) {
715 if (Q_UNLIKELY(m_inlineBaseDepths.isEmpty())) {
716 m_inlinePath.clear();
717 m_blockPath.clear();
718 m_inlineBaseDepths.clear();
719 m_inLink = false;
720 return;
721 }
722 const qsizetype expectedDepth = m_inlineBaseDepths.last();
723 if (m_inLink && m_inlinePath.size() > expectedDepth)
724 m_inLink = false;
725 m_inlinePath.resize(expectedDepth);
726 m_inlinePath.resize(expectedDepth);
727 m_inlineBaseDepths.removeLast();
728 m_blockPath.removeLast();
729 }
730}
731
732/*!
733 Adds \a inline_ to the current block's inline content.
734
735 If there is an active inline container (from FormattingLeft or
736 Link atom), the inline is added to that container's children
737 instead.
738
739 If no block is open, the inline is dropped. This shouldn't happen
740 with well-formed atom chains (text is always wrapped in
741 ParaLeft/ParaRight), and is asserted in debug builds.
742*/
743void ContentBuilder::addInline(InlineContent inline_)
744{
745 if (!m_inlinePath.isEmpty()) {
746 resolveInline()->children.append(std::move(inline_));
747 } else if (!m_blockPath.isEmpty()) {
748 resolveBlock()->inlineContent.append(std::move(inline_));
749 } else {
750 if (m_diagnose)
751 m_diagnose(QtWarningMsg, u"Dropping inline content outside any block"_s);
752 }
753}
754
755/*!
756 Convenience method: creates a leaf InlineContent of the given
757 \a type with \a text and appends it via addInline().
758*/
759void ContentBuilder::addLeafInline(InlineType type, const QString &text)
760{
761 InlineContent ic;
762 ic.type = type;
763 ic.text = text;
764 addInline(std::move(ic));
765}
766
767/*!
768 Pushes \a container as a new inline container. Subsequent
769 addInline() and pushInlineContainer() calls will nest
770 their content inside this container.
771
772 Unlike addInline(), which only appends leaf inlines, this method
773 also updates m_inlinePath to enable nesting. It respects the
774 existing inline path: if we are already inside a Link or formatting
775 container, the new container is added as a child of that container.
776*/
777void ContentBuilder::pushInlineContainer(InlineContent container)
778{
779 if (!m_inlinePath.isEmpty()) {
780 auto *parent = resolveInline();
781 parent->children.append(std::move(container));
782 m_inlinePath.append(parent->children.size() - 1);
783 } else if (!m_blockPath.isEmpty()) {
784 auto *block = resolveBlock();
785 block->inlineContent.append(std::move(container));
786 m_inlinePath.append(block->inlineContent.size() - 1);
787 }
788}
789
790/*!
791 Resolves the current block path to a ContentBlock pointer.
792
793 The returned pointer is valid only until the next QList mutation
794 on any list in the path. Callers must use the pointer within a
795 single expression and discard it before appending to any QList.
796*/
797ContentBlock *ContentBuilder::resolveBlock()
798{
799 Q_ASSERT(!m_blockPath.isEmpty());
800 Q_ASSERT(m_blockPath[0] >= 0 && m_blockPath[0] < m_result.size());
801 ContentBlock *block = &m_result[m_blockPath[0]];
802 for (qsizetype i = 1; i < m_blockPath.size(); ++i) {
803 Q_ASSERT(m_blockPath[i] >= 0 && m_blockPath[i] < block->children.size());
804 block = &block->children[m_blockPath[i]];
805 }
806 return block;
807}
808
809/*!
810 Resolves the current inline path to an InlineContent pointer.
811
812 Walks the block path first (via resolveBlock()), then descends
813 through the block's inlineContent and nested children lists
814 using the indices in m_inlinePath.
815*/
816InlineContent *ContentBuilder::resolveInline()
817{
818 ContentBlock *block = resolveBlock();
819 Q_ASSERT(!m_inlinePath.isEmpty());
820 Q_ASSERT(m_inlinePath[0] >= 0 && m_inlinePath[0] < block->inlineContent.size());
821 InlineContent *ic = &block->inlineContent[m_inlinePath[0]];
822 for (qsizetype i = 1; i < m_inlinePath.size(); ++i) {
823 Q_ASSERT(m_inlinePath[i] >= 0 && m_inlinePath[i] < ic->children.size());
824 ic = &ic->children[m_inlinePath[i]];
825 }
826 return ic;
827}
828
829} // namespace IR
830
831QT_END_NAMESPACE
#define ATOM_FORMATTING_TELETYPE
Definition atom.h:213
#define ATOM_FORMATTING_UNDERLINE
Definition atom.h:216
#define ATOM_FORMATTING_NOTRANSLATE
Definition atom.h:208
#define ATOM_LIST_TAG
Definition atom.h:219
#define ATOM_FORMATTING_SUBSCRIPT
Definition atom.h:211
#define ATOM_FORMATTING_BOLD
Definition atom.h:204
#define ATOM_FORMATTING_TRADEMARK
Definition atom.h:214
#define ATOM_LIST_VALUE
Definition atom.h:220
#define ATOM_FORMATTING_ITALIC
Definition atom.h:206
#define ATOM_FORMATTING_LINK
Definition atom.h:207
#define ATOM_FORMATTING_SUPERSCRIPT
Definition atom.h:212
#define ATOM_FORMATTING_INDEX
Definition atom.h:205
#define ATOM_FORMATTING_UICONTROL
Definition atom.h:215
#define ATOM_FORMATTING_PARAMETER
Definition atom.h:209
The Atom class is the fundamental unit for representing documents internally.
Definition atom.h:19
AtomType type() const
Return the type of this atom.
Definition atom.h:155
@ TableRight
Definition atom.h:97
@ GeneratedList
Definition atom.h:52
@ BriefRight
Definition atom.h:27
@ TableHeaderRight
Definition atom.h:99
@ FormatElse
Definition atom.h:47
@ InlineImage
Definition atom.h:58
@ TableRowRight
Definition atom.h:101
@ Nop
Definition atom.h:74
@ WarningRight
Definition atom.h:111
@ ListTagRight
Definition atom.h:68
@ NavLink
Definition atom.h:73
@ ListItemNumber
Definition atom.h:66
@ SinceTagRight
Definition atom.h:91
@ CodeBad
Definition atom.h:32
@ AnnotatedList
Definition atom.h:22
@ SectionRight
Definition atom.h:84
@ SectionHeadingLeft
Definition atom.h:85
@ TableLeft
Definition atom.h:96
@ ListItemRight
Definition atom.h:70
@ Image
Definition atom.h:54
@ TableItemRight
Definition atom.h:103
@ ListItemLeft
Definition atom.h:69
@ Code
Definition atom.h:31
@ ListLeft
Definition atom.h:65
@ NavAutoLink
Definition atom.h:72
@ BriefLeft
Definition atom.h:26
@ ImageText
Definition atom.h:55
@ ListRight
Definition atom.h:71
@ ParaRight
Definition atom.h:78
@ Qml
Definition atom.h:79
@ FormattingLeft
Definition atom.h:50
@ FormattingRight
Definition atom.h:51
@ SectionHeadingRight
Definition atom.h:86
@ Link
Definition atom.h:63
@ FormatEndif
Definition atom.h:48
@ SinceTagLeft
Definition atom.h:90
@ AutoLink
Definition atom.h:23
@ TableItemLeft
Definition atom.h:102
@ NoteRight
Definition atom.h:76
@ BaseName
Definition atom.h:24
@ FormatIf
Definition atom.h:49
virtual bool isLinkAtom() const
Definition atom.h:163
const Atom * next() const
Return the next atom in the atom list.
Definition atom.h:152
virtual Genus genus()
Definition atom.h:164
Converts Atom chains to QList<IR::ContentBlock> trees.
QList< ContentBlock > build(const Atom *firstAtom)
Walks the atom chain starting at firstAtom and returns a list of ContentBlock trees representing the ...
ContentBuilder(BriefHandling briefHandling=BriefHandling::Skip, int headingOffset=0, DiagnosticHandler diagnosticHandler={})
Constructs a ContentBuilder.
static QString genusToString(Genus genus)
static QString stripCodeMarkerTags(const QString &markedCode)
static QString placeholderSortAttribute(const Atom *atom)
Definition builder.cpp:14
LinkState
LinkOrigin
BriefHandling
BlockType
static InlineType formattingToInlineType(const QString &formatting)
InlineType
Combined button and popup list for selecting options.
Represents a structural block element in documentation.
Represents inline content within a documentation block.