1*71096e46SAndreas Gohr<?php 2*71096e46SAndreas Gohr 3*71096e46SAndreas Gohrnamespace dokuwiki\Parsing; 4*71096e46SAndreas Gohr 5*71096e46SAndreas Gohruse dokuwiki\Extension\Event; 6*71096e46SAndreas Gohruse dokuwiki\Extension\SyntaxPlugin; 7*71096e46SAndreas Gohruse dokuwiki\Parsing\Handler\Block; 8*71096e46SAndreas Gohruse dokuwiki\Parsing\Handler\CallWriter; 9*71096e46SAndreas Gohruse dokuwiki\Parsing\Handler\CallWriterInterface; 10*71096e46SAndreas Gohruse dokuwiki\Parsing\ParserMode\ModeInterface; 11*71096e46SAndreas Gohr 12*71096e46SAndreas Gohr/** 13*71096e46SAndreas Gohr * The Handler receives token events from the Lexer and turns them into 14*71096e46SAndreas Gohr * instruction calls for the Renderer. 15*71096e46SAndreas Gohr */ 16*71096e46SAndreas Gohrclass Handler 17*71096e46SAndreas Gohr{ 18*71096e46SAndreas Gohr /** @var CallWriterInterface */ 19*71096e46SAndreas Gohr protected $callWriter; 20*71096e46SAndreas Gohr 21*71096e46SAndreas Gohr /** @var array The current CallWriter will write directly to this list of calls, Parser reads it */ 22*71096e46SAndreas Gohr public $calls = []; 23*71096e46SAndreas Gohr 24*71096e46SAndreas Gohr /** @var array internal status holders for some modes */ 25*71096e46SAndreas Gohr protected $status = [ 26*71096e46SAndreas Gohr 'section' => false, 27*71096e46SAndreas Gohr 'doublequote' => 0, 28*71096e46SAndreas Gohr 'footnote' => false, 29*71096e46SAndreas Gohr ]; 30*71096e46SAndreas Gohr 31*71096e46SAndreas Gohr /** @var bool should blocks be rewritten? FIXME seems to always be true */ 32*71096e46SAndreas Gohr protected $rewriteBlocks = true; 33*71096e46SAndreas Gohr 34*71096e46SAndreas Gohr /** @var array<string, ModeInterface> mode name → mode object for dispatch */ 35*71096e46SAndreas Gohr protected $modeObjects = []; 36*71096e46SAndreas Gohr 37*71096e46SAndreas Gohr /** @var string the original (pre-remap) mode name for the current token */ 38*71096e46SAndreas Gohr protected $currentModeName = ''; 39*71096e46SAndreas Gohr 40*71096e46SAndreas Gohr /** 41*71096e46SAndreas Gohr * Handler constructor. 42*71096e46SAndreas Gohr */ 43*71096e46SAndreas Gohr public function __construct() 44*71096e46SAndreas Gohr { 45*71096e46SAndreas Gohr $this->callWriter = new CallWriter($this); 46*71096e46SAndreas Gohr } 47*71096e46SAndreas Gohr 48*71096e46SAndreas Gohr /** 49*71096e46SAndreas Gohr * Register a mode object for token dispatch. 50*71096e46SAndreas Gohr * 51*71096e46SAndreas Gohr * Called by the Parser when modes are added. 52*71096e46SAndreas Gohr * 53*71096e46SAndreas Gohr * @param string $name Mode name 54*71096e46SAndreas Gohr * @param ModeInterface $obj The mode object 55*71096e46SAndreas Gohr */ 56*71096e46SAndreas Gohr public function registerModeObject($name, ModeInterface $obj) 57*71096e46SAndreas Gohr { 58*71096e46SAndreas Gohr $this->modeObjects[$name] = $obj; 59*71096e46SAndreas Gohr } 60*71096e46SAndreas Gohr 61*71096e46SAndreas Gohr /** 62*71096e46SAndreas Gohr * Get the original mode name for the current token. 63*71096e46SAndreas Gohr * 64*71096e46SAndreas Gohr * This is the mode name as registered in the Lexer, before any 65*71096e46SAndreas Gohr * mapHandler() remapping. Useful for modes that register multiple 66*71096e46SAndreas Gohr * patterns under different names mapped to the same mode object. 67*71096e46SAndreas Gohr * 68*71096e46SAndreas Gohr * @return string 69*71096e46SAndreas Gohr */ 70*71096e46SAndreas Gohr public function getModeName() 71*71096e46SAndreas Gohr { 72*71096e46SAndreas Gohr return $this->currentModeName; 73*71096e46SAndreas Gohr } 74*71096e46SAndreas Gohr 75*71096e46SAndreas Gohr /** 76*71096e46SAndreas Gohr * Dispatch a token to the appropriate handler. 77*71096e46SAndreas Gohr * 78*71096e46SAndreas Gohr * This is the single entry point called by the Lexer for every token. 79*71096e46SAndreas Gohr * It dispatches to mode objects, plugins, or sub-mode handler methods. 80*71096e46SAndreas Gohr * 81*71096e46SAndreas Gohr * @param string $modeName The resolved mode name 82*71096e46SAndreas Gohr * @param string $match The matched text 83*71096e46SAndreas Gohr * @param int $state The lexer state (DOKU_LEXER_* constant) 84*71096e46SAndreas Gohr * @param int $pos Byte position in the source 85*71096e46SAndreas Gohr * @param string $originalModeName The original mode name before mapHandler remapping 86*71096e46SAndreas Gohr * @return bool 87*71096e46SAndreas Gohr */ 88*71096e46SAndreas Gohr public function handleToken($modeName, $match, $state, $pos, $originalModeName = '') 89*71096e46SAndreas Gohr { 90*71096e46SAndreas Gohr $this->currentModeName = $originalModeName ?: $modeName; 91*71096e46SAndreas Gohr 92*71096e46SAndreas Gohr // core modes: dispatch through the mode object's handle() method 93*71096e46SAndreas Gohr if (isset($this->modeObjects[$modeName])) { 94*71096e46SAndreas Gohr return $this->modeObjects[$modeName]->handle($match, $state, $pos, $this); 95*71096e46SAndreas Gohr } 96*71096e46SAndreas Gohr 97*71096e46SAndreas Gohr // plugin modes: extract plugin name and call plugin() 98*71096e46SAndreas Gohr if (str_starts_with($modeName, 'plugin_')) { 99*71096e46SAndreas Gohr [, $plugin] = sexplode('_', $modeName, 2, ''); 100*71096e46SAndreas Gohr return $this->plugin($match, $state, $pos, $plugin); 101*71096e46SAndreas Gohr } 102*71096e46SAndreas Gohr 103*71096e46SAndreas Gohr // should not be reached — all modes should have registered objects 104*71096e46SAndreas Gohr return false; 105*71096e46SAndreas Gohr } 106*71096e46SAndreas Gohr 107*71096e46SAndreas Gohr /** 108*71096e46SAndreas Gohr * Add a new call by passing it to the current CallWriter 109*71096e46SAndreas Gohr * 110*71096e46SAndreas Gohr * @param string $handler handler method name (see mode handlers below) 111*71096e46SAndreas Gohr * @param mixed $args arguments for this call 112*71096e46SAndreas Gohr * @param int $pos byte position in the original source file 113*71096e46SAndreas Gohr */ 114*71096e46SAndreas Gohr public function addCall($handler, $args, $pos) 115*71096e46SAndreas Gohr { 116*71096e46SAndreas Gohr $call = [$handler, $args, $pos]; 117*71096e46SAndreas Gohr $this->callWriter->writeCall($call); 118*71096e46SAndreas Gohr } 119*71096e46SAndreas Gohr 120*71096e46SAndreas Gohr /** 121*71096e46SAndreas Gohr * Accessor for the current CallWriter 122*71096e46SAndreas Gohr * 123*71096e46SAndreas Gohr * @return CallWriterInterface 124*71096e46SAndreas Gohr */ 125*71096e46SAndreas Gohr public function getCallWriter() 126*71096e46SAndreas Gohr { 127*71096e46SAndreas Gohr return $this->callWriter; 128*71096e46SAndreas Gohr } 129*71096e46SAndreas Gohr 130*71096e46SAndreas Gohr /** 131*71096e46SAndreas Gohr * Set a new CallWriter 132*71096e46SAndreas Gohr * 133*71096e46SAndreas Gohr * @param CallWriterInterface $callWriter 134*71096e46SAndreas Gohr */ 135*71096e46SAndreas Gohr public function setCallWriter($callWriter) 136*71096e46SAndreas Gohr { 137*71096e46SAndreas Gohr $this->callWriter = $callWriter; 138*71096e46SAndreas Gohr } 139*71096e46SAndreas Gohr 140*71096e46SAndreas Gohr /** 141*71096e46SAndreas Gohr * Return the current internal status of the given name 142*71096e46SAndreas Gohr * 143*71096e46SAndreas Gohr * @param string $status 144*71096e46SAndreas Gohr * @return mixed|null 145*71096e46SAndreas Gohr */ 146*71096e46SAndreas Gohr public function getStatus($status) 147*71096e46SAndreas Gohr { 148*71096e46SAndreas Gohr if (!isset($this->status[$status])) return null; 149*71096e46SAndreas Gohr return $this->status[$status]; 150*71096e46SAndreas Gohr } 151*71096e46SAndreas Gohr 152*71096e46SAndreas Gohr /** 153*71096e46SAndreas Gohr * Set a new internal status 154*71096e46SAndreas Gohr * 155*71096e46SAndreas Gohr * @param string $status 156*71096e46SAndreas Gohr * @param mixed $value 157*71096e46SAndreas Gohr */ 158*71096e46SAndreas Gohr public function setStatus($status, $value) 159*71096e46SAndreas Gohr { 160*71096e46SAndreas Gohr $this->status[$status] = $value; 161*71096e46SAndreas Gohr } 162*71096e46SAndreas Gohr 163*71096e46SAndreas Gohr /** @deprecated 2019-10-31 use addCall() instead */ 164*71096e46SAndreas Gohr public function _addCall($handler, $args, $pos) 165*71096e46SAndreas Gohr { 166*71096e46SAndreas Gohr dbg_deprecated('addCall'); 167*71096e46SAndreas Gohr $this->addCall($handler, $args, $pos); 168*71096e46SAndreas Gohr } 169*71096e46SAndreas Gohr 170*71096e46SAndreas Gohr /** 171*71096e46SAndreas Gohr * Similar to addCall, but adds a plugin call 172*71096e46SAndreas Gohr * 173*71096e46SAndreas Gohr * @param string $plugin name of the plugin 174*71096e46SAndreas Gohr * @param mixed $args arguments for this call 175*71096e46SAndreas Gohr * @param int $state a LEXER_STATE_* constant 176*71096e46SAndreas Gohr * @param int $pos byte position in the original source file 177*71096e46SAndreas Gohr * @param string $match matched syntax 178*71096e46SAndreas Gohr */ 179*71096e46SAndreas Gohr public function addPluginCall($plugin, $args, $state, $pos, $match) 180*71096e46SAndreas Gohr { 181*71096e46SAndreas Gohr $call = ['plugin', [$plugin, $args, $state, $match], $pos]; 182*71096e46SAndreas Gohr $this->callWriter->writeCall($call); 183*71096e46SAndreas Gohr } 184*71096e46SAndreas Gohr 185*71096e46SAndreas Gohr /** 186*71096e46SAndreas Gohr * Finishes handling 187*71096e46SAndreas Gohr * 188*71096e46SAndreas Gohr * Called from the parser. Calls finalise() on the call writer, closes open 189*71096e46SAndreas Gohr * sections, rewrites blocks and adds document_start and document_end calls. 190*71096e46SAndreas Gohr * 191*71096e46SAndreas Gohr * @triggers PARSER_HANDLER_DONE 192*71096e46SAndreas Gohr */ 193*71096e46SAndreas Gohr public function finalize() 194*71096e46SAndreas Gohr { 195*71096e46SAndreas Gohr $this->callWriter->finalise(); 196*71096e46SAndreas Gohr 197*71096e46SAndreas Gohr if ($this->status['section']) { 198*71096e46SAndreas Gohr $last_call = end($this->calls); 199*71096e46SAndreas Gohr $this->calls[] = ['section_close', [], $last_call[2]]; 200*71096e46SAndreas Gohr } 201*71096e46SAndreas Gohr 202*71096e46SAndreas Gohr if ($this->rewriteBlocks) { 203*71096e46SAndreas Gohr $B = new Block(); 204*71096e46SAndreas Gohr $this->calls = $B->process($this->calls); 205*71096e46SAndreas Gohr } 206*71096e46SAndreas Gohr 207*71096e46SAndreas Gohr Event::createAndTrigger('PARSER_HANDLER_DONE', $this); 208*71096e46SAndreas Gohr 209*71096e46SAndreas Gohr array_unshift($this->calls, ['document_start', [], 0]); 210*71096e46SAndreas Gohr $last_call = end($this->calls); 211*71096e46SAndreas Gohr $this->calls[] = ['document_end', [], $last_call[2]]; 212*71096e46SAndreas Gohr } 213*71096e46SAndreas Gohr 214*71096e46SAndreas Gohr /** 215*71096e46SAndreas Gohr * Special plugin handler 216*71096e46SAndreas Gohr * 217*71096e46SAndreas Gohr * This handler is called for all modes starting with 'plugin_'. 218*71096e46SAndreas Gohr * An additional parameter with the plugin name is passed. The plugin's handle() 219*71096e46SAndreas Gohr * method is called here 220*71096e46SAndreas Gohr * 221*71096e46SAndreas Gohr * @param string $match matched syntax 222*71096e46SAndreas Gohr * @param int $state a LEXER_STATE_* constant 223*71096e46SAndreas Gohr * @param int $pos byte position in the original source file 224*71096e46SAndreas Gohr * @param string $pluginname name of the plugin 225*71096e46SAndreas Gohr * @return bool mode handled? 226*71096e46SAndreas Gohr * @author Andreas Gohr <andi@splitbrain.org> 227*71096e46SAndreas Gohr */ 228*71096e46SAndreas Gohr public function plugin($match, $state, $pos, $pluginname) 229*71096e46SAndreas Gohr { 230*71096e46SAndreas Gohr $data = [$match]; 231*71096e46SAndreas Gohr /** @var SyntaxPlugin $plugin */ 232*71096e46SAndreas Gohr $plugin = plugin_load('syntax', $pluginname); 233*71096e46SAndreas Gohr if ($plugin != null) { 234*71096e46SAndreas Gohr $data = $plugin->handle($match, $state, $pos, $this); 235*71096e46SAndreas Gohr } 236*71096e46SAndreas Gohr if ($data !== false) { 237*71096e46SAndreas Gohr $this->addPluginCall($pluginname, $data, $state, $pos, $match); 238*71096e46SAndreas Gohr } 239*71096e46SAndreas Gohr return true; 240*71096e46SAndreas Gohr } 241*71096e46SAndreas Gohr 242*71096e46SAndreas Gohr // region deprecated wrappers — called by plugins, delegate to mode objects 243*71096e46SAndreas Gohr 244*71096e46SAndreas Gohr /** 245*71096e46SAndreas Gohr * @deprecated 2026-04-16 use the Base mode object's handle() method 246*71096e46SAndreas Gohr */ 247*71096e46SAndreas Gohr public function base($match, $state, $pos) 248*71096e46SAndreas Gohr { 249*71096e46SAndreas Gohr dbg_deprecated(ParserMode\Base::class . '::handle()'); 250*71096e46SAndreas Gohr return $this->modeObjects['base']->handle($match, $state, $pos, $this); 251*71096e46SAndreas Gohr } 252*71096e46SAndreas Gohr 253*71096e46SAndreas Gohr /** 254*71096e46SAndreas Gohr * @deprecated 2026-04-16 use the Header mode object's handle() method 255*71096e46SAndreas Gohr */ 256*71096e46SAndreas Gohr public function header($match, $state, $pos) 257*71096e46SAndreas Gohr { 258*71096e46SAndreas Gohr dbg_deprecated(ParserMode\Header::class . '::handle()'); 259*71096e46SAndreas Gohr return $this->modeObjects['header']->handle($match, $state, $pos, $this); 260*71096e46SAndreas Gohr } 261*71096e46SAndreas Gohr 262*71096e46SAndreas Gohr /** 263*71096e46SAndreas Gohr * @deprecated 2026-04-16 use the Internallink mode object's handle() method 264*71096e46SAndreas Gohr */ 265*71096e46SAndreas Gohr public function internallink($match, $state, $pos) 266*71096e46SAndreas Gohr { 267*71096e46SAndreas Gohr dbg_deprecated(ParserMode\Internallink::class . '::handle()'); 268*71096e46SAndreas Gohr return $this->modeObjects['internallink']->handle($match, $state, $pos, $this); 269*71096e46SAndreas Gohr } 270*71096e46SAndreas Gohr 271*71096e46SAndreas Gohr /** 272*71096e46SAndreas Gohr * @deprecated 2026-04-16 use the Media mode object's handle() method 273*71096e46SAndreas Gohr */ 274*71096e46SAndreas Gohr public function media($match, $state, $pos) 275*71096e46SAndreas Gohr { 276*71096e46SAndreas Gohr dbg_deprecated(ParserMode\Media::class . '::handle()'); 277*71096e46SAndreas Gohr return $this->modeObjects['media']->handle($match, $state, $pos, $this); 278*71096e46SAndreas Gohr } 279*71096e46SAndreas Gohr 280*71096e46SAndreas Gohr // endregion deprecated wrappers 281*71096e46SAndreas Gohr} 282