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