module

Hwaro::Content::Processors::MarkdownExtensions

Constants

ADMONITION_BLOCKQUOTE_RE = /<blockquote>\s*<p>\[!(NOTE|TIP|IMPORTANT|WARNING|CAUTION)\]\s*(.*?)<\/blockquote>/m

Captures a blockquote whose first paragraph starts with [!TYPE]. Group 1: type token (uppercased). Group 2: the rest of the blockquote body, possibly starting with &lt;/p&gt; (when the marker was on its own paragraph) or with the inline body content (when the marker shared a paragraph with body text via a soft break).

ADMONITION_TYPES = {"NOTE", "TIP", "IMPORTANT", "WARNING", "CAUTION"}

--- GitHub-style Admonitions --- Recognised types match GitHub's alert syntax.

ANY_ID_ATTR_PRESENT_RE = /(?<![\w-])id\s*=/i
BARE_MATH_LINE_RE = /\A\x00MATH\d+\x00\z/

A line whose sole content is one math placeholder — the standalone display-math case, where the emitted

starts at line start and is a real CommonMark HTML block. Leading blockquote markers are stripped before the check: &gt; $$x$$ is block position too (the div becomes an HTML block inside the blockquote).

BLOCKQUOTE_MARKERS_RE = /\A(?:>[ \t]?)+/
CONTAINER_CLOSE_RE = /\A {0,3}:{3,}\z/
CONTAINER_OPEN_RE = /\A {0,3}:{3,}([A-Za-z][\w-]*)[ \t]*(.*)\z/

--- Custom containers (opt-in) --- :::type Optional Title::: blocks (markdown-it/remark style), emitted with the admonition markup so site CSS is shared:

Title

<blank line — ends the type-6 HTML block, so the body is parsed as ordinary markdown, fences and task lists included> …body…

A bare :{3,} run closes the innermost open container, which gives natural nesting (::::outer / :::inner / ::: / ::::) with a plain counter. Unclosed containers auto-close at EOF (markdown-it behavior). Fence-aware: ::: lines inside code fences stay verbatim. The type token is class-safe by construction; the title is HTML-escaped plain text.

DISPLAY_MATH_RE = InlineMarkdown::DISPLAY_MATH_RE

--- Math ---

ENGINE_MARKER_PREFIX = "<!--HWARO-"

Engine-generated marker comments (footnote data blocks, shortcode placeholders) start with this prefix and must pass through the transforming passes verbatim: a footnote body containing ~~x~~ or $x$ lives inside a &lt;!--HWARO-FN:…--&gt; line until postprocess, and rewriting it there corrupts the data. Author-typed lookalikes are neutralized to &lt;!-- HWARO- (with a space) by preprocess_footnotes before this prefix check can match them.

EXISTING_ID_RE = /(?<![\w-])id\s*=\s*"[^"]*"/i
EXISTING_REL_RE = /(?<![\w-])rel\s*=\s*"([^"]*)"/i
EXISTING_TARGET_RE = /(?<![\w-])target\s*=/i

CommonMark &quot;type 6&quot; HTML-block start condition (common block tags, including the

/
/
markup hwaro itself generates). A line opening one of these starts a raw-HTML block that runs to the next blank line — Markd performs NO inline parsing there, so backslash escapes ship verbatim instead of collapsing.

HTML_CODE_SPAN_RE = /<code(?:\s(?:[^>"']|"[^"]*"|'[^']*')*)?>[^<]*<\/code>/

Inline &lt;code&gt;…&lt;/code&gt; HTML spans — generated by InlineMarkdown for table cells / definition bodies (where the original backticks are already consumed), or author-written raw HTML. Their content is code: the strikethrough/footnote/math passes must treat it as opaque, exactly like backtick spans. [^&lt;]* keeps the match to a flat element (generated spans never contain tags); the attribute scan is quote-aware so a &gt; inside a quoted value doesn't end the opening tag early.

IMAGE_ATTR_RE = /(!\[[^\]]*\]\([^)]*\))\{([^{}]+)\}/

![alt](url){.class key=val} — an attribute block immediately following an inline image's closing ). Matched inside transform_outside_code_spans so a literal example in a code span isn't rewritten.

IMG_HATTR_RE = /(<img\b(?:[^>"']|"[^"]*"|'[^']*')*?)(\s*\/?>)((?:(?!<img\b|<!--HATTR:|<\/(?:p|li|t[dh]|d[dt]|h[1-6])\b).)*?)<!--HATTR:([0-9a-f]+)-->/m

&lt;img ...&gt; opening portion (quote-aware, so a &gt; inside an attribute value like alt=&quot;Home &gt; Docs&quot; isn't mistaken for the tag end), its closer (&gt; or /&gt;, with any whitespace before it), an optional wrapper the image trails inside (group 3), and the marker comment this preprocess pass appended. The wrapper is what a render-image.html hook emits around the &lt;img&gt; (e.g. a &lt;figure&gt;…&lt;/figure&gt;): the marker lands after the whole hook output, not glued to the &lt;img&gt;, so a naive &quot;marker immediately follows the tag&quot; match would drop the attributes silently. The tempered gap stops at the next image or marker, so back-to-back attributed images each bind their own block, and the no-hook case (marker glued to the tag) keeps an empty group 3. It also stops at a closing block tag Markd wraps the image in (&lt;/p&gt;, &lt;/li&gt;, table cells, &lt;/hN&gt;, …): an image's own marker is always in the same block, so the gap never legitimately crosses one — this prevents a plain (marker-less) image from reaching forward and absorbing a later element's marker (e.g. a heading whose non-conformant hook emitted non-&lt;hN&gt; markup, leaving its HATTR marker unconsumed by the heading pass). Hook wrappers (&lt;/figure&gt;, &lt;/span&gt;, &lt;/a&gt;, &lt;/picture&gt;, …) are deliberately NOT excluded, so they still bind normally.

INLINE_MATH_RE = InlineMarkdown::INLINE_MATH_RE
MATH_BODY_MARKD_ACTIVE_RE = /[\\`*_\[\]\-."']/

Markd-active characters inside a math body rendered in normal inline context (see the branch comments in expand_math). Beyond emphasis/code/link chars, -, ., and both quotes are included so markd's opt-in smart punctuation can't rewrite --/.../ quotes inside formulas; when smart is off the backslash escapes collapse to the same characters, so output is byte-identical.

MATH_PLACEHOLDER_RE = /\x00MATH(\d+)\x00/
SINGLE_LINE_CODE_SPAN_RE = /`[^`\n]+`/

Code-span pattern confined to one line, for stashing inside multi-line chunks: a stray lone backtick in one paragraph must not absorb text from another.

STRIKETHROUGH_CODE_RE = /`[^`]+`/
STRIKETHROUGH_RE = InlineMarkdown::INLINE_STRIKETHROUGH_RE

--- Strikethrough (GFM) --- ~~text~~&lt;del&gt;text&lt;/del&gt;. Markd doesn't ship a GFM strikethrough parser, so we apply this pre-Markd. The walk is fence-aware so examples inside fenced code blocks (``` / ~~~) render verbatim, and inline `code` runs on the same line are skipped via a placeholder pass so e.g. `~~not strike~~` stays as code.

When math is also enabled, preprocess stashes $…$/$$…$$ spans into opaque placeholders before this pass runs, so $~~x~~$ reaches KaTeX verbatim instead of being rewritten here.

TASK_LIST_ITEM_RE = /<li>(\n?(?:<p>)?)<input type="checkbox"( checked)? disabled>/

--- Task list classes (GFM markup, opt-in) --- preprocess_task_lists runs before Markd, when the

  • doesn't exist yet — so the GFM classes are added here, on the rendered HTML. Matches both list shapes markd emits: tight (&lt;li&gt;&lt;input …) and loose (&lt;li&gt;\n&lt;p&gt;&lt;input …). Code blocks are immune (their &lt;input is entity-escaped).

  • TASK_LIST_RE = /^(\s*[-*+]\s)\[([ xX])\]/m

    --- Task Lists --- Converts - [ ] and - [x] to checkbox HTML in list items

    Instance methods

    inline_flags(config : Models::MarkdownConfig) : InlineMarkdown::Flags

    Builds the shared InlineMarkdown::Flags for a markdown config — math plus the F10 opt-in inline markup — so table cells, definition lists, and footnote bodies all see the same set of enabled transforms as the main per-line pass above.

    Source
    postprocess(html : String, config : Models::MarkdownConfig) : String

    Post-process HTML after Markd rendering

    Source
    postprocess_admonitions(html : String) : String

    Post-processing: rewrite GitHub &gt; [!TYPE] blockquotes as admonition divs. Note: the lazy match against &lt;/blockquote&gt; means a nested blockquote inside the admonition will close the match early. Acceptable for v1 — GitHub admonitions don't support nested blockquotes either.

    Source
    postprocess_attributes(html : String) : String
    Source
    postprocess_footnotes(html : String, *, math : Bool = false) : String

    Post-processing: convert footnote comments to HTML section. math: true keeps $…$ spans in footnote bodies untransformed (math is not rendered in footnotes, but its internals must not be rewritten by emphasis/strikethrough either). Pre-F10 signature — delegates to the flags overload (existing callers/specs keep calling this one directly).

    Source
    postprocess_footnotes(html : String, *, flags : InlineMarkdown::Flags) : String

    flags also threads the F10 opt-in inline markup (ins/mark/sub/ sup) into footnote bodies, alongside the math flag.

    Source
    postprocess_heading_ids(html : String) : String
    Source
    postprocess_mermaid(html : String) : String

    --- Mermaid --- Post-processing: convert mermaid code blocks to div elements

    Source
    postprocess_task_list_classes(html : String) : String
    Source
    preprocess(content : String, config : Models::MarkdownConfig) : String

    Pre-process markdown content before Markd parsing

    Source
    preprocess_containers(content : String) : String
    Source
    preprocess_definition_lists(content : String, *, math : Bool = false) : String

    --- Definition Lists --- Converts Term\n: Definition syntax to

    HTML. Fence-aware: Term / : def lines shown inside a ```/~~~ example stay verbatim instead of becoming
    markup inside the code block. math: true keeps $…$ spans in
    /
    bodies untransformed for the later math pass (see InlineMarkdown.render). Pre-F10 signature — delegates to the flags overload (existing callers/specs keep calling this one directly).

    Source
    preprocess_definition_lists(content : String, *, flags : InlineMarkdown::Flags) : String

    flags also threads the F10 opt-in inline markup (ins/mark/sub/ sup) into term/definition bodies, alongside the math flag.

    Source
    preprocess_footnotes(content : String) : String
    Source
    preprocess_heading_ids(content : String, *, safe : Bool = false) : String

    Walk lines and apply the heading-id transform only outside fenced code blocks, so ## ... {#id} shown inside a ``` example in the docs renders verbatim.

    Under Markd's safe mode, inline HTML comments are replaced with the placeholder &lt;!-- raw HTML omitted --&gt;, which would both lose the id and leak that placeholder into the heading text. In that case we strip the {#id} syntax silently — custom heading IDs are not supported alongside markdown.safe = true.

    Source
    preprocess_math(content : String) : String

    One-shot math transform (stash + immediate expand). preprocess itself uses the two phases separately so the combined pass runs in between — see the ordering comment there.

    Source
    preprocess_strikethrough(content : String) : String
    Source
    preprocess_task_lists(content : String) : String
    Source

    Nested types