<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" href="/rss.xsl.xml"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/">
<channel>
    <title>Changes in GfmBacktickDouble.php</title>
    <description></description>
    <language>en</language>
    <copyright>Copyright 2025</copyright>
    <generator>Java</generator><item>
        <title>1c00c02121477c81b95fe750b94acdf109cba20a - fix(parser): validate inline formatting closers with a single memoized scan</title>
        <link>http://127.0.0.1:8080/history/dokuwiki/inc/Parsing/ParserMode/GfmBacktickDouble.php#1c00c02121477c81b95fe750b94acdf109cba20a</link>
        <description>fix(parser): validate inline formatting closers with a single memoized scanThe inline formatting modes only open a span when a valid closer existsahead. That check was a lookahead built on CONTENT_UNTIL_PARA, testedcharacter by character up to the next paragraph break and re-evaluatedfrom scratch for every opener candidate &#8212; openers times paragraphlength. With pcre.jit=0 a crafted 32KB page took 16s and an ordinary34KB page with long paragraphs 37s; with the JIT on (the PHP default)the per-character lookahead exhausted the JIT stack, the match silentlyfailed, and the formatting &#8212; or everything after it &#8212; rendered as plaintext.The check also decided the wrong thing. It scanned raw text, so a closerlookalike inside content the lexer consumes atomically &#8212; a nowiki or %%span, a backtick code span, a link, a URL &#8212; counted as a real closereven though the mode&apos;s exit pattern can never fire there. And it ignoredthe enclosing span: an inner delimiter whose only closer lay past thecloser of the mode it sits in was entered anyway, so a stray delimiterpaired with one in a following sibling span and dragged the boundaryalong &#8212; the `*` in &apos;&apos;glob/*.conf&apos;&apos; joined the `*` of the next &apos;&apos;...&apos;&apos;span and corrupted the paragraph; the same held for //, ** and __ insidemonospace, and an emphasis opened inside ((...)) ran past the footnote&apos;s)) and the enclosing bold&apos;s **.Each formatting mode now declares its closer throughLexer::addCloserPattern(), mirroring addExitPattern(), and the lexeranswers &quot;does a valid closer exist ahead&quot; with one anchored possessivescan per range instead of a lookahead per opener:- The scan runs left to right from the opener, hopping over opaque spans  derived from already-registered patterns &#8212; a plain or special match is  consumed in one step, an entry into a verbatim mode (nowiki, the  backtick code spans) extends to that mode&apos;s first exit &#8212; so a closer  lookalike inside consumed content is never mistaken for a closer. Each  hop finds the earliest of boundary, closer, or opaque span in a single  leftmost search, keeping the check linear.- An opener is rejected when the nearest enclosing mode that has a closer  of its own would close before the opener&apos;s own closer, so a delimiter  that can never close within its span stays literal. That ancestor is  found by walking the mode stack past modes that declare no closer  (plugins, footnotes); the nearest guarded ancestor suffices, as it was  itself validated against its own when it opened.- Both verdicts are memoized and reset per parse() run: a proven closer  validates every earlier candidate, and a proven closer-free range  rejects every later candidate before the next boundary. With the lexer  consuming each opened span, the whole parse is linear in document size.Closer patterns match the closing delimiter itself with flanking contextin lookarounds &#8212; the convention exit patterns already follow &#8212; so closerpositions compare exactly across modes and a closer directly after aninner opener is seen. AbstractFormatting derives the closer from the exitpattern and registers it with the paragraph break as the boundary,preserving the rule that formatting never spans paragraphs; a mode withother needs can pass a different boundary or none.Footnote declares its )) as a closer rather than guarding its (( entrywith a (?=.*)) lookahead, so the footnote becomes a boundary the scansees and formatting inside it no longer pairs across the )); its closertakes no paragraph boundary, as footnotes are block-level. GfmEmphasisgains a closer pattern so single * emphasis is validated the same way,while its entry lookahead still enforces CommonMark nearest-delimiterpairing. GfmEmphasis and GfmStrong span bodies cannot contain theirdelimiter, so their in-pattern lookaheads stay linear on their own; theGFM backtick span bodies get deterministic alternatives with possessivequantifiers, removing their per-character backtracking.CONTENT_UNTIL_PARA is removed: any entry pattern built with it recreatesthe quadratic scan. ParallelRegex gains escapePattern() so embeddedcloser fragments follow the lexer&apos;s bare-parenthesis convention, reportsPREG_JIT_STACKLIMIT_ERROR so a future JIT exhaustion surfaces instead ofsilently truncating, and no longer rewrites its registered patterns inplace while compiling the compound regex.The adversarial 32KB page drops to 0.1s, the 37-second benign page to0.1s, and a 128KB variant stays under 0.6s.

            List of files:
            /dokuwiki/inc/Parsing/ParserMode/GfmBacktickDouble.php</description>
        <pubDate>Thu, 09 Jul 2026 12:09:52 +0000</pubDate>
        <dc:creator>Andreas Gohr &lt;gohr@cosmocode.de&gt;</dc:creator>
    </item>
<item>
        <title>8ed75a23932353c18b43f67323808e9a662f532a - add GfmBacktickSingle / GfmBacktickDouble for GFM inline code spans</title>
        <link>http://127.0.0.1:8080/history/dokuwiki/inc/Parsing/ParserMode/GfmBacktickDouble.php#8ed75a23932353c18b43f67323808e9a662f532a</link>
        <description>add GfmBacktickSingle / GfmBacktickDouble for GFM inline code spansTwo new inline formatting modes covering GFM code spans in their n=1and n=2 forms:  GfmBacktickSingle   `text`    &#8594; &lt;code&gt;text&lt;/code&gt;  GfmBacktickDouble   ``text``  &#8594; &lt;code&gt;text&lt;/code&gt;Both emit monospace_open and monospace_close around an unformatted()call (the same instruction shape as DokuWiki&apos;s two-single-quote pairwrapping a nowiki span), so renderers that distinguish verbatim textfrom plain cdata &#8212; metadata, indexer, non-XHTML backends &#8212; treat thebody as literal.GfmBacktickDouble extends GfmBacktickSingle to reuse handle() and thebody-normalization helper; only the delimiter length and the bodycharacter class differ. Both share sort 165 and gate on Markdownbeing loaded.Design notes:* The lexer has no backreferences, so each length is its own mode.  Length-boundary guards (?&lt;!`)...(?!`) 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&apos;s input regardless of registration  order &#8212; sort can&apos;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:    ` `          &#8594; &lt;code&gt; &lt;/code&gt;    (all-whitespace, no strip)    ` a`         &#8594; &lt;code&gt; a&lt;/code&gt;   (asymmetric, no strip)    ` `` `       &#8594; &lt;code&gt;``&lt;/code&gt;   (interior run-of-2 + strip)    ``foo`bar``  &#8594; &lt;code&gt;foo`bar&lt;/code&gt;* Body character classes admit exactly the runs that cannot be valid  closers for this mode&apos;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 &#8212; no other inline parsing runs inside a span.Deliberately not implemented, with skip.php entries explaining why:  351 &#8212; code-span precedence over emphasis (*foo`*` expected to render        as *foo&lt;code&gt;*&lt;/code&gt;). 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 &#8212; the trailing &quot; outside the code span gets converted to a        curly quote by DokuWiki typography, diverging from spec HTML.  354 &#8212; raw HTML tag pass-through; DokuWiki does not render raw HTML        by default.  356 &#8212; GFM angle-bracket autolink &lt;http://&#8230;&gt;: not implemented.Per-mode unit tests cover basic matching, flanking via the length-boundary guards, interior-run support in the body, edge-spacestripping, newline normalization, all-whitespace bodies, paragraph-boundary rejection, content-is-literal, and sort values.ModeRegistryTest&apos;s gating data provider picks up both modes.Net effect on GfmSpecTest: eleven previously-red code-span examplesnow pass (339, 340, 341, 342, 344, 345, 346, 347, 349, 350, 357, 359&#8212; the simple pairs, edge-space, interior-run, newline-normalization,and mismatched-run cases). Four skipped. Three remain pending outsidethe code-span scope (emphasis interactions that need GfmLink oncethat lands).

            List of files:
            /dokuwiki/inc/Parsing/ParserMode/GfmBacktickDouble.php</description>
        <pubDate>Wed, 22 Apr 2026 07:39:11 +0000</pubDate>
        <dc:creator>Andreas Gohr &lt;gohr@cosmocode.de&gt;</dc:creator>
    </item>
</channel>
</rss>
