History log of /dokuwiki/inc/Parsing/ModeRegistry.php (Results 1 – 25 of 32)
Revision Date Author Comments
# 2d05a06d 08-Jul-2026 Andreas Gohr <gohr@cosmocode.de>

fix(info): read parser modes from the registry, not the deprecated global

The info plugin's ~~INFO:syntaxtypes~~ read the $PARSER_MODES global
directly. Since the mode taxonomy moved into ModeRegist

fix(info): read parser modes from the registry, not the deprecated global

The info plugin's ~~INFO:syntaxtypes~~ read the $PARSER_MODES global
directly. Since the mode taxonomy moved into ModeRegistry (c8dd1b9d2),
that global is only populated as a side effect of a parse, so on a warm
instruction cache with no parse in the request it is unset and the
syntax-types table renders empty. ~~INFO:syntaxmodes~~ used the
deprecated p_get_parsermodes(), logging a deprecation on every render.

Both now build a ModeRegistry and read the categories and modes from it
directly, independent of parse or cache state.

show more ...


# a7e10216 25-Jun-2026 splitbrain <86426+splitbrain@users.noreply.github.com>

�� Rector and PHPCS fixes


# 47a02a10 04-Jun-2026 Andreas Gohr <gohr@cosmocode.de>

Parsing: make parse syntax a per-parse value, drop ModeInterface

The active parse's syntax flavour is a per-parse question, not process-
global state: within a single request a plugin can render bun

Parsing: make parse syntax a per-parse value, drop ModeInterface

The active parse's syntax flavour is a per-parse question, not process-
global state: within a single request a plugin can render bundled
DokuWiki-syntax text inside an otherwise-Markdown page. Yet ModeRegistry
was a singleton that read $conf['syntax'] and the $PARSER_MODES global,
and every mode reached it through ModeRegistry::getInstance() — so the
flavour lived in shared mutable state that two parses in one request
would fight over.

Make the registry a short-lived value instead:

- ModeRegistry is constructed once per parse with an explicit $syntax
and injected into Parser, Handler and every mode. getSyntax() /
isDwPreferred() / isMdPreferred() consult $this->syntax; the
DOKU_UNITTEST-gated mode-list cache hack is gone (each registry is
fresh, nothing to invalidate).
- p_get_instructions() is now the single place in the pipeline where
$conf['syntax'] is read; from there the flavour travels as a
parameter. No code under inc/Parsing/ reads $conf['syntax'] directly
anymore — the five syntax-reading modes (Preformatted, GfmHr,
GfmEscape, Externallink, GfmQuote) route through $this->registry.

Keep the two concepts apart, as documented in the ModeRegistry and
AbstractMode docblocks: the user's configured *preference* stays in
$conf['syntax'] for UI code (toolbar, settings), while the active
parse's syntax is a parameter carried by the registry.

$PARSER_MODES is demoted to a deprecated, read-only mirror, published
during loadPluginModes() — third-party syntax plugins (columnlist,
alphalist2, phpwikify, skipentity) and the bundled info plugin read the
global directly, often from their constructors, so the taxonomy must
stay visible there. No core code reads the mirror.

Fold ModeInterface into AbstractMode while here: getSort()/handle() are
abstract, the connect callbacks carry defaults, and the public $Lexer
"FIXME should be done by setter" becomes setLexer()/getLexer() injected
by Parser::addMode() alongside the registry. Nested-content resolution
moves to the allowedCategories()/filterAllowedModes() hooks, resolved
once when the registry is attached.

Tests build their own parser/registry through ParserTestBase::setSyntax()
instead of mutating $conf and calling the removed ModeRegistry::reset().

show more ...


# e7dae73b 12-May-2026 Andreas Gohr <andi@splitbrain.org>

fix: apply rector and code sniffer fixes


# 06ab3bb2 12-May-2026 Andreas Gohr <andi@splitbrain.org>

Merge branch 'master' into gfm

* master: (176 commits)
Delete inc/Search/concept.txt
�� Rector and PHPCS fixes
�� Update deleted files
updated phpseclib dependency
use new ModeRegistry con

Merge branch 'master' into gfm

* master: (176 commits)
Delete inc/Search/concept.txt
�� Rector and PHPCS fixes
�� Update deleted files
updated phpseclib dependency
use new ModeRegistry constant in info plugin
keep historic typo in value but not in constant
SearchIndex: fix comment position
�� Rector and PHPCS fixes
Translation update (tr)
Translation update (fr)
refactor(changelog): persist external-edit detection on first read
fix(infoutils): escape git log arguments for Windows compatibility
fix(preview): remove deprecated X-XSS-Protection header
fix: don't move the editor textarea into the toolbar
fix EXIF-rotated images shown cropped in previews, closes #4482
fix: avoid picker close/reopen race on toggle button click
subscriptions: include diff link in plain-text list mails too
Translation update (ru)
Translation update (pl)
Translation update (pt-br)
...

# Conflicts:
# inc/Parsing/ParserMode/Quote.php

show more ...


# 56c730b5 06-May-2026 Andreas Gohr <andi@splitbrain.org>

keep historic typo in value but not in constant

We need to keep the historic typo in the value ("substition"), but there
is no reason to keep it in the constant.


# f9d3b7bd 05-May-2026 Andreas Gohr <andi@splitbrain.org>

Externallink: add per-scheme angle-bracket autolinks for MD syntax

Adds CommonMark §6.5 <URL> autolinks to Externallink, gated to
md/md+dw/dw+md syntax via ModeRegistry::isMdPreferred(). Per-scheme

Externallink: add per-scheme angle-bracket autolinks for MD syntax

Adds CommonMark §6.5 <URL> autolinks to Externallink, gated to
md/md+dw/dw+md syntax via ModeRegistry::isMdPreferred(). Per-scheme
patterns share the existing conf/scheme.conf allow-list so unknown
schemes fall through to literal cdata instead of being silently
dropped by the renderer. Internal whitespace inside the brackets
disqualifies the autolink and the whole envelope is emitted as
cdata to keep the bare-URL detector off the URL.

LinksTest gains 5 cases covering success, internal-whitespace and
leading-whitespace disqualification, unregistered scheme fallthrough,
and the dw-only no-op path. SpecCompatRenderer URL encoder is updated
to match cmark-gfm's HREF_SAFE table (square brackets and a few other
characters move from safe to encoded). skip.php loses the obsolete
#356 entry and gains #605/#606/#607/#609 explaining the unregistered-
scheme cases that the per-scheme regex naturally rejects.

show more ...


# d2085866 04-May-2026 Andreas Gohr <andi@splitbrain.org>

extend GfmNumericEntity to HTML5 named entities, rename to GfmHtmlEntity

Numeric refs are still decoded explicitly: PHP's html_entity_decode
returns the input unchanged for U+0000, surrogates, U+10F

extend GfmNumericEntity to HTML5 named entities, rename to GfmHtmlEntity

Numeric refs are still decoded explicitly: PHP's html_entity_decode
returns the input unchanged for U+0000, surrogates, U+10FFFF, and
BMP noncharacters where CommonMark requires U+FFFD or the literal
codepoint. Named refs delegate to html_entity_decode with ENT_HTML5,
which carries the full HTML5 named-entity table (including multi-
codepoint decodes like &ngE; -> U+2267 + U+0338).

Unknown names stay literal: the original &xxx; passes through as
cdata and the renderer's &-escaping turns it into &amp;xxx;.

show more ...


# 150dc5f2 04-May-2026 Andreas Gohr <andi@splitbrain.org>

add GfmNumericEntity for CommonMark numeric character references

Decodes &#nnn; (decimal, 1-7 digits) and &#xhhh; / &#Xhhh; (hex,
1-6 digits) to the corresponding Unicode codepoint, emitted as
plain

add GfmNumericEntity for CommonMark numeric character references

Decodes &#nnn; (decimal, 1-7 digits) and &#xhhh; / &#Xhhh; (hex,
1-6 digits) to the corresponding Unicode codepoint, emitted as
plain cdata. Codepoint 0, codepoints above U+10FFFF, and the
surrogate range U+D800..U+DFFF map to U+FFFD per the spec.

Distinct from the typography Entity mode, which is renderer-side
configurable via entities.conf. Numeric refs are not configurable
so decoding happens at parse time and the renderer needs no
changes.

Lexer leftmost-match consumes the run before any structural
pattern, so &#42;foo&#42; renders as literal *foo* and &#42; foo
does not start a list - matching the spec rule that numeric refs
cannot stand in for structural markers.

show more ...


# 13a62f81 04-May-2026 Andreas Gohr <andi@splitbrain.org>

rename syntax flavors 'dokuwiki' / 'markdown' to 'dw' / 'md'

Symmetry with the existing 'dw+md' / 'md+dw' setting values.


# c4bcbc2e 04-May-2026 Andreas Gohr <andi@splitbrain.org>

add GfmLinebreak for GFM hard line breaks

Two-or-more trailing spaces, or a single backslash, immediately before
a non-final newline render as a `<br/>`. Both delimiter forms share a
single SUBSTITI

add GfmLinebreak for GFM hard line breaks

Two-or-more trailing spaces, or a single backslash, immediately before
a non-final newline render as a `<br/>`. Both delimiter forms share a
single SUBSTITION mode at sort 140, loaded under any MD-active syntax
(markdown, dw+md, md+dw); pure dokuwiki is unaffected.

Reuses the existing `linebreak` handler call and renderer; no new
instructions or renderer changes. SpecCompatRenderer overrides
linebreak() to emit the spec's `<br />` shape. Examples 662, 663
(line break inside a raw HTML tag) are skipped — raw HTML is not
passed through by default.

show more ...


# 3e6baeff 30-Apr-2026 Andreas Gohr <andi@splitbrain.org>

replace DW Hr with unified GfmHr

Single mode covers both DokuWiki (4+ dashes) and GFM (3+ of -/*/_)
horizontal rules; pattern self-narrows on $conf['syntax']. Always
loaded across all four syntax se

replace DW Hr with unified GfmHr

Single mode covers both DokuWiki (4+ dashes) and GFM (3+ of -/*/_)
horizontal rules; pattern self-narrows on $conf['syntax']. Always
loaded across all four syntax settings, mirroring the GfmQuote
replacement pattern. Same `hr` handler call so renderers and the
call API are unchanged.

Drops DW's old [ \t]* leading-whitespace tolerance — inert in
practice past 0-1 spaces (Preformatted at sort 20 intercepts
everything ≥ 2 spaces or any tab).

Spec examples 13, 20, 26-28, 224 turn green; 17, 21-24, 29, 30, 31
go to skip.php as deliberate non-implementations (whitespace
tolerance and list-precedence cases).

show more ...


# 309a0852 30-Apr-2026 Andreas Gohr <andi@splitbrain.org>

replace DW Quote with unified GfmQuote

GfmQuote covers blockquote parsing for both DokuWiki and GFM dialects
in a single mode. Same quote_open/quote_close handler instructions; a
DW-preferred post-p

replace DW Quote with unified GfmQuote

GfmQuote covers blockquote parsing for both DokuWiki and GFM dialects
in a single mode. Same quote_open/quote_close handler instructions; a
DW-preferred post-pass flattens sub-parsed paragraph wrapping into
linebreak calls so existing pages keep their <br/>-between-lines
rendering. MD-preferred keeps the <p>-wrapped spec shape.

Block content (lists, fenced code, tables) inside `>` quotes now
renders, since the body is sub-parsed. Headers stay excluded
(BASEONLY) — TOC and section-edit anchors don't compose with
<blockquote>, same rationale as GfmListblock.

Convert ModeRegistry's sub-parser cache into an acquire/release pool
to support same-key re-entrancy: a list inside a quote re-enters
gfm_quote during the list-item sub-parse, and the inner call needs
its own parser instance even though the exclusion key matches.
GfmListblock is updated to use the new acquire/release primitives.

show more ...


# 74031e46 28-Apr-2026 Andreas Gohr <andi@splitbrain.org>

add GfmEscape for GFM backslash escapes

Implements GFM §6.1 backslash-escape handling. GfmEscape is a sort-5
inline mode in CATEGORY_SUBSTITION that claims `\X` for any escapable
ASCII punctuation c

add GfmEscape for GFM backslash escapes

Implements GFM §6.1 backslash-escape handling. GfmEscape is a sort-5
inline mode in CATEGORY_SUBSTITION that claims `\X` for any escapable
ASCII punctuation char before competing delimiters can match. The
shared character class lives on Helpers\Escape so the lexer pattern
and the post-hoc unescape stay in lockstep.

Whole-span captures (GfmCode info string, GfmLink label/URL) bypass
the lexer; those modes call Escape::unescapeBackslashes() on the
relevant slot. GfmLink skips the unescape when the URL classifies as
a windowssharelink so the leading \\host survives intact.

GfmTable cells get a separate per-cell `\|` to `|` pass in the
rewriter to honour the tables-extension rule that pipes always
unescape, even inside code spans where standard §6.1 escapes don't
fire.

show more ...


# 3dabe4e0 28-Apr-2026 Andreas Gohr <andi@splitbrain.org>

add GfmTable for GFM tables

Implements the GFM pipe-table extension as a CONTAINER mode at sort 55,
one below DW Table at 60. A lookahead-validated entry pattern asserts a
header line plus a `:?-+:?

add GfmTable for GFM tables

Implements the GFM pipe-table extension as a CONTAINER mode at sort 55,
one below DW Table at 60. A lookahead-validated entry pattern asserts a
header line plus a `:?-+:?` delimiter row before consuming any input, so
non-table paragraphs containing pipes flow through unchanged. Cells are
inline-only per spec.

Handler\GfmTable rewrites the flat token stream into the canonical
table_open / tablethead_* / tabletbody_* / table_close sequence, deriving
per-column alignment from the delimiter row, padding short body rows
(spec 202), truncating long ones (spec 204), and falling back to a single
cdata when the column count mismatches (spec 203).

`tabletbody_open` / `tabletbody_close` are emitted for the first time;
they are part of the base renderer API but DW Table never used them.
Added to Block's blockOpen / blockClose lists alongside `tabletfoot_*`
for symmetry. SpecCompatRenderer gains minimal table-element overrides
so spec roundtrip output matches GFM's `<table><thead><tr><th>` shape
without DW's wrapper div, row/col counter classes, or align-as-class.

show more ...


# 685560eb 28-Apr-2026 Andreas Gohr <andi@splitbrain.org>

add GfmListblock for GFM lists

GfmListblock captures an entire list block atomically with one
addSpecialPattern match, then walks the captured text in handle()
grouping lines into items. Each item's

add GfmListblock for GFM lists

GfmListblock captures an entire list block atomically with one
addSpecialPattern match, then walks the captured text in handle()
grouping lines into items. Each item's body is dedented to its
content column and parsed by ModeRegistry::getSubParser() so
block content (paragraphs, fenced code, blockquotes, plugin
blocks) works inside items uniformly. Sub-parsed calls are wrapped
in a Nest call before they reach the outer handler, matching the
Footnote pattern: the main handler's Block rewriter treats nest
as opaque and the renderer base class unwraps it transparently,
so multi-paragraph items don't get double-wrapped in <p>.

Marker syntax: -, *, + (unordered) or 1-9 digits followed by
. or ) (ordered). Indentation is a 2-space-multiple step starting
at 0; depth = (indent / 2) + 1, odd indents round down, tabs become
two spaces. The first ordered item's number drives the start
attribute on <ol> via the listo_open $start parameter.

GfmLists subclasses AbstractListsRewriter with the GFM marker
parser; the state machine on the base class is shared with DW Lists.

GfmListblock loads only when $conf['syntax'] is markdown or md+dw.
Under those settings the DW Listblock is suppressed because the two
list models conflict — DW's mandatory 2-space indent rule vs GFM's
zero-indent top-level rule, and -/*/+ markers shared. Plugins that
relied on Listblock loading under md+dw will see it absent there.

Sub-parser exclusion set: CATEGORY_BASEONLY (no Header inside list
items) and gfm_listblock itself (defensive guard against re-entry
on pathological inputs; nested lists are handled by the outer
pattern, not by re-entry).

Tests cover marker variants, ordered start numbers, nested lists at
two and three levels, inline formatting inside items, marker-
character switches keeping one list, type switches splitting the
list, fenced code inside items, multi-paragraph (loose) items, and
two regressions on blank-line tolerance inside the captured block.
SpecCompatRenderer learns to render the list call sequence, and
spec.txt tests for digit/marker-width/lazy-continuation behavior
that GfmListblock deliberately doesn't implement are documented in
gfm-spec/skip.php with the per-bucket reasons (A-F).

Drops two now-obsolete entries from skip.php (image escapes that
land via earlier GfmLink/GfmMedia work) and inlines the Setext
explanation that previously pointed at SPEC.md. Replaces the
SPEC.md reference in GfmEmphasisTest with the inline reason.

show more ...


# 9172eccf 28-Apr-2026 Andreas Gohr <andi@splitbrain.org>

add sub-parser support to Handler / Parser / ModeRegistry

A block mode that wants to parse the body of one of its captured
matches needs a second Parser instance configured with the active
modes min

add sub-parser support to Handler / Parser / ModeRegistry

A block mode that wants to parse the body of one of its captured
matches needs a second Parser instance configured with the active
modes minus whatever would re-enter the outer mode. Doing this by
hand is verbose and easy to get wrong — modes hold a $Lexer slot
that addMode() overwrites, so the same mode object can't be shared
between the main parser and a sub-parser.

Three small additions:

Handler::reset() — clears calls, status, currentModeName, and
installs a fresh CallWriter. Lets one Handler instance be parsed
against repeatedly without state bleed.

Parser::getHandler() — accessor; sub-parser callers need it to
reach the handler for reset() and for harvesting the produced
call list.

ModeRegistry::getSubParser($excludeCategories, $excludeModes) —
returns a cached Parser preconfigured with every active mode
except those excluded. Mode objects are cloned before being
attached so connectTo()'s assignment to $Lexer does not clobber
the main parser's references. Cache key is the exclusion-set;
default exclusion is CATEGORY_BASEONLY (no Header inside the
sub-parsed content).

Tests cover Handler::reset's full clear, sub-parser caching,
default and custom exclusions, registry-reset propagation, and
the clone-not-share invariant for $Lexer.

show more ...


# 96d096f1 27-Apr-2026 Andreas Gohr <andi@splitbrain.org>

remove getLineStartMarkers registry — sort order already wins

Preformatted's entry pattern carried a `(?![\*\-])` negative
lookahead to defer to list modes on indented bullet lines.
0cecf9d50 (2005,

remove getLineStartMarkers registry — sort order already wins

Preformatted's entry pattern carried a `(?![\*\-])` negative
lookahead to defer to list modes on indented bullet lines.
0cecf9d50 (2005, "new parser added") introduced it hardcoded;
7958e6980 (2026, "decouple hardcoded mode names in Eol and
Preformatted") refactored that hardcoded knowledge into
register/getLineStartMarkers on ModeRegistry so each list mode
owned its marker chars. Both preserved the behavior verbatim;
neither documented why it was needed.

Tracing the lexer, it isn't. ParallelRegex merges all entry
patterns into one PCRE expression; PCRE returns the leftmost
match and breaks ties on expression order. Modes are added in
sort order via ModeRegistry::getModes(), so Listblock (sort 10)
always precedes Preformatted (sort 20) and wins the tie on
" - foo" without any lookahead. The only test that caught a
difference was testPreformattedList, which happened to register
modes in non-canonical order - that was a test bug.

This patch drops the lookahead in Preformatted::connectTo, the
registerLineStartMarkers call in Listblock::preConnect, the
register/getLineStartMarkers methods on ModeRegistry, and the
three registry-API unit tests. testPreformattedList now
registers Listblock before Preformatted.

show more ...


# 781f5c71 23-Apr-2026 Andreas Gohr <andi@splitbrain.org>

gate monospace, unformatted, file on DokuWiki syntax

These DokuWiki specific modes should only be loaded when DokuWiki syntax
is still wanted, not in Markdown-only mode.
Expands the ModeRegistryTest

gate monospace, unformatted, file on DokuWiki syntax

These DokuWiki specific modes should only be loaded when DokuWiki syntax
is still wanted, not in Markdown-only mode.
Expands the ModeRegistryTest data provider to cover the full always-loaded
and DW-always sets.

show more ...


# b1c59bed 23-Apr-2026 Andreas Gohr <andi@splitbrain.org>

add GfmCode / GfmFile for fenced code blocks

GfmCode (backticks) emits the `code` handler instruction; GfmFile
(tildes) emits `file`. Column-0 fences only, no length pairing
between opener and close

add GfmCode / GfmFile for fenced code blocks

GfmCode (backticks) emits the `code` handler instruction; GfmFile
(tildes) emits `file`. Column-0 fences only, no length pairing
between opener and closer, and unclosed fences stay literal —
matching DokuWiki's `<code>` tag convention. The info string accepts
DW's full attribute vocabulary (language, filename, [options])
through a new shared `Helpers::parseCodeAttributes` that `Code`
also uses, with `html` aliased to `html4strict` and `-` meaning "no
language".

Preformatted's indent threshold is now preference-gated: 2 spaces
in DW-preferred settings, 4 spaces in MD-preferred, matching GFM's
indented code block rule. A single tab is a trigger in both.

show more ...


# 3440a8c0 22-Apr-2026 Andreas Gohr <gohr@cosmocode.de>

add GfmMedia and extend GfmLink with image-as-label form

- New GfmMedia parses `![alt](url)` with the full DokuWiki media-parameter
vocabulary in the URL slot (?100x200, ?right, ?nolink, ?recache,

add GfmMedia and extend GfmLink with image-as-label form

- New GfmMedia parses `![alt](url)` with the full DokuWiki media-parameter
vocabulary in the URL slot (?100x200, ?right, ?nolink, ?recache, …).
Adds `?left`/`?right`/`?center` align keywords shared with DW `{{…}}`
— gives pure-Markdown users a way to align inline images.
- GfmLink now also matches `[![alt](img)](target)` — the GFM equivalent
of `[[target|{{img}}]]`. Detection is post-entry, mirroring
Internallink's `^{{…}}$` check; one mode covers the whole family.
- LinkDispatch trait replaced by Helpers::classifyLink and
Helpers::parseMediaParameters — two pure static methods, shared by
DW and GFM counterparts.
- Entry patterns for GfmLink / GfmMedia simplified (permissive URL slot,
handle-time parsing), following DW's Internallink style.
- GfmSpecTest drives a test-only SpecCompatRenderer that emits bare
<img> / <a> instead of DW's wiki-wrapped HTML, recovering 13 spec
tests that previously failed/skipped only because of renderer shape.

show more ...


# e89aeebd 22-Apr-2026 Andreas Gohr <gohr@cosmocode.de>

add GfmLink for GFM inline links `[text](url)`

Extracts the URL-classification ladder from Internallink into a
LinkDispatch trait so both modes route identically across all six
DokuWiki link flavors

add GfmLink for GFM inline links `[text](url)`

Extracts the URL-classification ladder from Internallink into a
LinkDispatch trait so both modes route identically across all six
DokuWiki link flavors (internal, external, interwiki, email,
windowsshare, local anchor). GfmLink parses the `[text](url)` form
with optional `"title"` / `'title'` and hands the URL to the trait.
The GFM title attribute is discarded — DokuWiki link instructions
have no slot for it.

show more ...


# 8719732d 22-Apr-2026 Andreas Gohr <gohr@cosmocode.de>

add GfmHeader for ATX headings (`# text` through `###### text`)

Opener must sit at column 0. GFM tolerates 0-3 spaces before the `#`
but that collides with DokuWiki's 2-space-indent preformatted blo

add GfmHeader for ATX headings (`# text` through `###### text`)

Opener must sit at column 0. GFM tolerates 0-3 spaces before the `#`
but that collides with DokuWiki's 2-space-indent preformatted block,
so the tolerance is dropped rather than plumbed across modes.

Widen the XHTML renderer's section-node tracker from 5 slots to 6 so
h6 doesn't hit "Undefined array key 5". Extend GfmSpecTest's HTML
normalizer to strip DokuWiki's section-div wrappers, section-edit
comments, and header id/class attributes so heading spec examples
can validate semantic correctness.

show more ...


# 8ed75a23 22-Apr-2026 Andreas Gohr <gohr@cosmocode.de>

add GfmBacktickSingle / GfmBacktickDouble for GFM inline code spans

Two new inline formatting modes covering GFM code spans in their n=1
and n=2 forms:

GfmBacktickSingle `text` → <code>text<

add GfmBacktickSingle / GfmBacktickDouble for GFM inline code spans

Two new inline formatting modes covering GFM code spans in their n=1
and n=2 forms:

GfmBacktickSingle `text` → <code>text</code>
GfmBacktickDouble ``text`` → <code>text</code>

Both emit monospace_open and monospace_close around an unformatted()
call (the same instruction shape as DokuWiki's two-single-quote pair
wrapping a nowiki span), so renderers that distinguish verbatim text
from plain cdata — metadata, indexer, non-XHTML backends — treat the
body as literal.

GfmBacktickDouble extends GfmBacktickSingle to reuse handle() and the
body-normalization helper; only the delimiter length and the body
character class differ. Both share sort 165 and gate on Markdown
being loaded.

Design notes:

* The lexer has no backreferences, so each length is its own mode.
Length-boundary guards (?<!`)...(?!`) on every opener and closer
ensure a run of two-or-more backticks is never read as an n=1
delimiter and a run of three-or-more is never read as n=2. The two
modes never steal each other's input regardless of registration
order — sort can't reach this kind of cross-position constraint.

* Edge-whitespace handling and newline normalization live in handle(),
not in the regex. On DOKU_LEXER_UNMATCHED the body is normalized:
1. CR/LF and LF become single spaces (GFM line-ending rule).
2. If the body starts and ends with a space and is not entirely
whitespace, one space is stripped from each end.
That produces the right GFM output for the tricky cases without
special-casing the entry pattern:
` ` → <code> </code> (all-whitespace, no strip)
` a` → <code> a</code> (asymmetric, no strip)
` `` ` → <code>``</code> (interior run-of-2 + strip)
``foo`bar`` → <code>foo`bar</code>

* Body character classes admit exactly the runs that cannot be valid
closers for this mode's length: n=1 allows `[^`] | ``+`, n=2 allows
`[^`] | `(?!`)`. That is what lets a single-backtick span contain
a pair and a double-backtick span contain a lone backtick.

* allowedModes is empty — no other inline parsing runs inside a span.

Deliberately not implemented, with skip.php entries explaining why:

351 — code-span precedence over emphasis (*foo`*` expected to render
as *foo<code>*</code>). Cross-positional: the single-pass
lexer matches leftmost-first and cannot reject an earlier
emphasis opener because a later backtick span would consume
its closer. A proper fix would need a pre-scan pass; sort
values only break ties at the same position.
353 — the trailing " outside the code span gets converted to a
curly quote by DokuWiki typography, diverging from spec HTML.
354 — raw HTML tag pass-through; DokuWiki does not render raw HTML
by default.
356 — GFM angle-bracket autolink <http://…>: not implemented.

Per-mode unit tests cover basic matching, flanking via the length-
boundary guards, interior-run support in the body, edge-space
stripping, newline normalization, all-whitespace bodies, paragraph-
boundary rejection, content-is-literal, and sort values.
ModeRegistryTest's gating data provider picks up both modes.

Net effect on GfmSpecTest: eleven previously-red code-span examples
now pass (339, 340, 341, 342, 344, 345, 346, 347, 349, 350, 357, 359
— the simple pairs, edge-space, interior-run, newline-normalization,
and mismatched-run cases). Four skipped. Three remain pending outside
the code-span scope (emphasis interactions that need GfmLink once
that lands).

show more ...


# 0244be5c 21-Apr-2026 Andreas Gohr <gohr@cosmocode.de>

add GfmDeleted mode for GFM strikethrough (`~~text~~`)

Shares the deleted_open/deleted_close instructions with DW's <del> mode.
Entry/exit anchors `(?<!~)` / `(?!~)` reject runs of three or more til

add GfmDeleted mode for GFM strikethrough (`~~text~~`)

Shares the deleted_open/deleted_close instructions with DW's <del> mode.
Entry/exit anchors `(?<!~)` / `(?!~)` reject runs of three or more tildes
so fenced-code markers remain untouched. Also trim redundant class-level
docblocks on sibling Gfm test files.

show more ...


12