module

Hwaro::Content::Processors::InlineMarkdown

Constants

AUTOLINK_RE = /<([a-zA-Z][a-zA-Z0-9+.\-]{1,31}:(?:[^\s&]|&(?!lt;|gt;))*+)>/

A CommonMark URI autolink (<https://…>) as HTML.escape left it.

CODESPAN_TOKEN_RE = /\x00CODESPAN(\d{1,9})\x00/
DISPLAY_MATH_RE = /\$\$((?:(?!\n[ \t\r]*\n).)*?)\$\$/m

Math span patterns — canonical home for the whole pipeline (MarkdownExtensions aliases these, mirroring INLINE_STRIKETHROUGH_RE).

Display math must not cross a blank line (the tempered dot refuses to consume a newline that starts one, whitespace-only lines included): a stray unmatched $$ would otherwise pair with a legitimate $$ several paragraphs later and swallow all the prose in between. Blank lines are invalid inside LaTeX display math anyway, so no real formula is lost.

Inline math admits backslash escapes in the body ($x = \$5$) and requires an unescaped, non-space-preceded closer. A body ending in a literal \ won't close — meaningless in LaTeX at the end of a formula.

ESCAPE_RE = /\\(&(?:amp|lt|gt|quot|#39);|[!#$%()*+,\-.\/:;=?@\[\\\]^_`{|}~])/

A backslash before ASCII punctuation (shown HTML-escaped, so \" is \&quot;) is a CommonMark escape: the character is literal.

ESCAPE_TOKEN_RE = /\x00ESC(\d{1,9})\x00/
ESCAPED_ENTITY_RE = /&amp;(#[0-9]{1,7}|#[xX][0-9a-fA-F]{1,6}|[a-zA-Z][a-zA-Z0-9]{1,31});/

An entity or numeric character reference as HTML.escape left it.

IMAGE_SIZE_RE = /(<img\b[^>]*)>\{width=(\d+)(?: height=(\d+))?\}/

{width=300} / {width=300 height=200} right after an image is its size, the attribute block ![[pic.png|300]] is rewritten to.

INLINE_BOLD_ASTERISK_RE = /\*\*(?=\S)(.+?)(?<=\S)\*\*/

Flanking guards ((?=\S) … (?<=\S)): a delimiter run that touches whitespace on the inside must NOT open/close emphasis, so literal 2 * 3 and 4 * 5 (arithmetic in a table cell or footnote) is left alone instead of becoming 2 <em> 3 and 4 </em> 5. This approximates CommonMark's left/right-flanking rule that the body markd uses.

INLINE_BOLD_UNDERSCORE_RE = /__(?=\S)(.+?)(?<=\S)__/
INLINE_CODE_SPAN_RE = /(?<!`)(`++)([\s\S]+?)(?<!`)\1(?!`)/

CommonMark code spans: a run of N backticks opens, and the next run of EXACTLY N backticks closes. The single-backtick-only form this replaced could not see `tick` or a`b at all — it matched the inner backticks instead, shredding the span into stray <code> tags and literal delimiters inside table cells, footnote bodies, and definition lists (the three callers of render).

The opening run is POSSESSIVE (`++) and BOTH delimiters are fenced by (?<!)/(?!) so each is a whole run: without those, \1 would happily match two of a three-backtick run and pair mismatched delimiters. The opener's lookbehind matters twice over:

  • correctness — without it, an unmatched longer run bleeds into a later shorter one: a became <code> </code>a (the scan restarted on the SECOND backtick of the), where CommonMark gives a ``;
  • cost — without it, every backtick of a run is a fresh start position that possessively re-consumes the rest of the run, so an unclosed run of N backticks cost O(N²): a 200 KB cell of backticks did not finish in ten minutes. With it, positions inside a run are rejected in O(1) and the scan is linear again. [\s\S] (not .) because a footnote or definition body may still carry a newline at this point.
INLINE_IMAGE_OPEN_RE = /!\[([^\]]*)\]\(/

Link/image openers up to and including (; the destination and optional title are read by scan_link_tail.

INLINE_INS_RE = /\+\+(?=\S)(.+?)(?<=\S)\+\+/

Opt-in inline markup (F10) — all gated behind their own [markdown] flags (see Flags), so with every flag off these patterns are never even consulted.

++ins++: same flanking-guard shape as strikethrough. A lone ++ (as in C++) never gets a second delimiter to pair with, so it's left alone without any special-casing.

INLINE_ITALIC_ASTERISK_RE = /(?<!\*)\*(?=[^\s*])(.+?)(?<=[^\s*])\*(?!\*)/

The italic delimiter must be a LONE */_ (not part of a **/__ run) — (?<!\*)…(?!\*) and [^\s*] neighbours — otherwise a spaced 2 ** 3 and 4 ** 5 (which the bold regex correctly declines) would be re-matched across the two ** runs into <em>* 3 and 4 *</em>.

INLINE_ITALIC_UNDERSCORE_RE = /(?<![a-zA-Z0-9_])_(?=[^\s_])(.+?)(?<=[^\s_])_(?![a-zA-Z0-9_])/
INLINE_LINK_OPEN_RE = /\[([^\]]+)\]\(/
INLINE_MARK_RE = /(?<!=)==(?=[^\s=])(.+?)(?<=[^\s=])==(?!=)/

==mark==: the (?<!=)/(?!=) outer guards and the [^\s=] inner guards keep a run of = (a setext heading underline, a ==== divider) from ever matching — there's no non-= character for the inner lookaround to anchor on.

INLINE_MATH_RE = /(?<![\\$])\$(?!\s)((?:[^\n$\\]|\\[^\n])+?)(?<![\s\\])\$(?!\d)/
INLINE_STRIKETHROUGH_RE = /(?<!(?<!\\)\\)~~(?=\S)(.+?)(?<=\S)(?<!(?<!\\)\\)~~/

A ~~ right after an unescaped backslash is literal (\~~not\~~): rewriting it would leave \<del>, which Markd escapes into visible &lt;del&gt; text. \\~~x~~ (escaped backslash) still strikes.

INLINE_SUB_RE = /(?<!~)~([^~\s]+)~(?!~)/

~sub~: single tilde, deliberately disjoint from the double-tilde strikethrough delimiter (which always runs first and consumes any ~~...~~ pair before this pattern gets a chance to see it).

INLINE_SUP_RE = /(?<![\^\[])\^([^\^\s]+)\^(?!\^)/

^sup^: the (?<![\^\[]) guard specifically excludes a ^ that immediately follows [ — i.e. a footnote reference's [^key] — so sup and footnotes can both be enabled without sup mangling a footnote marker before the footnotes pass gets to it.

LINK_TAG_TOKEN_RE = /\x00IMTAG(\d{1,9})\x00/
MATHSPAN_TOKEN_RE = /\x00MATHSPAN(\d{1,9})\x00/
MAX_DEST_PAREN_DEPTH = 32

CommonMark allows 32 levels of balanced parentheses in a destination.

MAX_TITLE_BYTES = 2000

ponytail: a title longer than this is not read (keeps a run of unterminated [a](x " openers linear); such a link keeps the first-) fallback.

SCPH_TOKEN_RE = /\x00SCPH(\d{1,9})\x00/
SHORTCODE_PLACEHOLDER_RE = /<!--HWARO-SHORTCODE-PLACEHOLDER-\d+-->/

Placeholder comments left by Core::Build::ShortcodeProcessor for already-rendered shortcodes (canonical home here, next to the other inline patterns; the shortcode processor aliases it and emits the matching text). They must ride through render untouched: the HTML.escape at the top would otherwise turn them into &lt;!--…--&gt;, which the post-Markdown replacement pass cannot find — leaking the escaped comment into table cells, definition bodies, and footnotes.

UNSAFE_DATA_PROTOCOL_RE = /^\s*data:image\/(?:png|gif|jpeg|webp)/i
UNSAFE_PROTOCOL_RE = /^\s*(javascript|vbscript|file|data):/i

Schemes that must be blocked in <a href="…"> / <img src="…"> even when Markd's safe option is off. data: is allowed only for image MIME types (matching Markd's own UNSAFE_DATA_PROTOCOL).

Instance methods

render(text : String, *, flags : Flags = Flags.new) : String

Render a small inline-markdown subset over already-HTML-escaped or raw text. Code spans are extracted first so their content survives the other passes verbatim.

With flags.math, $…$/$$…$$ spans are stashed too and restored UNtransformed: emphasis/strikethrough/link passes must not rewrite formula internals ($~~x~~$, $f([x])(y)$), and the math preprocess wraps the still-raw span afterwards.

flags also controls the F10 opt-in inline markup (ins/mark/sub/sup).

Source
safe_url?(url : String) : Bool

Returns true for URLs we're willing to emit in a generated href/src. Reject javascript:, vbscript:, file:, and non-image data: URIs. Percent-decode first so encodings like java%73cript: don't slip past. Also strip ASCII control/whitespace bytes (NUL–space and DEL) anywhere in the decoded value: browsers ignore tabs/newlines/NULs inside a URL scheme, so java%09script: would otherwise execute as javascript:. The unsafe regexes are anchored at ^, so stripping these from the whole string only affects scheme detection, never legitimate URLs.

Source

Nested types