1*8719732dSAndreas Gohr<?php 2*8719732dSAndreas Gohr 3*8719732dSAndreas Gohrnamespace dokuwiki\Parsing\ParserMode; 4*8719732dSAndreas Gohr 5*8719732dSAndreas Gohruse dokuwiki\Parsing\Handler; 6*8719732dSAndreas Gohr 7*8719732dSAndreas Gohr/** 8*8719732dSAndreas Gohr * GFM ATX heading: 1-6 leading `#` characters, a mandatory space (or end of 9*8719732dSAndreas Gohr * line for an empty heading), and optional body; emits the same 10*8719732dSAndreas Gohr * header / section_open / section_close instructions as DokuWiki's Header 11*8719732dSAndreas Gohr * so renderers and TOC treat it identically. 12*8719732dSAndreas Gohr * 13*8719732dSAndreas Gohr * Setext headings (=== / --- underlines) are deliberately not supported — 14*8719732dSAndreas Gohr * they collide with DokuWiki's horizontal rule and heading delimiters. 15*8719732dSAndreas Gohr * 16*8719732dSAndreas Gohr * Leading indentation is also not supported: GFM allows 0-3 spaces before 17*8719732dSAndreas Gohr * the opener, but DokuWiki uses 2-space indent for Preformatted blocks 18*8719732dSAndreas Gohr * and that collision isn't worth untangling for a tolerance feature. The 19*8719732dSAndreas Gohr * opener must sit at column 0. 20*8719732dSAndreas Gohr */ 21*8719732dSAndreas Gohrclass GfmHeader extends AbstractMode 22*8719732dSAndreas Gohr{ 23*8719732dSAndreas Gohr /** @inheritdoc */ 24*8719732dSAndreas Gohr public function getSort() 25*8719732dSAndreas Gohr { 26*8719732dSAndreas Gohr return 50; 27*8719732dSAndreas Gohr } 28*8719732dSAndreas Gohr 29*8719732dSAndreas Gohr /** @inheritdoc */ 30*8719732dSAndreas Gohr public function connectTo($mode) 31*8719732dSAndreas Gohr { 32*8719732dSAndreas Gohr // Entry pattern breakdown: 33*8719732dSAndreas Gohr // \n — line start (Parser prepends a newline) 34*8719732dSAndreas Gohr // #{1,6}(?!#) — 1-6 `#` characters; the lookahead 35*8719732dSAndreas Gohr // rejects 7+ so `####### foo` stays as 36*8719732dSAndreas Gohr // paragraph text 37*8719732dSAndreas Gohr // (?:[ \t][^\n]*)? — optional body starting with a space 38*8719732dSAndreas Gohr // or tab; a hash touching a letter 39*8719732dSAndreas Gohr // (`#hashtag`) has no body match and 40*8719732dSAndreas Gohr // the `(?=\n)` below fails unless the 41*8719732dSAndreas Gohr // whole line is just the hashes 42*8719732dSAndreas Gohr // (?=\n) — must end the line 43*8719732dSAndreas Gohr $this->Lexer->addSpecialPattern( 44*8719732dSAndreas Gohr '\n#{1,6}(?!#)(?:[ \t][^\n]*)?(?=\n)', 45*8719732dSAndreas Gohr $mode, 46*8719732dSAndreas Gohr 'gfm_header' 47*8719732dSAndreas Gohr ); 48*8719732dSAndreas Gohr } 49*8719732dSAndreas Gohr 50*8719732dSAndreas Gohr /** @inheritdoc */ 51*8719732dSAndreas Gohr public function handle($match, $state, $pos, Handler $handler) 52*8719732dSAndreas Gohr { 53*8719732dSAndreas Gohr $line = ltrim($match, "\n"); 54*8719732dSAndreas Gohr $level = strspn($line, '#'); 55*8719732dSAndreas Gohr $title = trim(substr($line, $level)); 56*8719732dSAndreas Gohr 57*8719732dSAndreas Gohr // Optional closing `#` run. The sequence must be preceded by 58*8719732dSAndreas Gohr // whitespace; a `#` touching the body (`# foo#`) is content. 59*8719732dSAndreas Gohr // A body that is nothing but `#`s is a closer with no title. 60*8719732dSAndreas Gohr if ($title !== '' && preg_match('/^#+$/', $title)) { 61*8719732dSAndreas Gohr $title = ''; 62*8719732dSAndreas Gohr } elseif (preg_match('/^(.*?)[ \t]+#+$/', $title, $m)) { 63*8719732dSAndreas Gohr $title = rtrim($m[1]); 64*8719732dSAndreas Gohr } 65*8719732dSAndreas Gohr 66*8719732dSAndreas Gohr if ($handler->getStatus('section')) { 67*8719732dSAndreas Gohr $handler->addCall('section_close', [], $pos); 68*8719732dSAndreas Gohr } 69*8719732dSAndreas Gohr $handler->addCall('header', [$title, $level, $pos], $pos); 70*8719732dSAndreas Gohr $handler->addCall('section_open', [$level], $pos); 71*8719732dSAndreas Gohr $handler->setStatus('section', true); 72*8719732dSAndreas Gohr return true; 73*8719732dSAndreas Gohr } 74*8719732dSAndreas Gohr} 75