Group 1: type token (uppercased). Group 2: whatever else sat on the
marker's own line — the custom title (Obsidian / Hugo syntax:
> [!NOTE] Custom Title); empty for a bare marker. Group 3: the rest
of the blockquote body, possibly starting with </p> (when the
marker line was a paragraph of its own) or with the inline body
content (when body text followed the marker via a soft break).
Group 2 stops at the line break, so a same-line title can never be
confused with a soft-broken body. It used to be \s*(.*?), which
swallowed the title into the first body paragraph — Custom Title
rendered as body text under the default "Note" heading. Obsidian's
optional fold marker ([!NOTE]+ / [!NOTE]-, group 4) is dropped,
unless foldable ([markdown] wikilinks) renders it as <details>.
CommonMark <scheme:…> autolink: its text is a URL, not prose.
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: > $$x$$ is block position too (the
div becomes an HTML block inside the blockquote).
BLOCKQUOTE_MARKERS_RE = /\A(?:>[ \t]?)+/
CODE_SPAN_TOKEN_RE = /\x00CS(\d{1,9})\x00/
Placeholder emitted by transform_outside_code_spans for a stashed
code span; the capture is the span's index.
--- 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 inline markdown (escaped).
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 <!--HWARO-FN:…--> line until postprocess,
and rewriting it there corrupts the data. Author-typed lookalikes
are neutralized to <!-- HWARO- (with a space) by
preprocess_footnotes before this prefix check can match them.
Occurrence count rides on the number field as NUM.OCC (e.g. 1.3).
The . separator can't appear in the legacy 3-field NUM: form, so a
3-field comment whose text starts with digits+colon is never misread
as a count.
--- Footnotes ---
Pre-processing: extract footnote definitions and replace references with placeholders
FOOTNOTE_REF_RE = /\[\^([^\]]+)\]/
HATTR_MARKER_RE = /<!--HATTR:([0-9a-f]+)-->/
--- Custom Attributes (F9) postprocess ---
Resolves <!--HATTR:HEXPAYLOAD--> markers (left by the preprocess
branches above) into real id/class/other attributes on the
heading tag or <img> tag they trail. Runs BEFORE footnotes (an
attribute block is only ever on a heading/image line, never inside
footnote marker comments) and AFTER heading_ids (so a heading that
got a <!--HID:...--> marker instead — the pure {#id} case — is
already resolved and simply won't match HATTR_MARKER_RE here).
--- Custom Attributes (F9) ---
Generalized {#id .class key=val} attribute blocks — headings and
inline images. See markdown_attributes.cr for the token grammar.
Deliberately broader than HEADING_ID_RE's brace group
([^{}]+ vs \#[\w][\w:-]*): this is what makes the two
regexes disjoint on ## H {#id} (HEADING_ID_RE wins) while still
catching ## H {#id .class} (falls through to this one, since
HEADING_ID_RE requires the braces to contain ONLY #id).
--- Custom Heading IDs ---
## My Heading {#custom-id} → ## My Heading <!--HID:custom-id-->
The marker survives Markd rendering and is converted to an id="..."
attribute in postprocess_heading_ids.
Restricting the id charset to [\w][\w:-]* keeps it valid as an
HTML id without further escaping (no quote, < or & can occur
in it). A leading digit is deliberately allowed: that's the HTML5
rule, and hwaro's own auto-slugs already produce digit-leading ids
like 1-create-a-project for ## 1. Create a project, so a
translated heading has to be able to pin that same anchor (#792).
CommonMark allows up to 3 leading spaces before an ATX heading, which
we capture and preserve so Markd still recognises the line as a heading.
\r? before $: CRLF content otherwise never matches and the id is
silently dropped.
CommonMark "type 6" 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.
Inline <code>…</code> 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. [^<]* keeps the match to a
flat element (generated spans never contain tags); the attribute
scan is quote-aware so a > inside a quoted value doesn't end
the opening tag early.
Raw inline HTML tags as Markd recognizes them (Markd::Rule::OPEN_TAG
and CLOSE_TAG: tag name, attributes with optional unquoted/quoted
values, /> or >), so text that merely starts with < —
<b ~~x~~ y> — stays Markdown. Possessive quantifiers give the
same matches without backtracking frames, so a tag with thousands
of attributes cannot exhaust the PCRE JIT stack.
{.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.
The label of  / ![label][ref], brackets excluded:
it becomes the alt attribute (plain text), so no extension may
inject markup into it. One level of nested brackets is allowed
(![Figure[^1]](f.png)); possessive, so a long label cannot
backtrack.
<img ...> opening portion (quote-aware, so a > inside an
attribute value like alt="Home > Docs" isn't mistaken for the
tag end), its closer (> or />, 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 <img> (e.g. a
<figure>…</figure>): the marker lands after the whole hook
output, not glued to the <img>, so a naive "marker immediately
follows the tag" 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 (</p>, </li>, table
cells, </hN>, …): 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-<hN> markup, leaving its HATTR marker unconsumed by the heading
pass). Hook wrappers (</figure>, </span>, </a>, </picture>, …)
are deliberately NOT excluded, so they still bind normally. The tag
scan is IMG_LAZY_REGEX's possessive form of (?:…)*?(\s*\/?>).
INLINE_MATH_RE = InlineMarkdown::INLINE_MATH_RE
INLINE_TAG_RE = /<(\/?)([a-zA-Z][\w-]*)[^>]*>/
Captures a blockquote whose first paragraph starts with [!TYPE].
Non-void tags that markd's inline renderer can open on a title line.
Balanced when every opener on the line is closed on the line: a
<strong>/<a>/<code>… opened here and closed on a later line
of the same paragraph means the author wrapped one inline element
across the soft break, not that they wrote a title.
A whole-line reference definition: [label]:, a destination
(<…> or a non-space run), an optional title after whitespace, and
nothing else. Group 1 ends where the destination starts. A line
with trailing text is a paragraph to Markd, not a definition.
LINK_DEST_TOKEN_RE = /\x00LD(\d{1,9})\x00/
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.
<script>/<style>/<textarea> bodies are text, not markup: the
HTML passes must not rewrite an "<img …>" or "<a href=…>" JS
string (their double-quoted attributes would end it).
$1 = a raw-text opener's name. <!-- alternates so the scan steps
over comments (a commented-out <!-- <script src="old.js"> -->
opens nothing), and so does a tag one of whose quoted values holds a
< (title="<textarea>"): the last branch stops at that value's
= and the scan resumes past the whole tag. Every other tag fails
that branch at its > without a match being built.
A whole opening tag; a quote opens a value only right after =.
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 (GFM) ---
~~text~~ → <del>text</del>. 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 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
(<li><input …) and loose (<li>\n<p><input …). Code blocks
are immune (their <input is entity-escaped).
--- Task Lists ---
Converts - [ ] and - [x] to checkbox HTML in list items. The marker
must be followed by whitespace or end the line (GFM), so a list item
opening with a link labelled x (- [x](url)) keeps its link.
One to four spaces may follow the list marker (- [x]); five
make the item's content an indented code block.
Safe mode keeps hwaro's own markup through markd's raw-HTML
stripping by marking it: every open tag and comment a pass
generates carries this per-process secret (<del MARK>,
<!--HWARO-MARK…), which author content cannot forge — it never
reaches any output. HighlightingRenderer keeps a marked construct
(and a close tag balancing one) and omits all other raw HTML;
untrust then drops the marks from the rendered HTML. Engine
comments keep their ENGINE_MARKER_PREFIX.
Derive an id-safe token from a footnote key. The key can contain ASCII
whitespace ([^my note]), which is invalid in an id/fragment, so
collapse runs of whitespace to a single - before HTML-escaping the
rest. Must be applied identically on the reference side and the
li/backref side so forward and backward anchors still match.
{…} attribute blocks on images: the attributes switch, or
wikilinks, whose sized ![[img.png|300]] embeds are emitted as
{width=300}. Heading blocks stay attributes-only.
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.
Yields every stretch of html outside raw-text bodies and joins the
results with those bodies kept verbatim. scripts: true yields each
<script> body too, as a stretch of its own: for a pass whose
rewrite is right for the HTML a script inserts
(el.innerHTML = '<a href="/x/">'), which still never matches
across a body's edge.
Post-processing: rewrite GitHub > [!TYPE] blockquotes as admonition divs.
Note: the lazy match against </blockquote> means a nested blockquote
inside the admonition will close the match early. Acceptable for v1 —
GitHub admonitions don't support nested blockquotes either.
Post-processing: convert footnote comments to HTML section.
flags.math renders $…$ spans in footnote bodies as math, their
internals untouched by emphasis/strikethrough; flags also threads
the F10 opt-in inline markup (ins/mark/sub/sup).
content through the block with its math spans (as the math pass
sees them) stashed out, then put back as written — for passes that
run before preprocess (wikilinks) and must not touch formulas.
The block also gets the spans' sources, for restore_math on any
piece it lifts out (a placeholder must never reach Markd).
Byte ranges of raw-text element bodies, from the end of the opening
tag up to the close tag, so the opener's own attributes stay markup.
An unterminated element ends the scan and an unterminated comment is
no comment; neither runs to the end. comments: true also lists
each comment (<!-- through -->): only the ones outside the
bodies, as a browser parses them ("<!--" in a script opens none),
including those after an unterminated element.