recon_gen.common.rich_text

Compose rich-text XML for QuickSight SheetTextBox.Content.

QuickSight accepts a small XML dialect inside a single <text-box> root (undocumented — full set confirmed by round-tripping a UI-authored text box that exercised every formatting button via describe-analysis-definition):

  • <inline ...>text</inline> — styled run; attrs font-size="36px", color="#hex", background-color="#hex" (highlight), font-family="Name"

  • <b> / <i> / <s> / <u> — bold / italic / strikethrough / underline (bare HTML tags, NOT <inline> attrs)

  • <block align="center">text</block> — paragraph alignment (center / right; left is the default, emitted with no block)

  • <br/> — explicit line break

  • <ul><li class="ql-indent-0">item</li></ul> — bulleted list (the ql-indent-0 class is required for top-level bullets)

  • <a href="..." target="_self">Link</a> — hyperlink

  • <expression>${pName}</expression> — live parameter-value injection

  • Body text between tags must be XML-escaped; &nbsp; survives

Theme tokens aren’t supported by the parser, so colors are resolved to hex at generate-time and interpolated here by the caller.

Authoring helpers:

  • body(text) — single-line plain text, XML-escaped. Use for one-shot prose with no paragraph breaks or links.

  • markdown(text) — multi-paragraph prose with optional inline [text](url) links. \n\n paragraph breaks become <br/><br/>, a lone \n is a CommonMark soft break that collapses to a single space, [text](url) becomes a clickable <a href="...">. Use whenever the source string is L2-YAML-supplied description prose (which is markdown-shaped by convention).

  • markdown_inline(text) — same XML-escape + [text](url) link handling, but ALL newlines collapse to a single space (no <br/>). Use inside contexts where <br/> is not a valid child — most notably <li>: QS’s XML parser rejects <br/> as a child of <li> with Element 'li' cannot have 'br' elements as children.

bullets() is defensive: it routes items through markdown() then strips any <br/> from each item with a UserWarning showing the original input. Authors can choose markdown_inline explicitly when they need a guaranteed-no-<br/> rendering of a single string outside a bullet context.

Functions

body(text)

Plain body text — XML-escaped, no styling.

bold(text)

Bold run — <b> (XML-escaped body).

bullets(items)

Bulleted list at indent level 0.

bullets_raw(items)

Bulleted list whose items are pre-composed XML (so inline styling works inside bullets).

code(text)

Inline monospace run.

heading(text[, color])

Top-level heading (32px).

inline(text, *[, font_size, color])

Styled inline run.

link(text, href)

Hyperlink opening in the same tab.

markdown(text)

Block + inline markdown → QuickSight rich-text XML.

markdown_inline(text)

Single-line markdown with link handling but no <br/>.

subheading(text[, color])

Section subheading (20px).

text_box(*parts)

Wrap parts in a <text-box> root.

recon_gen.common.rich_text.body(text)[source]

Plain body text — XML-escaped, no styling.

Return type:

str

Parameters:

text (str)

recon_gen.common.rich_text.bold(text)[source]

Bold run — <b> (XML-escaped body).

Return type:

str

Parameters:

text (str)

recon_gen.common.rich_text.bullets(items)[source]

Bulleted list at indent level 0.

Each item is processed through markdown() (so inline [text](url) links render as clickable anchors) and then defensively stripped of any <br/> tags — QS’s XML parser rejects <br/> as a child of <li> with Element 'li' cannot have 'br' elements as children. A \n\n paragraph break still reflows to <br/><br/> via markdown() and would break CreateAnalysis inside an <li> (the v8.5.4 → v8.5.8 regression on the L1 Drift sheet), so it is stripped here. (A lone \n is a soft break that markdown() collapses to a space upstream, so it never reaches this strip.)

Any stripped <br/> raises a UserWarning showing the original item so authors can clean the source string when the line break was actually intended (e.g. break the item into two).

Return type:

str

Parameters:

items (Iterable[str])

recon_gen.common.rich_text.bullets_raw(items)[source]

Bulleted list whose items are pre-composed XML (so inline styling works inside bullets).

Return type:

str

Parameters:

items (Iterable[str])

recon_gen.common.rich_text.code(text)[source]

Inline monospace run. QS has no code tag, so this is an <inline font-family> (render.py maps it to a <span style="font-family:…"> in App2).

Return type:

str

Parameters:

text (str)

recon_gen.common.rich_text.heading(text, color=None)[source]

Top-level heading (32px).

Return type:

str

Parameters:
  • text (str)

  • color (str | None)

recon_gen.common.rich_text.inline(text, *, font_size=None, color=None)[source]

Styled inline run. font_size like "24px"; color like "#2E5090".

Return type:

str

Parameters:
  • text (str)

  • font_size (str | None)

  • color (str | None)

Hyperlink opening in the same tab.

Return type:

str

Parameters:
  • text (str)

  • href (str)

recon_gen.common.rich_text.markdown(text)[source]

Block + inline markdown → QuickSight rich-text XML.

Block structure (paragraphs split on a blank line, \n\n+):

  • A block whose first non-blank line is a bullet (- `` / ``* ``) becomes a ``<ul><li class="ql-indent-0">…</li></ul> list. Non-bullet lines inside the block are continuations of the preceding item (soft-wrapped source reflows into one item). Consecutive bullet blocks (authors who separate items with blank lines) merge into one <ul>.

  • Any other block is a prose paragraph: a lone \n is a CommonMark soft break (→ a single space), and blocks join with <br/><br/>.

Inline (within every block): [text](url) → clickable <a>, **bold**<b>, `code` → monospace <inline>; the remaining text is XML-escaped.

Use whenever the input is L2-YAML-supplied prose or any markdown-shaped string. body() is for plain single-line text only — feeding multi-paragraph / link- / emphasis-bearing strings to body() produces unrendered markdown in QuickSight (the v8.4.0 footgun this helper closes; AO.R.3 added bold/code/bullet parsing so dashboard panels stop rendering literal ** / ``- `` markers).

Return type:

str

Parameters:

text (str)

recon_gen.common.rich_text.markdown_inline(text)[source]

Single-line markdown with link handling but no <br/>.

Same transformations as markdown() for inline links and XML-escaping, but ANY whitespace run that contains a newline collapses to a single space. <br/> is never emitted. Use inside <li> (the QS XML parser rejects <br/> as an <li> child) or any other context where line breaks must not appear.

Trailing / leading whitespace is stripped — useful when the input came from a YAML | block scalar (which always ends in \n). Strip happens after link substitution so a link sitting at the end of the input stays attached to the next part rather than getting whitespace-eaten.

Return type:

str

Parameters:

text (str)

recon_gen.common.rich_text.subheading(text, color=None)[source]

Section subheading (20px).

Return type:

str

Parameters:
  • text (str)

  • color (str | None)

recon_gen.common.rich_text.text_box(*parts)[source]

Wrap parts in a <text-box> root.

Auto-pads the interior with leading + trailing <br/> so the rendered text doesn’t sit flush against the box’s top / bottom edges. SheetTextBox itself has no padding/margin fields in the AWS API — interior breathing room only comes via the rich-text grammar inside Content. Two <br/> per side matches what hand-authored QS UI text boxes emit when an editor hits Enter twice for spacing (a common pattern).

Pass already-<br/>-padded parts if you want even more space; the auto-pad is additive.

Return type:

str

Parameters:

parts (str)