171096e46SAndreas Gohr<?php 271096e46SAndreas Gohr 371096e46SAndreas Gohrnamespace dokuwiki\Parsing; 471096e46SAndreas Gohr 58788dbbdSsplitbrainuse dokuwiki\Parsing\ParserMode\Base; 68788dbbdSsplitbrainuse dokuwiki\Parsing\ParserMode\Header; 78788dbbdSsplitbrainuse dokuwiki\Parsing\ParserMode\Internallink; 88788dbbdSsplitbrainuse dokuwiki\Parsing\ParserMode\Media; 971096e46SAndreas Gohruse dokuwiki\Extension\Event; 1071096e46SAndreas Gohruse dokuwiki\Extension\SyntaxPlugin; 1171096e46SAndreas Gohruse dokuwiki\Parsing\Handler\Block; 1271096e46SAndreas Gohruse dokuwiki\Parsing\Handler\CallWriter; 1371096e46SAndreas Gohruse dokuwiki\Parsing\Handler\CallWriterInterface; 1471096e46SAndreas Gohruse dokuwiki\Parsing\ParserMode\ModeInterface; 1571096e46SAndreas Gohr 1671096e46SAndreas Gohr/** 1771096e46SAndreas Gohr * The Handler receives token events from the Lexer and turns them into 1871096e46SAndreas Gohr * instruction calls for the Renderer. 1971096e46SAndreas Gohr */ 2071096e46SAndreas Gohrclass Handler 2171096e46SAndreas Gohr{ 2271096e46SAndreas Gohr /** @var CallWriterInterface */ 2371096e46SAndreas Gohr protected $callWriter; 2471096e46SAndreas Gohr 2571096e46SAndreas Gohr /** @var array The current CallWriter will write directly to this list of calls, Parser reads it */ 2671096e46SAndreas Gohr public $calls = []; 2771096e46SAndreas Gohr 2871096e46SAndreas Gohr /** @var array internal status holders for some modes */ 2971096e46SAndreas Gohr protected $status = [ 3071096e46SAndreas Gohr 'section' => false, 3171096e46SAndreas Gohr 'doublequote' => 0, 3271096e46SAndreas Gohr 'footnote' => false, 3371096e46SAndreas Gohr ]; 3471096e46SAndreas Gohr 3571096e46SAndreas Gohr /** @var array<string, ModeInterface> mode name → mode object for dispatch */ 3671096e46SAndreas Gohr protected $modeObjects = []; 3771096e46SAndreas Gohr 3871096e46SAndreas Gohr /** @var string the original (pre-remap) mode name for the current token */ 3971096e46SAndreas Gohr protected $currentModeName = ''; 4071096e46SAndreas Gohr 4171096e46SAndreas Gohr /** 4271096e46SAndreas Gohr * Handler constructor. 4371096e46SAndreas Gohr */ 4471096e46SAndreas Gohr public function __construct() 4571096e46SAndreas Gohr { 4671096e46SAndreas Gohr $this->callWriter = new CallWriter($this); 4771096e46SAndreas Gohr } 4871096e46SAndreas Gohr 4971096e46SAndreas Gohr /** 5071096e46SAndreas Gohr * Register a mode object for token dispatch. 5171096e46SAndreas Gohr * 5271096e46SAndreas Gohr * Called by the Parser when modes are added. 5371096e46SAndreas Gohr * 5471096e46SAndreas Gohr * @param string $name Mode name 5571096e46SAndreas Gohr * @param ModeInterface $obj The mode object 5671096e46SAndreas Gohr */ 5771096e46SAndreas Gohr public function registerModeObject($name, ModeInterface $obj) 5871096e46SAndreas Gohr { 5971096e46SAndreas Gohr $this->modeObjects[$name] = $obj; 6071096e46SAndreas Gohr } 6171096e46SAndreas Gohr 6271096e46SAndreas Gohr /** 6371096e46SAndreas Gohr * Get the original mode name for the current token. 6471096e46SAndreas Gohr * 6571096e46SAndreas Gohr * This is the mode name as registered in the Lexer, before any 6671096e46SAndreas Gohr * mapHandler() remapping. Useful for modes that register multiple 6771096e46SAndreas Gohr * patterns under different names mapped to the same mode object. 6871096e46SAndreas Gohr * 6971096e46SAndreas Gohr * @return string 7071096e46SAndreas Gohr */ 7171096e46SAndreas Gohr public function getModeName() 7271096e46SAndreas Gohr { 7371096e46SAndreas Gohr return $this->currentModeName; 7471096e46SAndreas Gohr } 7571096e46SAndreas Gohr 7671096e46SAndreas Gohr /** 7771096e46SAndreas Gohr * Dispatch a token to the appropriate handler. 7871096e46SAndreas Gohr * 7971096e46SAndreas Gohr * This is the single entry point called by the Lexer for every token. 8071096e46SAndreas Gohr * It dispatches to mode objects, plugins, or sub-mode handler methods. 8171096e46SAndreas Gohr * 8271096e46SAndreas Gohr * @param string $modeName The resolved mode name 8371096e46SAndreas Gohr * @param string $match The matched text 8471096e46SAndreas Gohr * @param int $state The lexer state (DOKU_LEXER_* constant) 8571096e46SAndreas Gohr * @param int $pos Byte position in the source 8671096e46SAndreas Gohr * @param string $originalModeName The original mode name before mapHandler remapping 8771096e46SAndreas Gohr * @return bool 8871096e46SAndreas Gohr */ 8971096e46SAndreas Gohr public function handleToken($modeName, $match, $state, $pos, $originalModeName = '') 9071096e46SAndreas Gohr { 9171096e46SAndreas Gohr $this->currentModeName = $originalModeName ?: $modeName; 9271096e46SAndreas Gohr 93*7686f203SAnna Dabrowska // check plugin modes first: they must go through plugin() so addPluginCall() emits an instruction. 94*7686f203SAnna Dabrowska // SyntaxPlugin::handle() only returns data but in contrast to core modes, it does not write to the call list. 9571096e46SAndreas Gohr if (str_starts_with($modeName, 'plugin_')) { 9671096e46SAndreas Gohr [, $plugin] = sexplode('_', $modeName, 2, ''); 9771096e46SAndreas Gohr return $this->plugin($match, $state, $pos, $plugin); 9871096e46SAndreas Gohr } 9971096e46SAndreas Gohr 100*7686f203SAnna Dabrowska // core modes: dispatch through the mode object's handle() method 101*7686f203SAnna Dabrowska if (isset($this->modeObjects[$modeName])) { 102*7686f203SAnna Dabrowska return $this->modeObjects[$modeName]->handle($match, $state, $pos, $this); 103*7686f203SAnna Dabrowska } 104*7686f203SAnna Dabrowska 10571096e46SAndreas Gohr // should not be reached — all modes should have registered objects 10671096e46SAndreas Gohr return false; 10771096e46SAndreas Gohr } 10871096e46SAndreas Gohr 10971096e46SAndreas Gohr /** 11071096e46SAndreas Gohr * Add a new call by passing it to the current CallWriter 11171096e46SAndreas Gohr * 11271096e46SAndreas Gohr * @param string $handler handler method name (see mode handlers below) 11371096e46SAndreas Gohr * @param mixed $args arguments for this call 11471096e46SAndreas Gohr * @param int $pos byte position in the original source file 11571096e46SAndreas Gohr */ 11671096e46SAndreas Gohr public function addCall($handler, $args, $pos) 11771096e46SAndreas Gohr { 11871096e46SAndreas Gohr $call = [$handler, $args, $pos]; 11971096e46SAndreas Gohr $this->callWriter->writeCall($call); 12071096e46SAndreas Gohr } 12171096e46SAndreas Gohr 12271096e46SAndreas Gohr /** 12371096e46SAndreas Gohr * Accessor for the current CallWriter 12471096e46SAndreas Gohr * 12571096e46SAndreas Gohr * @return CallWriterInterface 12671096e46SAndreas Gohr */ 12771096e46SAndreas Gohr public function getCallWriter() 12871096e46SAndreas Gohr { 12971096e46SAndreas Gohr return $this->callWriter; 13071096e46SAndreas Gohr } 13171096e46SAndreas Gohr 13271096e46SAndreas Gohr /** 13371096e46SAndreas Gohr * Set a new CallWriter 13471096e46SAndreas Gohr * 13571096e46SAndreas Gohr * @param CallWriterInterface $callWriter 13671096e46SAndreas Gohr */ 13771096e46SAndreas Gohr public function setCallWriter($callWriter) 13871096e46SAndreas Gohr { 13971096e46SAndreas Gohr $this->callWriter = $callWriter; 14071096e46SAndreas Gohr } 14171096e46SAndreas Gohr 14271096e46SAndreas Gohr /** 14371096e46SAndreas Gohr * Return the current internal status of the given name 14471096e46SAndreas Gohr * 14571096e46SAndreas Gohr * @param string $status 14671096e46SAndreas Gohr * @return mixed|null 14771096e46SAndreas Gohr */ 14871096e46SAndreas Gohr public function getStatus($status) 14971096e46SAndreas Gohr { 15071096e46SAndreas Gohr if (!isset($this->status[$status])) return null; 15171096e46SAndreas Gohr return $this->status[$status]; 15271096e46SAndreas Gohr } 15371096e46SAndreas Gohr 15471096e46SAndreas Gohr /** 15571096e46SAndreas Gohr * Set a new internal status 15671096e46SAndreas Gohr * 15771096e46SAndreas Gohr * @param string $status 15871096e46SAndreas Gohr * @param mixed $value 15971096e46SAndreas Gohr */ 16071096e46SAndreas Gohr public function setStatus($status, $value) 16171096e46SAndreas Gohr { 16271096e46SAndreas Gohr $this->status[$status] = $value; 16371096e46SAndreas Gohr } 16471096e46SAndreas Gohr 16571096e46SAndreas Gohr /** @deprecated 2019-10-31 use addCall() instead */ 1663ea3771bSAndreas Gohr // phpcs:ignore PSR2.Methods.MethodDeclaration.Underscore -- backward compatibility 16771096e46SAndreas Gohr public function _addCall($handler, $args, $pos) 16871096e46SAndreas Gohr { 16971096e46SAndreas Gohr dbg_deprecated('addCall'); 17071096e46SAndreas Gohr $this->addCall($handler, $args, $pos); 17171096e46SAndreas Gohr } 17271096e46SAndreas Gohr 17371096e46SAndreas Gohr /** 17471096e46SAndreas Gohr * Similar to addCall, but adds a plugin call 17571096e46SAndreas Gohr * 17671096e46SAndreas Gohr * @param string $plugin name of the plugin 17771096e46SAndreas Gohr * @param mixed $args arguments for this call 17871096e46SAndreas Gohr * @param int $state a LEXER_STATE_* constant 17971096e46SAndreas Gohr * @param int $pos byte position in the original source file 18071096e46SAndreas Gohr * @param string $match matched syntax 18171096e46SAndreas Gohr */ 18271096e46SAndreas Gohr public function addPluginCall($plugin, $args, $state, $pos, $match) 18371096e46SAndreas Gohr { 18471096e46SAndreas Gohr $call = ['plugin', [$plugin, $args, $state, $match], $pos]; 18571096e46SAndreas Gohr $this->callWriter->writeCall($call); 18671096e46SAndreas Gohr } 18771096e46SAndreas Gohr 18871096e46SAndreas Gohr /** 18971096e46SAndreas Gohr * Finishes handling 19071096e46SAndreas Gohr * 19171096e46SAndreas Gohr * Called from the parser. Calls finalise() on the call writer, closes open 19271096e46SAndreas Gohr * sections, rewrites blocks and adds document_start and document_end calls. 19371096e46SAndreas Gohr * 19471096e46SAndreas Gohr * @triggers PARSER_HANDLER_DONE 19571096e46SAndreas Gohr */ 19671096e46SAndreas Gohr public function finalize() 19771096e46SAndreas Gohr { 19871096e46SAndreas Gohr $this->callWriter->finalise(); 19971096e46SAndreas Gohr 20071096e46SAndreas Gohr if ($this->status['section']) { 20171096e46SAndreas Gohr $last_call = end($this->calls); 20271096e46SAndreas Gohr $this->calls[] = ['section_close', [], $last_call[2]]; 20371096e46SAndreas Gohr } 20471096e46SAndreas Gohr 20571096e46SAndreas Gohr $B = new Block(); 20671096e46SAndreas Gohr $this->calls = $B->process($this->calls); 20771096e46SAndreas Gohr 20871096e46SAndreas Gohr Event::createAndTrigger('PARSER_HANDLER_DONE', $this); 20971096e46SAndreas Gohr 21071096e46SAndreas Gohr array_unshift($this->calls, ['document_start', [], 0]); 21171096e46SAndreas Gohr $last_call = end($this->calls); 21271096e46SAndreas Gohr $this->calls[] = ['document_end', [], $last_call[2]]; 21371096e46SAndreas Gohr } 21471096e46SAndreas Gohr 21571096e46SAndreas Gohr /** 21671096e46SAndreas Gohr * Special plugin handler 21771096e46SAndreas Gohr * 21871096e46SAndreas Gohr * This handler is called for all modes starting with 'plugin_'. 21971096e46SAndreas Gohr * An additional parameter with the plugin name is passed. The plugin's handle() 22071096e46SAndreas Gohr * method is called here 22171096e46SAndreas Gohr * 22271096e46SAndreas Gohr * @param string $match matched syntax 22371096e46SAndreas Gohr * @param int $state a LEXER_STATE_* constant 22471096e46SAndreas Gohr * @param int $pos byte position in the original source file 22571096e46SAndreas Gohr * @param string $pluginname name of the plugin 22671096e46SAndreas Gohr * @return bool mode handled? 22771096e46SAndreas Gohr * @author Andreas Gohr <andi@splitbrain.org> 22871096e46SAndreas Gohr */ 22971096e46SAndreas Gohr public function plugin($match, $state, $pos, $pluginname) 23071096e46SAndreas Gohr { 23171096e46SAndreas Gohr $data = [$match]; 23271096e46SAndreas Gohr /** @var SyntaxPlugin $plugin */ 23371096e46SAndreas Gohr $plugin = plugin_load('syntax', $pluginname); 23471096e46SAndreas Gohr if ($plugin != null) { 23571096e46SAndreas Gohr $data = $plugin->handle($match, $state, $pos, $this); 23671096e46SAndreas Gohr } 23771096e46SAndreas Gohr if ($data !== false) { 23871096e46SAndreas Gohr $this->addPluginCall($pluginname, $data, $state, $pos, $match); 23971096e46SAndreas Gohr } 24071096e46SAndreas Gohr return true; 24171096e46SAndreas Gohr } 24271096e46SAndreas Gohr 24371096e46SAndreas Gohr // region deprecated wrappers — called by plugins, delegate to mode objects 24471096e46SAndreas Gohr 24571096e46SAndreas Gohr /** 24671096e46SAndreas Gohr * @deprecated 2026-04-16 use the Base mode object's handle() method 24771096e46SAndreas Gohr */ 24871096e46SAndreas Gohr public function base($match, $state, $pos) 24971096e46SAndreas Gohr { 2508788dbbdSsplitbrain dbg_deprecated(Base::class . '::handle()'); 25171096e46SAndreas Gohr return $this->modeObjects['base']->handle($match, $state, $pos, $this); 25271096e46SAndreas Gohr } 25371096e46SAndreas Gohr 25471096e46SAndreas Gohr /** 25571096e46SAndreas Gohr * @deprecated 2026-04-16 use the Header mode object's handle() method 25671096e46SAndreas Gohr */ 25771096e46SAndreas Gohr public function header($match, $state, $pos) 25871096e46SAndreas Gohr { 2598788dbbdSsplitbrain dbg_deprecated(Header::class . '::handle()'); 26071096e46SAndreas Gohr return $this->modeObjects['header']->handle($match, $state, $pos, $this); 26171096e46SAndreas Gohr } 26271096e46SAndreas Gohr 26371096e46SAndreas Gohr /** 26471096e46SAndreas Gohr * @deprecated 2026-04-16 use the Internallink mode object's handle() method 26571096e46SAndreas Gohr */ 26671096e46SAndreas Gohr public function internallink($match, $state, $pos) 26771096e46SAndreas Gohr { 2688788dbbdSsplitbrain dbg_deprecated(Internallink::class . '::handle()'); 26971096e46SAndreas Gohr return $this->modeObjects['internallink']->handle($match, $state, $pos, $this); 27071096e46SAndreas Gohr } 27171096e46SAndreas Gohr 27271096e46SAndreas Gohr /** 27371096e46SAndreas Gohr * @deprecated 2026-04-16 use the Media mode object's handle() method 27471096e46SAndreas Gohr */ 27571096e46SAndreas Gohr public function media($match, $state, $pos) 27671096e46SAndreas Gohr { 2778788dbbdSsplitbrain dbg_deprecated(Media::class . '::handle()'); 27871096e46SAndreas Gohr return $this->modeObjects['media']->handle($match, $state, $pos, $this); 27971096e46SAndreas Gohr } 28071096e46SAndreas Gohr 28171096e46SAndreas Gohr // endregion deprecated wrappers 28271096e46SAndreas Gohr} 283