xref: /dokuwiki/inc/Parsing/Parser.php (revision 504c13e8df88563c11b3720b317991bc38835a35)
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     * Adds the base mode and initialized the lexer
40     *
41     * @param Base $BaseMode
42     */
43    protected function addBaseMode($BaseMode)
44    {
45        $this->modes['base'] = $BaseMode;
46        if (!$this->lexer) {
47            $this->lexer = new Lexer($this->handler, 'base', true);
48        }
49        $this->modes['base']->Lexer = $this->lexer;
50        $this->handler->registerModeObject('base', $BaseMode);
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        $this->handler->registerModeObject($name, $Mode);
70    }
71
72    /**
73     * Connect all modes with each other
74     *
75     * This is the last step before actually parsing.
76     */
77    protected function connectModes()
78    {
79
80        if ($this->connected) {
81            return;
82        }
83
84        // Run all preConnect() first so modes can register shared
85        // metadata (e.g. line start markers) before any patterns are built
86        foreach (array_keys($this->modes) as $mode) {
87            if ($mode == 'base') continue;
88            $this->modes[$mode]->preConnect();
89        }
90
91        foreach (array_keys($this->modes) as $mode) {
92            if ($mode == 'base') continue;
93
94            foreach (array_keys($this->modes) as $cm) {
95                if ($this->modes[$cm]->accepts($mode)) {
96                    $this->modes[$mode]->connectTo($cm);
97                }
98            }
99
100            $this->modes[$mode]->postConnect();
101        }
102
103        $this->connected = true;
104    }
105
106    /**
107     * Parses wiki syntax to instructions
108     *
109     * @param string $doc the wiki syntax text
110     * @return array instructions
111     */
112    public function parse($doc)
113    {
114        $this->connectModes();
115        // Normalize CRs and pad doc
116        $doc = "\n" . str_replace("\r\n", "\n", $doc) . "\n";
117        $this->lexer->parse($doc);
118
119        if (!method_exists($this->handler, 'finalize')) {
120            /** @deprecated 2019-10 we have a legacy handler from a plugin, assume legacy _finalize exists */
121
122            DebugHelper::dbgCustomDeprecationEvent(
123                'finalize()',
124                $this->handler::class . '::_finalize()',
125                __METHOD__,
126                __FILE__,
127                __LINE__
128            );
129            $this->handler->_finalize();
130        } else {
131            $this->handler->finalize();
132        }
133        return $this->handler->calls;
134    }
135}
136