xref: /dokuwiki/inc/Parsing/Parser.php (revision e3c3abf1ebade68a06abb03d098d58afda87b317)
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        $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        $this->modes['base'] = $BaseMode;
45        if(!$this->lexer) {
46            $this->lexer = new Lexer($this->handler, 'base', true);
47        }
48        $this->modes['base']->Lexer = $this->lexer;
49    }
50
51    /**
52     * Add a new syntax element (mode) to the parser
53     *
54     * PHP preserves order of associative elements
55     * Mode sequence is important
56     *
57     * @param string $name
58     * @param ModeInterface $Mode
59     */
60    public function addMode($name, ModeInterface $Mode) {
61        if(!isset($this->modes['base'])) {
62            $this->addBaseMode(new Base());
63        }
64        $Mode->Lexer = $this->lexer; // FIXME should be done by setter
65        $this->modes[$name] = $Mode;
66    }
67
68    /**
69     * Connect all modes with each other
70     *
71     * This is the last step before actually parsing.
72     */
73    protected function connectModes() {
74
75        if($this->connected) {
76            return;
77        }
78
79        foreach(array_keys($this->modes) as $mode) {
80            // Base isn't connected to anything
81            if($mode == 'base') {
82                continue;
83            }
84            $this->modes[$mode]->preConnect();
85
86            foreach(array_keys($this->modes) as $cm) {
87
88                if($this->modes[$cm]->accepts($mode)) {
89                    $this->modes[$mode]->connectTo($cm);
90                }
91
92            }
93
94            $this->modes[$mode]->postConnect();
95        }
96
97        $this->connected = true;
98    }
99
100    /**
101     * Parses wiki syntax to instructions
102     *
103     * @param string $doc the wiki syntax text
104     * @return array instructions
105     */
106    public function parse($doc) {
107        $this->connectModes();
108        // Normalize CRs and pad doc
109        $doc = "\n" . str_replace("\r\n", "\n", $doc) . "\n";
110        $this->lexer->parse($doc);
111
112        if (!method_exists($this->handler, 'finalize')) {
113            /** @deprecated 2019-10 we have a legacy handler from a plugin, assume legacy _finalize exists */
114
115            DebugHelper::dbgCustomDeprecationEvent(
116                'finalize()',
117                get_class($this->handler) . '::_finalize()',
118                __METHOD__,
119                __FILE__,
120                __LINE__
121            );
122            $this->handler->_finalize();
123        } else {
124            $this->handler->finalize();
125        }
126        return $this->handler->calls;
127    }
128
129}
130