1<?php
2
3namespace dokuwiki\Parsing;
4
5use dokuwiki\Debug\DebugHelper;
6use Doku_Handler;
7use dokuwiki\Parsing\Lexer\Lexer;
8use dokuwiki\Parsing\ParserMode\Base;
9use 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 */
15class 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