1<?php 2 3namespace dokuwiki\Parsing; 4 5use dokuwiki\Debug\DebugHelper; 6use dokuwiki\Parsing\Lexer\Lexer; 7use dokuwiki\Parsing\ParserMode\Base; 8use dokuwiki\Parsing\ParserMode\ModeInterface; 9 10/** 11 * Sets up the Lexer with modes and points it to the Handler 12 * For an intro to the Lexer see: wiki:parser 13 */ 14class Parser 15{ 16 /** @var Handler */ 17 protected $handler; 18 19 /** @var Lexer $lexer */ 20 protected $lexer; 21 22 /** @var ModeInterface[] $modes */ 23 protected $modes = []; 24 25 /** @var bool mode connections may only be set up once */ 26 protected $connected = false; 27 28 /** 29 * dokuwiki\Parsing\Doku_Parser constructor. 30 * 31 * @param Handler $handler 32 */ 33 public function __construct(Handler $handler) 34 { 35 $this->handler = $handler; 36 } 37 38 /** 39 * Accessor for the Handler instance. 40 * 41 * @return Handler 42 */ 43 public function getHandler() 44 { 45 return $this->handler; 46 } 47 48 /** 49 * Adds the base mode and initialized the lexer 50 * 51 * @param Base $BaseMode 52 */ 53 protected function addBaseMode($BaseMode) 54 { 55 $this->modes['base'] = $BaseMode; 56 if (!$this->lexer) { 57 $this->lexer = new Lexer($this->handler, 'base', true); 58 } 59 $this->modes['base']->Lexer = $this->lexer; 60 $this->handler->registerModeObject('base', $BaseMode); 61 } 62 63 /** 64 * Add a new syntax element (mode) to the parser 65 * 66 * PHP preserves order of associative elements 67 * Mode sequence is important 68 * 69 * @param string $name 70 * @param ModeInterface $Mode 71 */ 72 public function addMode($name, ModeInterface $Mode) 73 { 74 if (!isset($this->modes['base'])) { 75 $this->addBaseMode(new Base()); 76 } 77 $Mode->Lexer = $this->lexer; // FIXME should be done by setter 78 $this->modes[$name] = $Mode; 79 $this->handler->registerModeObject($name, $Mode); 80 } 81 82 /** 83 * Connect all modes with each other 84 * 85 * This is the last step before actually parsing. 86 */ 87 protected function connectModes() 88 { 89 90 if ($this->connected) { 91 return; 92 } 93 94 // Run all preConnect() first so modes can register shared 95 // metadata (e.g. line start markers) before any patterns are built 96 foreach (array_keys($this->modes) as $mode) { 97 if ($mode == 'base') continue; 98 $this->modes[$mode]->preConnect(); 99 } 100 101 foreach (array_keys($this->modes) as $mode) { 102 if ($mode == 'base') continue; 103 104 foreach (array_keys($this->modes) as $cm) { 105 if ($this->modes[$cm]->accepts($mode)) { 106 $this->modes[$mode]->connectTo($cm); 107 } 108 } 109 110 $this->modes[$mode]->postConnect(); 111 } 112 113 $this->connected = true; 114 } 115 116 /** 117 * Parses wiki syntax to instructions 118 * 119 * @param string $doc the wiki syntax text 120 * @return array instructions 121 */ 122 public function parse($doc) 123 { 124 $this->connectModes(); 125 // Normalize CRs and pad doc 126 $doc = "\n" . str_replace("\r\n", "\n", $doc) . "\n"; 127 $this->lexer->parse($doc); 128 129 if (!method_exists($this->handler, 'finalize')) { 130 /** @deprecated 2019-10 we have a legacy handler from a plugin, assume legacy _finalize exists */ 131 132 DebugHelper::dbgCustomDeprecationEvent( 133 'finalize()', 134 $this->handler::class . '::_finalize()', 135 __METHOD__, 136 __FILE__, 137 __LINE__ 138 ); 139 $this->handler->_finalize(); 140 } else { 141 $this->handler->finalize(); 142 } 143 return $this->handler->calls; 144 } 145} 146