<?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 AbstractFormatting.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/AbstractFormatting.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/AbstractFormatting.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>47a02a102092be9e1e6f1ddaf158bdfffdb13d4f - Parsing: make parse syntax a per-parse value, drop ModeInterface</title>
        <link>http://127.0.0.1:8080/history/dokuwiki/inc/Parsing/ParserMode/AbstractFormatting.php#47a02a102092be9e1e6f1ddaf158bdfffdb13d4f</link>
        <description>Parsing: make parse syntax a per-parse value, drop ModeInterfaceThe active parse&apos;s syntax flavour is a per-parse question, not process-global state: within a single request a plugin can render bundledDokuWiki-syntax text inside an otherwise-Markdown page. Yet ModeRegistrywas a singleton that read $conf[&apos;syntax&apos;] and the $PARSER_MODES global,and every mode reached it through ModeRegistry::getInstance() &#8212; so theflavour lived in shared mutable state that two parses in one requestwould 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-&gt;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[&apos;syntax&apos;] is read; from there the flavour travels as a  parameter. No code under inc/Parsing/ reads $conf[&apos;syntax&apos;] directly  anymore &#8212; the five syntax-reading modes (Preformatted, GfmHr,  GfmEscape, Externallink, GfmQuote) route through $this-&gt;registry.Keep the two concepts apart, as documented in the ModeRegistry andAbstractMode docblocks: the user&apos;s configured *preference* stays in$conf[&apos;syntax&apos;] for UI code (toolbar, settings), while the activeparse&apos;s syntax is a parameter carried by the registry.$PARSER_MODES is demoted to a deprecated, read-only mirror, publishedduring loadPluginModes() &#8212; third-party syntax plugins (columnlist,alphalist2, phpwikify, skipentity) and the bundled info plugin read theglobal directly, often from their constructors, so the taxonomy muststay visible there. No core code reads the mirror.Fold ModeInterface into AbstractMode while here: getSort()/handle() areabstract, the connect callbacks carry defaults, and the public $Lexer&quot;FIXME should be done by setter&quot; becomes setLexer()/getLexer() injectedby Parser::addMode() alongside the registry. Nested-content resolutionmoves to the allowedCategories()/filterAllowedModes() hooks, resolvedonce when the registry is attached.Tests build their own parser/registry through ParserTestBase::setSyntax()instead of mutating $conf and calling the removed ModeRegistry::reset().

            List of files:
            /dokuwiki/inc/Parsing/ParserMode/AbstractFormatting.php</description>
        <pubDate>Thu, 04 Jun 2026 12:27:59 +0000</pubDate>
        <dc:creator>Andreas Gohr &lt;gohr@cosmocode.de&gt;</dc:creator>
    </item>
<item>
        <title>06ab3bb2c83692e8fee0c14ad0cc085ac2b07162 - Merge branch &apos;master&apos; into gfm</title>
        <link>http://127.0.0.1:8080/history/dokuwiki/inc/Parsing/ParserMode/AbstractFormatting.php#06ab3bb2c83692e8fee0c14ad0cc085ac2b07162</link>
        <description>Merge branch &apos;master&apos; into gfm* master: (176 commits)  Delete inc/Search/concept.txt  &#55358;&#56598; Rector and PHPCS fixes  &#55358;&#56598; 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  &#55358;&#56598; 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&apos;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

            List of files:
            /dokuwiki/inc/Parsing/ParserMode/AbstractFormatting.php</description>
        <pubDate>Tue, 12 May 2026 14:11:42 +0000</pubDate>
        <dc:creator>Andreas Gohr &lt;andi@splitbrain.org&gt;</dc:creator>
    </item>
<item>
        <title>56c730b56ef2746acf3b1a27c69bada1239535bd - keep historic typo in value but not in constant</title>
        <link>http://127.0.0.1:8080/history/dokuwiki/inc/Parsing/ParserMode/AbstractFormatting.php#56c730b56ef2746acf3b1a27c69bada1239535bd</link>
        <description>keep historic typo in value but not in constantWe need to keep the historic typo in the value (&quot;substition&quot;), but thereis no reason to keep it in the constant.

            List of files:
            /dokuwiki/inc/Parsing/ParserMode/AbstractFormatting.php</description>
        <pubDate>Wed, 06 May 2026 19:14:25 +0000</pubDate>
        <dc:creator>Andreas Gohr &lt;andi@splitbrain.org&gt;</dc:creator>
    </item>
<item>
        <title>6b33ca93d31259cdb3d5ae2fc49b5215b6bde268 - add regex-primitive constants and getInstructionName() hook</title>
        <link>http://127.0.0.1:8080/history/dokuwiki/inc/Parsing/ParserMode/AbstractFormatting.php#6b33ca93d31259cdb3d5ae2fc49b5215b6bde268</link>
        <description>add regex-primitive constants and getInstructionName() hookPreparatory refactor for the upcoming GFM parser modes. No behaviourchange for any existing mode: CONTENT_UNTIL_PARA still evaluates tothe same regex (now factored through NOT_AT_PARA_BREAK), andgetInstructionName() defaults to getModeName() so all currentAbstractFormatting subclasses emit the same handler instructions asbefore.AbstractMode gains four new shared regex constants:  NOT_AT_PARA_BREAK  &#8212; zero-width assertion: current position is not                       the start of a paragraph break (blank line).                       Extracted from CONTENT_UNTIL_PARA for reuse in                       patterns that need a custom body char class.  NON_WORD_CHAR      &#8212; char class: ASCII whitespace or ASCII punctuation                       except `_`. Multibyte-safe by construction:                       UTF-8 continuation bytes are &gt;= 0x80 and thus                       fall outside every ASCII class, so checking                       positively that the surrounding context IS a                       non-word char correctly treats multibyte                       letters as word-like. No `u` flag required.  NO_WORD_BEFORE     &#8212; zero-width: preceded by NON_WORD_CHAR or at                       start-of-input/line. For intraword-aware                       openers.  NO_WORD_AFTER      &#8212; zero-width: followed by NON_WORD_CHAR or at                       end-of-input. Complement of NO_WORD_BEFORE.AbstractFormatting gains a getInstructionName() hook that defaults togetModeName(). Subclasses that want to emit handler instructions undera different name than their lexer mode name (so a Gfm mode can shareDW&apos;s `emphasis_open`/`strong_open` instructions while registering itsown lexer state) override this method.

            List of files:
            /dokuwiki/inc/Parsing/ParserMode/AbstractFormatting.php</description>
        <pubDate>Mon, 20 Apr 2026 18:45:38 +0000</pubDate>
        <dc:creator>Andreas Gohr &lt;gohr@cosmocode.de&gt;</dc:creator>
    </item>
<item>
        <title>71096e46fcbfaeaa808667aba794e77fe2780169 - move handler methods into ParserMode classes and rename Handler</title>
        <link>http://127.0.0.1:8080/history/dokuwiki/inc/Parsing/ParserMode/AbstractFormatting.php#71096e46fcbfaeaa808667aba794e77fe2780169</link>
        <description>move handler methods into ParserMode classes and rename HandlerEach ParserMode class now implements handle() from ModeInterface,containing the token handling logic that previously lived as individualmethods on Doku_Handler.The Handler class (formerly Doku_Handler) is the single dispatch point:Lexer passes tokens to Handler::handleToken() which routes to modeobjects, plugins, or returns false. The Lexer only tokenizes andresolves mapHandler aliases.Key changes:- Add handle() to ModeInterface, implemented by all mode classes- Move Doku_Handler to dokuwiki\Parsing\Handler namespace- File extends Code (shared parsing via $type property)- Quotes uses mapHandler() + Handler::getModeName() for sub-modes- Media::parseMedia() replaces Doku_Handler_Parse_Media()- Code::parseHighlightOptions() replaces parse_highlight_options()- Per-parse state (footnote, doublequote) stays on Handler- Deprecated wrappers kept for base/header/internallink/media- Class alias and rector rules added for backward compatibility

            List of files:
            /dokuwiki/inc/Parsing/ParserMode/AbstractFormatting.php</description>
        <pubDate>Sat, 18 Apr 2026 15:35:30 +0000</pubDate>
        <dc:creator>Andreas Gohr &lt;andi@splitbrain.org&gt;</dc:creator>
    </item>
<item>
        <title>1f44347694a1b2d0182a0aaa0ca79ba9930f1d2a - split Formatting into individual classes per formatting type</title>
        <link>http://127.0.0.1:8080/history/dokuwiki/inc/Parsing/ParserMode/AbstractFormatting.php#1f44347694a1b2d0182a0aaa0ca79ba9930f1d2a</link>
        <description>split Formatting into individual classes per formatting typeIntroduce AbstractFormatting as a base class and seven concreteclasses (Strong, Emphasis, Underline, Monospace, Subscript,Superscript, Deleted) that each define their own patterns andsort order. Delete the old Formatting class and update teststo use the new classes directly. ModeRegistry now treatsformatting modes as regular built-in modes.

            List of files:
            /dokuwiki/inc/Parsing/ParserMode/AbstractFormatting.php</description>
        <pubDate>Thu, 16 Apr 2026 16:17:15 +0000</pubDate>
        <dc:creator>Andreas Gohr &lt;andi@splitbrain.org&gt;</dc:creator>
    </item>
</channel>
</rss>
