xref: /dokuwiki/inc/parser/handler.php (revision 95078f236f25cbd6aa6d3e5aae7a3f3d39de6834)
10cecf9d5Sandi<?php
25c2aad12SAndreas Gohr
3cbb44eabSAndreas Gohruse dokuwiki\Extension\Event;
4e1d9dcc8SAndreas Gohruse dokuwiki\Extension\SyntaxPlugin;
5be906b56SAndreas Gohruse dokuwiki\Parsing\Handler\Block;
6be906b56SAndreas Gohruse dokuwiki\Parsing\Handler\CallWriter;
7be906b56SAndreas Gohruse dokuwiki\Parsing\Handler\CallWriterInterface;
8be906b56SAndreas Gohruse dokuwiki\Parsing\Handler\Lists;
9be906b56SAndreas Gohruse dokuwiki\Parsing\Handler\Nest;
10be906b56SAndreas Gohruse dokuwiki\Parsing\Handler\Preformatted;
11be906b56SAndreas Gohruse dokuwiki\Parsing\Handler\Quote;
12be906b56SAndreas Gohruse dokuwiki\Parsing\Handler\Table;
135c2aad12SAndreas Gohr
148b1b81beSAndreas Gohr/**
158b1b81beSAndreas Gohr * Class Doku_Handler
168b1b81beSAndreas Gohr */
17faf3f01bSAndreas Gohrclass Doku_Handler
18faf3f01bSAndreas Gohr{
198b1b81beSAndreas Gohr    /** @var CallWriterInterface */
20faf3f01bSAndreas Gohr    protected $callWriter;
210cecf9d5Sandi
228b1b81beSAndreas Gohr    /** @var array The current CallWriter will write directly to this list of calls, Parser reads it */
23faf3f01bSAndreas Gohr    public $calls = [];
240cecf9d5Sandi
258b1b81beSAndreas Gohr    /** @var array internal status holders for some modes */
26faf3f01bSAndreas Gohr    protected $status = [
2744881bd0Shenning.noren        'section' => false,
28faf3f01bSAndreas Gohr        'doublequote' => 0
29faf3f01bSAndreas Gohr    ];
300cecf9d5Sandi
318b1b81beSAndreas Gohr    /** @var bool should blocks be rewritten? FIXME seems to always be true */
328b1b81beSAndreas Gohr    protected $rewriteBlocks = true;
33b7c441b9SHarry Fuecks
348b1b81beSAndreas Gohr    /**
35ac1d8211SAndreas Gohr     * @var bool are we in a footnote already?
36ac1d8211SAndreas Gohr     */
37ac1d8211SAndreas Gohr    protected $footnote;
38ac1d8211SAndreas Gohr
39ac1d8211SAndreas Gohr    /**
408b1b81beSAndreas Gohr     * Doku_Handler constructor.
418b1b81beSAndreas Gohr     */
42faf3f01bSAndreas Gohr    public function __construct()
43faf3f01bSAndreas Gohr    {
448b1b81beSAndreas Gohr        $this->callWriter = new CallWriter($this);
450cecf9d5Sandi    }
460cecf9d5Sandi
47276820f7SScrutinizer Auto-Fixer    /**
488b1b81beSAndreas Gohr     * Add a new call by passing it to the current CallWriter
498b1b81beSAndreas Gohr     *
508b1b81beSAndreas Gohr     * @param string $handler handler method name (see mode handlers below)
518b1b81beSAndreas Gohr     * @param mixed $args arguments for this call
528b1b81beSAndreas Gohr     * @param int $pos byte position in the original source file
53276820f7SScrutinizer Auto-Fixer     */
54faf3f01bSAndreas Gohr    public function addCall($handler, $args, $pos)
55faf3f01bSAndreas Gohr    {
56faf3f01bSAndreas Gohr        $call = [$handler, $args, $pos];
578b1b81beSAndreas Gohr        $this->callWriter->writeCall($call);
580cecf9d5Sandi    }
590cecf9d5Sandi
60533aca44SAndreas Gohr    /**
61533aca44SAndreas Gohr     * Accessor for the current CallWriter
62533aca44SAndreas Gohr     *
63533aca44SAndreas Gohr     * @return CallWriterInterface
64533aca44SAndreas Gohr     */
65faf3f01bSAndreas Gohr    public function getCallWriter()
66faf3f01bSAndreas Gohr    {
67533aca44SAndreas Gohr        return $this->callWriter;
68533aca44SAndreas Gohr    }
69533aca44SAndreas Gohr
70533aca44SAndreas Gohr    /**
71533aca44SAndreas Gohr     * Set a new CallWriter
72533aca44SAndreas Gohr     *
73533aca44SAndreas Gohr     * @param CallWriterInterface $callWriter
74533aca44SAndreas Gohr     */
75faf3f01bSAndreas Gohr    public function setCallWriter($callWriter)
76faf3f01bSAndreas Gohr    {
77533aca44SAndreas Gohr        $this->callWriter = $callWriter;
78533aca44SAndreas Gohr    }
79533aca44SAndreas Gohr
80eb33b670SAndreas Gohr    /**
81eb33b670SAndreas Gohr     * Return the current internal status of the given name
82eb33b670SAndreas Gohr     *
83eb33b670SAndreas Gohr     * @param string $status
84eb33b670SAndreas Gohr     * @return mixed|null
85eb33b670SAndreas Gohr     */
86faf3f01bSAndreas Gohr    public function getStatus($status)
87faf3f01bSAndreas Gohr    {
88eb33b670SAndreas Gohr        if (!isset($this->status[$status])) return null;
89eb33b670SAndreas Gohr        return $this->status[$status];
90eb33b670SAndreas Gohr    }
91eb33b670SAndreas Gohr
92eb33b670SAndreas Gohr    /**
93eb33b670SAndreas Gohr     * Set a new internal status
94eb33b670SAndreas Gohr     *
95eb33b670SAndreas Gohr     * @param string $status
96eb33b670SAndreas Gohr     * @param mixed $value
97eb33b670SAndreas Gohr     */
98faf3f01bSAndreas Gohr    public function setStatus($status, $value)
99faf3f01bSAndreas Gohr    {
100eb33b670SAndreas Gohr        $this->status[$status] = $value;
101eb33b670SAndreas Gohr    }
102eb33b670SAndreas Gohr
10331c0895aSAndreas Gohr    /** @deprecated 2019-10-31 use addCall() instead */
104faf3f01bSAndreas Gohr    public function _addCall($handler, $args, $pos)
105faf3f01bSAndreas Gohr    {
10631c0895aSAndreas Gohr        dbg_deprecated('addCall');
10731c0895aSAndreas Gohr        $this->addCall($handler, $args, $pos);
10831c0895aSAndreas Gohr    }
10931c0895aSAndreas Gohr
1108b1b81beSAndreas Gohr    /**
1118b1b81beSAndreas Gohr     * Similar to addCall, but adds a plugin call
1128b1b81beSAndreas Gohr     *
1138b1b81beSAndreas Gohr     * @param string $plugin name of the plugin
1148b1b81beSAndreas Gohr     * @param mixed $args arguments for this call
1158b1b81beSAndreas Gohr     * @param int $state a LEXER_STATE_* constant
1168b1b81beSAndreas Gohr     * @param int $pos byte position in the original source file
1178b1b81beSAndreas Gohr     * @param string $match matched syntax
1188b1b81beSAndreas Gohr     */
119faf3f01bSAndreas Gohr    public function addPluginCall($plugin, $args, $state, $pos, $match)
120faf3f01bSAndreas Gohr    {
121faf3f01bSAndreas Gohr        $call = ['plugin', [$plugin, $args, $state, $match], $pos];
1228b1b81beSAndreas Gohr        $this->callWriter->writeCall($call);
12304ebd214Schris    }
12404ebd214Schris
1258b1b81beSAndreas Gohr    /**
1268b1b81beSAndreas Gohr     * Finishes handling
1278b1b81beSAndreas Gohr     *
1288b1b81beSAndreas Gohr     * Called from the parser. Calls finalise() on the call writer, closes open
1298b1b81beSAndreas Gohr     * sections, rewrites blocks and adds document_start and document_end calls.
1308b1b81beSAndreas Gohr     *
1318b1b81beSAndreas Gohr     * @triggers PARSER_HANDLER_DONE
1328b1b81beSAndreas Gohr     */
133faf3f01bSAndreas Gohr    public function finalize()
134faf3f01bSAndreas Gohr    {
1358b1b81beSAndreas Gohr        $this->callWriter->finalise();
136f4f02a0fSchris
137e1c10e4dSchris        if ($this->status['section']) {
138e1c10e4dSchris            $last_call = end($this->calls);
139faf3f01bSAndreas Gohr            $this->calls[] = ['section_close', [], $last_call[2]];
1400cecf9d5Sandi        }
1410cecf9d5Sandi
142b7c441b9SHarry Fuecks        if ($this->rewriteBlocks) {
1435c2aad12SAndreas Gohr            $B = new Block();
1440cecf9d5Sandi            $this->calls = $B->process($this->calls);
145b7c441b9SHarry Fuecks        }
146e0ad864eSchris
147cbb44eabSAndreas Gohr        Event::createAndTrigger('PARSER_HANDLER_DONE', $this);
1480cecf9d5Sandi
149faf3f01bSAndreas Gohr        array_unshift($this->calls, ['document_start', [], 0]);
1500cecf9d5Sandi        $last_call = end($this->calls);
151faf3f01bSAndreas Gohr        $this->calls[] = ['document_end', [], $last_call[2]];
1520cecf9d5Sandi    }
1530cecf9d5Sandi
1549e491c01SAndreas Gohr    /**
1559e491c01SAndreas Gohr     * fetch the current call and advance the pointer to the next one
1569e491c01SAndreas Gohr     *
1578b1b81beSAndreas Gohr     * @fixme seems to be unused?
1589e491c01SAndreas Gohr     * @return bool|mixed
1599e491c01SAndreas Gohr     */
160faf3f01bSAndreas Gohr    public function fetch()
161faf3f01bSAndreas Gohr    {
1629e491c01SAndreas Gohr        $call = current($this->calls);
1639e491c01SAndreas Gohr        if ($call !== false) {
1649e491c01SAndreas Gohr            next($this->calls); //advance the pointer
1659e491c01SAndreas Gohr            return $call;
1660cecf9d5Sandi        }
16744881bd0Shenning.noren        return false;
1680cecf9d5Sandi    }
169ee20e7d1Sandi
170ee20e7d1Sandi
171ee20e7d1Sandi    /**
172e2d88156SLarsDW223     * Internal function for parsing highlight options.
173e2d88156SLarsDW223     * $options is parsed for key value pairs separated by commas.
174e2d88156SLarsDW223     * A value might also be missing in which case the value will simple
175e2d88156SLarsDW223     * be set to true. Commas in strings are ignored, e.g. option="4,56"
176e2d88156SLarsDW223     * will work as expected and will only create one entry.
177e2d88156SLarsDW223     *
17854f741e8SAndreas Gohr     * @param string $options space separated list of key-value pairs,
179e2d88156SLarsDW223     *                        e.g. option1=123, option2="456"
180e2d88156SLarsDW223     * @return array|null     Array of key-value pairs $array['key'] = 'value';
181e2d88156SLarsDW223     *                        or null if no entries found
182e2d88156SLarsDW223     */
183faf3f01bSAndreas Gohr    protected function parse_highlight_options($options)
184faf3f01bSAndreas Gohr    {
185faf3f01bSAndreas Gohr        $result = [];
18654f741e8SAndreas Gohr        preg_match_all('/(\w+(?:="[^"]*"))|(\w+(?:=[^\s]*))|(\w+[^=\s\]])(?:\s*)/', $options, $matches, PREG_SET_ORDER);
187e2d88156SLarsDW223        foreach ($matches as $match) {
188e2d88156SLarsDW223            $equal_sign = strpos($match [0], '=');
189e2d88156SLarsDW223            if ($equal_sign === false) {
19054f741e8SAndreas Gohr                $key = trim($match[0]);
191e2d88156SLarsDW223                $result [$key] = 1;
192e2d88156SLarsDW223            } else {
193e2d88156SLarsDW223                $key = substr($match[0], 0, $equal_sign);
194e2d88156SLarsDW223                $value = substr($match[0], $equal_sign + 1);
195e2d88156SLarsDW223                $value = trim($value, '"');
196e2d88156SLarsDW223                if (strlen($value) > 0) {
197e2d88156SLarsDW223                    $result [$key] = $value;
198e2d88156SLarsDW223                } else {
199e2d88156SLarsDW223                    $result [$key] = 1;
200e2d88156SLarsDW223                }
201e2d88156SLarsDW223            }
202e2d88156SLarsDW223        }
203e2d88156SLarsDW223
204e2d88156SLarsDW223        // Check for supported options
205e2d88156SLarsDW223        $result = array_intersect_key(
206e2d88156SLarsDW223            $result,
207faf3f01bSAndreas Gohr            array_flip([
208e2d88156SLarsDW223                'enable_line_numbers',
209e2d88156SLarsDW223                'start_line_numbers_at',
210e2d88156SLarsDW223                'highlight_lines_extra',
211faf3f01bSAndreas Gohr                'enable_keyword_links'
212faf3f01bSAndreas Gohr            ])
213e2d88156SLarsDW223        );
214e2d88156SLarsDW223
215e2d88156SLarsDW223        // Sanitize values
216e2d88156SLarsDW223        if (isset($result['enable_line_numbers'])) {
21754f741e8SAndreas Gohr            if ($result['enable_line_numbers'] === 'false') {
21854f741e8SAndreas Gohr                $result['enable_line_numbers'] = false;
21954f741e8SAndreas Gohr            }
220e2d88156SLarsDW223            $result['enable_line_numbers'] = (bool)$result['enable_line_numbers'];
221e2d88156SLarsDW223        }
222e2d88156SLarsDW223        if (isset($result['highlight_lines_extra'])) {
223e2d88156SLarsDW223            $result['highlight_lines_extra'] = array_map('intval', explode(',', $result['highlight_lines_extra']));
224e2d88156SLarsDW223            $result['highlight_lines_extra'] = array_filter($result['highlight_lines_extra']);
225e2d88156SLarsDW223            $result['highlight_lines_extra'] = array_unique($result['highlight_lines_extra']);
226e2d88156SLarsDW223        }
227e2d88156SLarsDW223        if (isset($result['start_line_numbers_at'])) {
228e2d88156SLarsDW223            $result['start_line_numbers_at'] = (int)$result['start_line_numbers_at'];
229e2d88156SLarsDW223        }
230e2d88156SLarsDW223        if (isset($result['enable_keyword_links'])) {
23154f741e8SAndreas Gohr            if ($result['enable_keyword_links'] === 'false') {
23254f741e8SAndreas Gohr                $result['enable_keyword_links'] = false;
23354f741e8SAndreas Gohr            }
23454f741e8SAndreas Gohr            $result['enable_keyword_links'] = (bool)$result['enable_keyword_links'];
235e2d88156SLarsDW223        }
236e2d88156SLarsDW223        if (count($result) == 0) {
237e2d88156SLarsDW223            return null;
238e2d88156SLarsDW223        }
239e2d88156SLarsDW223
240e2d88156SLarsDW223        return $result;
241e2d88156SLarsDW223    }
242e2d88156SLarsDW223
2438b1b81beSAndreas Gohr    /**
2448b1b81beSAndreas Gohr     * Simplifies handling for the formatting tags which all behave the same
2458b1b81beSAndreas Gohr     *
2468b1b81beSAndreas Gohr     * @param string $match matched syntax
2478b1b81beSAndreas Gohr     * @param int $state a LEXER_STATE_* constant
2488b1b81beSAndreas Gohr     * @param int $pos byte position in the original source file
2498b1b81beSAndreas Gohr     * @param string $name actual mode name
2508b1b81beSAndreas Gohr     */
251faf3f01bSAndreas Gohr    protected function nestingTag($match, $state, $pos, $name)
252faf3f01bSAndreas Gohr    {
2538b1b81beSAndreas Gohr        switch ($state) {
2548b1b81beSAndreas Gohr            case DOKU_LEXER_ENTER:
255faf3f01bSAndreas Gohr                $this->addCall($name . '_open', [], $pos);
2568b1b81beSAndreas Gohr                break;
2578b1b81beSAndreas Gohr            case DOKU_LEXER_EXIT:
258faf3f01bSAndreas Gohr                $this->addCall($name . '_close', [], $pos);
2598b1b81beSAndreas Gohr                break;
2608b1b81beSAndreas Gohr            case DOKU_LEXER_UNMATCHED:
261faf3f01bSAndreas Gohr                $this->addCall('cdata', [$match], $pos);
2628b1b81beSAndreas Gohr                break;
2638b1b81beSAndreas Gohr        }
2648b1b81beSAndreas Gohr    }
2658b1b81beSAndreas Gohr
2668b1b81beSAndreas Gohr
2678b1b81beSAndreas Gohr    /**
2688b1b81beSAndreas Gohr     * The following methods define the handlers for the different Syntax modes
2698b1b81beSAndreas Gohr     *
270be906b56SAndreas Gohr     * The handlers are called from dokuwiki\Parsing\Lexer\Lexer\invokeParser()
2718b1b81beSAndreas Gohr     *
2728b1b81beSAndreas Gohr     * @todo it might make sense to move these into their own class or merge them with the
2738b1b81beSAndreas Gohr     *       ParserMode classes some time.
2748b1b81beSAndreas Gohr     */
2758b1b81beSAndreas Gohr    // region mode handlers
2768b1b81beSAndreas Gohr
2778b1b81beSAndreas Gohr    /**
2788b1b81beSAndreas Gohr     * Special plugin handler
2798b1b81beSAndreas Gohr     *
2808b1b81beSAndreas Gohr     * This handler is called for all modes starting with 'plugin_'.
2818b1b81beSAndreas Gohr     * An additional parameter with the plugin name is passed. The plugin's handle()
2828b1b81beSAndreas Gohr     * method is called here
2838b1b81beSAndreas Gohr     *
2848b1b81beSAndreas Gohr     * @param string $match matched syntax
2858b1b81beSAndreas Gohr     * @param int $state a LEXER_STATE_* constant
2868b1b81beSAndreas Gohr     * @param int $pos byte position in the original source file
2878b1b81beSAndreas Gohr     * @param string $pluginname name of the plugin
2888b1b81beSAndreas Gohr     * @return bool mode handled?
289faf3f01bSAndreas Gohr     * @author Andreas Gohr <andi@splitbrain.org>
290faf3f01bSAndreas Gohr     *
2918b1b81beSAndreas Gohr     */
292faf3f01bSAndreas Gohr    public function plugin($match, $state, $pos, $pluginname)
293faf3f01bSAndreas Gohr    {
294faf3f01bSAndreas Gohr        $data = [$match];
295e1d9dcc8SAndreas Gohr        /** @var SyntaxPlugin $plugin */
2968b1b81beSAndreas Gohr        $plugin = plugin_load('syntax', $pluginname);
2978b1b81beSAndreas Gohr        if ($plugin != null) {
2988b1b81beSAndreas Gohr            $data = $plugin->handle($match, $state, $pos, $this);
2998b1b81beSAndreas Gohr        }
3008b1b81beSAndreas Gohr        if ($data !== false) {
3018b1b81beSAndreas Gohr            $this->addPluginCall($pluginname, $data, $state, $pos, $match);
3028b1b81beSAndreas Gohr        }
3038b1b81beSAndreas Gohr        return true;
3048b1b81beSAndreas Gohr    }
3058b1b81beSAndreas Gohr
3068b1b81beSAndreas Gohr    /**
3078b1b81beSAndreas Gohr     * @param string $match matched syntax
3088b1b81beSAndreas Gohr     * @param int $state a LEXER_STATE_* constant
3098b1b81beSAndreas Gohr     * @param int $pos byte position in the original source file
3108b1b81beSAndreas Gohr     * @return bool mode handled?
3118b1b81beSAndreas Gohr     */
312faf3f01bSAndreas Gohr    public function base($match, $state, $pos)
313faf3f01bSAndreas Gohr    {
314faf3f01bSAndreas Gohr        if ($state === DOKU_LEXER_UNMATCHED) {
315faf3f01bSAndreas Gohr            $this->addCall('cdata', [$match], $pos);
3168b1b81beSAndreas Gohr            return true;
3178b1b81beSAndreas Gohr        }
3188b1b81beSAndreas Gohr        return false;
3198b1b81beSAndreas Gohr    }
3208b1b81beSAndreas Gohr
3218b1b81beSAndreas Gohr    /**
3228b1b81beSAndreas Gohr     * @param string $match matched syntax
3238b1b81beSAndreas Gohr     * @param int $state a LEXER_STATE_* constant
3248b1b81beSAndreas Gohr     * @param int $pos byte position in the original source file
3258b1b81beSAndreas Gohr     * @return bool mode handled?
3268b1b81beSAndreas Gohr     */
327faf3f01bSAndreas Gohr    public function header($match, $state, $pos)
328faf3f01bSAndreas Gohr    {
3298b1b81beSAndreas Gohr        // get level and title
3308b1b81beSAndreas Gohr        $title = trim($match);
3318b1b81beSAndreas Gohr        $level = 7 - strspn($title, '=');
3328b1b81beSAndreas Gohr        if ($level < 1) $level = 1;
3338b1b81beSAndreas Gohr        $title = trim($title, '=');
3348b1b81beSAndreas Gohr        $title = trim($title);
3358b1b81beSAndreas Gohr
336faf3f01bSAndreas Gohr        if ($this->status['section']) $this->addCall('section_close', [], $pos);
3378b1b81beSAndreas Gohr
338faf3f01bSAndreas Gohr        $this->addCall('header', [$title, $level, $pos], $pos);
3398b1b81beSAndreas Gohr
340faf3f01bSAndreas Gohr        $this->addCall('section_open', [$level], $pos);
3418b1b81beSAndreas Gohr        $this->status['section'] = true;
3428b1b81beSAndreas Gohr        return true;
3438b1b81beSAndreas Gohr    }
3448b1b81beSAndreas Gohr
3458b1b81beSAndreas Gohr    /**
3468b1b81beSAndreas Gohr     * @param string $match matched syntax
3478b1b81beSAndreas Gohr     * @param int $state a LEXER_STATE_* constant
3488b1b81beSAndreas Gohr     * @param int $pos byte position in the original source file
3498b1b81beSAndreas Gohr     * @return bool mode handled?
3508b1b81beSAndreas Gohr     */
351faf3f01bSAndreas Gohr    public function notoc($match, $state, $pos)
352faf3f01bSAndreas Gohr    {
353faf3f01bSAndreas Gohr        $this->addCall('notoc', [], $pos);
3548b1b81beSAndreas Gohr        return true;
3558b1b81beSAndreas Gohr    }
3568b1b81beSAndreas Gohr
3578b1b81beSAndreas Gohr    /**
3588b1b81beSAndreas Gohr     * @param string $match matched syntax
3598b1b81beSAndreas Gohr     * @param int $state a LEXER_STATE_* constant
3608b1b81beSAndreas Gohr     * @param int $pos byte position in the original source file
3618b1b81beSAndreas Gohr     * @return bool mode handled?
3628b1b81beSAndreas Gohr     */
363faf3f01bSAndreas Gohr    public function nocache($match, $state, $pos)
364faf3f01bSAndreas Gohr    {
365faf3f01bSAndreas Gohr        $this->addCall('nocache', [], $pos);
3668b1b81beSAndreas Gohr        return true;
3678b1b81beSAndreas Gohr    }
3688b1b81beSAndreas Gohr
3698b1b81beSAndreas Gohr    /**
3708b1b81beSAndreas Gohr     * @param string $match matched syntax
3718b1b81beSAndreas Gohr     * @param int $state a LEXER_STATE_* constant
3728b1b81beSAndreas Gohr     * @param int $pos byte position in the original source file
3738b1b81beSAndreas Gohr     * @return bool mode handled?
3748b1b81beSAndreas Gohr     */
375faf3f01bSAndreas Gohr    public function linebreak($match, $state, $pos)
376faf3f01bSAndreas Gohr    {
377faf3f01bSAndreas Gohr        $this->addCall('linebreak', [], $pos);
3788b1b81beSAndreas Gohr        return true;
3798b1b81beSAndreas Gohr    }
3808b1b81beSAndreas Gohr
3818b1b81beSAndreas Gohr    /**
3828b1b81beSAndreas Gohr     * @param string $match matched syntax
3838b1b81beSAndreas Gohr     * @param int $state a LEXER_STATE_* constant
3848b1b81beSAndreas Gohr     * @param int $pos byte position in the original source file
3858b1b81beSAndreas Gohr     * @return bool mode handled?
3868b1b81beSAndreas Gohr     */
387faf3f01bSAndreas Gohr    public function eol($match, $state, $pos)
388faf3f01bSAndreas Gohr    {
389faf3f01bSAndreas Gohr        $this->addCall('eol', [], $pos);
3908b1b81beSAndreas Gohr        return true;
3918b1b81beSAndreas Gohr    }
3928b1b81beSAndreas Gohr
3938b1b81beSAndreas Gohr    /**
3948b1b81beSAndreas Gohr     * @param string $match matched syntax
3958b1b81beSAndreas Gohr     * @param int $state a LEXER_STATE_* constant
3968b1b81beSAndreas Gohr     * @param int $pos byte position in the original source file
3978b1b81beSAndreas Gohr     * @return bool mode handled?
3988b1b81beSAndreas Gohr     */
399faf3f01bSAndreas Gohr    public function hr($match, $state, $pos)
400faf3f01bSAndreas Gohr    {
401faf3f01bSAndreas Gohr        $this->addCall('hr', [], $pos);
4028b1b81beSAndreas Gohr        return true;
4038b1b81beSAndreas Gohr    }
4048b1b81beSAndreas Gohr
4058b1b81beSAndreas Gohr    /**
4068b1b81beSAndreas Gohr     * @param string $match matched syntax
4078b1b81beSAndreas Gohr     * @param int $state a LEXER_STATE_* constant
4088b1b81beSAndreas Gohr     * @param int $pos byte position in the original source file
4098b1b81beSAndreas Gohr     * @return bool mode handled?
4108b1b81beSAndreas Gohr     */
411faf3f01bSAndreas Gohr    public function strong($match, $state, $pos)
412faf3f01bSAndreas Gohr    {
4138b1b81beSAndreas Gohr        $this->nestingTag($match, $state, $pos, 'strong');
4148b1b81beSAndreas Gohr        return true;
4158b1b81beSAndreas Gohr    }
4168b1b81beSAndreas Gohr
4178b1b81beSAndreas Gohr    /**
4188b1b81beSAndreas Gohr     * @param string $match matched syntax
4198b1b81beSAndreas Gohr     * @param int $state a LEXER_STATE_* constant
4208b1b81beSAndreas Gohr     * @param int $pos byte position in the original source file
4218b1b81beSAndreas Gohr     * @return bool mode handled?
4228b1b81beSAndreas Gohr     */
423faf3f01bSAndreas Gohr    public function emphasis($match, $state, $pos)
424faf3f01bSAndreas Gohr    {
4258b1b81beSAndreas Gohr        $this->nestingTag($match, $state, $pos, 'emphasis');
4268b1b81beSAndreas Gohr        return true;
4278b1b81beSAndreas Gohr    }
4288b1b81beSAndreas Gohr
4298b1b81beSAndreas Gohr    /**
4308b1b81beSAndreas Gohr     * @param string $match matched syntax
4318b1b81beSAndreas Gohr     * @param int $state a LEXER_STATE_* constant
4328b1b81beSAndreas Gohr     * @param int $pos byte position in the original source file
4338b1b81beSAndreas Gohr     * @return bool mode handled?
4348b1b81beSAndreas Gohr     */
435faf3f01bSAndreas Gohr    public function underline($match, $state, $pos)
436faf3f01bSAndreas Gohr    {
4378b1b81beSAndreas Gohr        $this->nestingTag($match, $state, $pos, 'underline');
4388b1b81beSAndreas Gohr        return true;
4398b1b81beSAndreas Gohr    }
4408b1b81beSAndreas Gohr
4418b1b81beSAndreas Gohr    /**
4428b1b81beSAndreas Gohr     * @param string $match matched syntax
4438b1b81beSAndreas Gohr     * @param int $state a LEXER_STATE_* constant
4448b1b81beSAndreas Gohr     * @param int $pos byte position in the original source file
4458b1b81beSAndreas Gohr     * @return bool mode handled?
4468b1b81beSAndreas Gohr     */
447faf3f01bSAndreas Gohr    public function monospace($match, $state, $pos)
448faf3f01bSAndreas Gohr    {
4498b1b81beSAndreas Gohr        $this->nestingTag($match, $state, $pos, 'monospace');
4508b1b81beSAndreas Gohr        return true;
4518b1b81beSAndreas Gohr    }
4528b1b81beSAndreas Gohr
4538b1b81beSAndreas Gohr    /**
4548b1b81beSAndreas Gohr     * @param string $match matched syntax
4558b1b81beSAndreas Gohr     * @param int $state a LEXER_STATE_* constant
4568b1b81beSAndreas Gohr     * @param int $pos byte position in the original source file
4578b1b81beSAndreas Gohr     * @return bool mode handled?
4588b1b81beSAndreas Gohr     */
459faf3f01bSAndreas Gohr    public function subscript($match, $state, $pos)
460faf3f01bSAndreas Gohr    {
4618b1b81beSAndreas Gohr        $this->nestingTag($match, $state, $pos, 'subscript');
4628b1b81beSAndreas Gohr        return true;
4638b1b81beSAndreas Gohr    }
4648b1b81beSAndreas Gohr
4658b1b81beSAndreas Gohr    /**
4668b1b81beSAndreas Gohr     * @param string $match matched syntax
4678b1b81beSAndreas Gohr     * @param int $state a LEXER_STATE_* constant
4688b1b81beSAndreas Gohr     * @param int $pos byte position in the original source file
4698b1b81beSAndreas Gohr     * @return bool mode handled?
4708b1b81beSAndreas Gohr     */
471faf3f01bSAndreas Gohr    public function superscript($match, $state, $pos)
472faf3f01bSAndreas Gohr    {
4738b1b81beSAndreas Gohr        $this->nestingTag($match, $state, $pos, 'superscript');
4748b1b81beSAndreas Gohr        return true;
4758b1b81beSAndreas Gohr    }
4768b1b81beSAndreas Gohr
4778b1b81beSAndreas Gohr    /**
4788b1b81beSAndreas Gohr     * @param string $match matched syntax
4798b1b81beSAndreas Gohr     * @param int $state a LEXER_STATE_* constant
4808b1b81beSAndreas Gohr     * @param int $pos byte position in the original source file
4818b1b81beSAndreas Gohr     * @return bool mode handled?
4828b1b81beSAndreas Gohr     */
483faf3f01bSAndreas Gohr    public function deleted($match, $state, $pos)
484faf3f01bSAndreas Gohr    {
4858b1b81beSAndreas Gohr        $this->nestingTag($match, $state, $pos, 'deleted');
4868b1b81beSAndreas Gohr        return true;
4878b1b81beSAndreas Gohr    }
4888b1b81beSAndreas Gohr
4898b1b81beSAndreas Gohr    /**
4908b1b81beSAndreas Gohr     * @param string $match matched syntax
4918b1b81beSAndreas Gohr     * @param int $state a LEXER_STATE_* constant
4928b1b81beSAndreas Gohr     * @param int $pos byte position in the original source file
4938b1b81beSAndreas Gohr     * @return bool mode handled?
4948b1b81beSAndreas Gohr     */
495faf3f01bSAndreas Gohr    public function footnote($match, $state, $pos)
496faf3f01bSAndreas Gohr    {
497ac1d8211SAndreas Gohr        if (!isset($this->footnote)) $this->footnote = false;
4988b1b81beSAndreas Gohr
4998b1b81beSAndreas Gohr        switch ($state) {
5008b1b81beSAndreas Gohr            case DOKU_LEXER_ENTER:
5018b1b81beSAndreas Gohr                // footnotes can not be nested - however due to limitations in lexer it can't be prevented
5028b1b81beSAndreas Gohr                // we will still enter a new footnote mode, we just do nothing
503ac1d8211SAndreas Gohr                if ($this->footnote) {
504faf3f01bSAndreas Gohr                    $this->addCall('cdata', [$match], $pos);
5058b1b81beSAndreas Gohr                    break;
5068b1b81beSAndreas Gohr                }
507ac1d8211SAndreas Gohr                $this->footnote = true;
5088b1b81beSAndreas Gohr
5098b1b81beSAndreas Gohr                $this->callWriter = new Nest($this->callWriter, 'footnote_close');
510faf3f01bSAndreas Gohr                $this->addCall('footnote_open', [], $pos);
5118b1b81beSAndreas Gohr                break;
5128b1b81beSAndreas Gohr            case DOKU_LEXER_EXIT:
5138b1b81beSAndreas Gohr                // check whether we have already exitted the footnote mode, can happen if the modes were nested
514ac1d8211SAndreas Gohr                if (!$this->footnote) {
515faf3f01bSAndreas Gohr                    $this->addCall('cdata', [$match], $pos);
5168b1b81beSAndreas Gohr                    break;
5178b1b81beSAndreas Gohr                }
5188b1b81beSAndreas Gohr
519ac1d8211SAndreas Gohr                $this->footnote = false;
520faf3f01bSAndreas Gohr                $this->addCall('footnote_close', [], $pos);
5218b1b81beSAndreas Gohr
5228b1b81beSAndreas Gohr                /** @var Nest $reWriter */
5238b1b81beSAndreas Gohr                $reWriter = $this->callWriter;
5248b1b81beSAndreas Gohr                $this->callWriter = $reWriter->process();
5258b1b81beSAndreas Gohr                break;
5268b1b81beSAndreas Gohr            case DOKU_LEXER_UNMATCHED:
527faf3f01bSAndreas Gohr                $this->addCall('cdata', [$match], $pos);
5288b1b81beSAndreas Gohr                break;
5298b1b81beSAndreas Gohr        }
5308b1b81beSAndreas Gohr        return true;
5318b1b81beSAndreas Gohr    }
5328b1b81beSAndreas Gohr
5338b1b81beSAndreas Gohr    /**
5348b1b81beSAndreas Gohr     * @param string $match matched syntax
5358b1b81beSAndreas Gohr     * @param int $state a LEXER_STATE_* constant
5368b1b81beSAndreas Gohr     * @param int $pos byte position in the original source file
5378b1b81beSAndreas Gohr     * @return bool mode handled?
5388b1b81beSAndreas Gohr     */
539faf3f01bSAndreas Gohr    public function listblock($match, $state, $pos)
540faf3f01bSAndreas Gohr    {
5418b1b81beSAndreas Gohr        switch ($state) {
5428b1b81beSAndreas Gohr            case DOKU_LEXER_ENTER:
5438b1b81beSAndreas Gohr                $this->callWriter = new Lists($this->callWriter);
544faf3f01bSAndreas Gohr                $this->addCall('list_open', [$match], $pos);
5458b1b81beSAndreas Gohr                break;
5468b1b81beSAndreas Gohr            case DOKU_LEXER_EXIT:
547faf3f01bSAndreas Gohr                $this->addCall('list_close', [], $pos);
5488b1b81beSAndreas Gohr                /** @var Lists $reWriter */
5498b1b81beSAndreas Gohr                $reWriter = $this->callWriter;
5508b1b81beSAndreas Gohr                $this->callWriter = $reWriter->process();
5518b1b81beSAndreas Gohr                break;
5528b1b81beSAndreas Gohr            case DOKU_LEXER_MATCHED:
553faf3f01bSAndreas Gohr                $this->addCall('list_item', [$match], $pos);
5548b1b81beSAndreas Gohr                break;
5558b1b81beSAndreas Gohr            case DOKU_LEXER_UNMATCHED:
556faf3f01bSAndreas Gohr                $this->addCall('cdata', [$match], $pos);
5578b1b81beSAndreas Gohr                break;
5588b1b81beSAndreas Gohr        }
5598b1b81beSAndreas Gohr        return true;
5608b1b81beSAndreas Gohr    }
5618b1b81beSAndreas Gohr
5628b1b81beSAndreas Gohr    /**
5638b1b81beSAndreas Gohr     * @param string $match matched syntax
5648b1b81beSAndreas Gohr     * @param int $state a LEXER_STATE_* constant
5658b1b81beSAndreas Gohr     * @param int $pos byte position in the original source file
5668b1b81beSAndreas Gohr     * @return bool mode handled?
5678b1b81beSAndreas Gohr     */
568faf3f01bSAndreas Gohr    public function unformatted($match, $state, $pos)
569faf3f01bSAndreas Gohr    {
5708b1b81beSAndreas Gohr        if ($state == DOKU_LEXER_UNMATCHED) {
571faf3f01bSAndreas Gohr            $this->addCall('unformatted', [$match], $pos);
5728b1b81beSAndreas Gohr        }
5738b1b81beSAndreas Gohr        return true;
5748b1b81beSAndreas Gohr    }
5758b1b81beSAndreas Gohr
5768b1b81beSAndreas Gohr    /**
5778b1b81beSAndreas Gohr     * @param string $match matched syntax
5788b1b81beSAndreas Gohr     * @param int $state a LEXER_STATE_* constant
5798b1b81beSAndreas Gohr     * @param int $pos byte position in the original source file
5808b1b81beSAndreas Gohr     * @return bool mode handled?
5818b1b81beSAndreas Gohr     */
582faf3f01bSAndreas Gohr    public function preformatted($match, $state, $pos)
583faf3f01bSAndreas Gohr    {
5848b1b81beSAndreas Gohr        switch ($state) {
5858b1b81beSAndreas Gohr            case DOKU_LEXER_ENTER:
5868b1b81beSAndreas Gohr                $this->callWriter = new Preformatted($this->callWriter);
587faf3f01bSAndreas Gohr                $this->addCall('preformatted_start', [], $pos);
5888b1b81beSAndreas Gohr                break;
5898b1b81beSAndreas Gohr            case DOKU_LEXER_EXIT:
590faf3f01bSAndreas Gohr                $this->addCall('preformatted_end', [], $pos);
5918b1b81beSAndreas Gohr                /** @var Preformatted $reWriter */
5928b1b81beSAndreas Gohr                $reWriter = $this->callWriter;
5938b1b81beSAndreas Gohr                $this->callWriter = $reWriter->process();
5948b1b81beSAndreas Gohr                break;
5958b1b81beSAndreas Gohr            case DOKU_LEXER_MATCHED:
596faf3f01bSAndreas Gohr                $this->addCall('preformatted_newline', [], $pos);
5978b1b81beSAndreas Gohr                break;
5988b1b81beSAndreas Gohr            case DOKU_LEXER_UNMATCHED:
599faf3f01bSAndreas Gohr                $this->addCall('preformatted_content', [$match], $pos);
6008b1b81beSAndreas Gohr                break;
6018b1b81beSAndreas Gohr        }
6028b1b81beSAndreas Gohr
6038b1b81beSAndreas Gohr        return true;
6048b1b81beSAndreas Gohr    }
6058b1b81beSAndreas Gohr
6068b1b81beSAndreas Gohr    /**
6078b1b81beSAndreas Gohr     * @param string $match matched syntax
6088b1b81beSAndreas Gohr     * @param int $state a LEXER_STATE_* constant
6098b1b81beSAndreas Gohr     * @param int $pos byte position in the original source file
6108b1b81beSAndreas Gohr     * @return bool mode handled?
6118b1b81beSAndreas Gohr     */
612faf3f01bSAndreas Gohr    public function quote($match, $state, $pos)
613faf3f01bSAndreas Gohr    {
6148b1b81beSAndreas Gohr
6158b1b81beSAndreas Gohr        switch ($state) {
6168b1b81beSAndreas Gohr            case DOKU_LEXER_ENTER:
6178b1b81beSAndreas Gohr                $this->callWriter = new Quote($this->callWriter);
618faf3f01bSAndreas Gohr                $this->addCall('quote_start', [$match], $pos);
6198b1b81beSAndreas Gohr                break;
6208b1b81beSAndreas Gohr
6218b1b81beSAndreas Gohr            case DOKU_LEXER_EXIT:
622faf3f01bSAndreas Gohr                $this->addCall('quote_end', [], $pos);
6238b1b81beSAndreas Gohr                /** @var Lists $reWriter */
6248b1b81beSAndreas Gohr                $reWriter = $this->callWriter;
6258b1b81beSAndreas Gohr                $this->callWriter = $reWriter->process();
6268b1b81beSAndreas Gohr                break;
6278b1b81beSAndreas Gohr
6288b1b81beSAndreas Gohr            case DOKU_LEXER_MATCHED:
629faf3f01bSAndreas Gohr                $this->addCall('quote_newline', [$match], $pos);
6308b1b81beSAndreas Gohr                break;
6318b1b81beSAndreas Gohr
6328b1b81beSAndreas Gohr            case DOKU_LEXER_UNMATCHED:
633faf3f01bSAndreas Gohr                $this->addCall('cdata', [$match], $pos);
6348b1b81beSAndreas Gohr                break;
6358b1b81beSAndreas Gohr        }
6368b1b81beSAndreas Gohr
6378b1b81beSAndreas Gohr        return true;
6388b1b81beSAndreas Gohr    }
6398b1b81beSAndreas Gohr
6408b1b81beSAndreas Gohr    /**
6418b1b81beSAndreas Gohr     * @param string $match matched syntax
6428b1b81beSAndreas Gohr     * @param int $state a LEXER_STATE_* constant
6438b1b81beSAndreas Gohr     * @param int $pos byte position in the original source file
6448b1b81beSAndreas Gohr     * @return bool mode handled?
6458b1b81beSAndreas Gohr     */
646faf3f01bSAndreas Gohr    public function file($match, $state, $pos)
647faf3f01bSAndreas Gohr    {
6483d491f75SAndreas Gohr        return $this->code($match, $state, $pos, 'file');
6493d491f75SAndreas Gohr    }
6503d491f75SAndreas Gohr
6518b1b81beSAndreas Gohr    /**
6528b1b81beSAndreas Gohr     * @param string $match matched syntax
6538b1b81beSAndreas Gohr     * @param int $state a LEXER_STATE_* constant
6548b1b81beSAndreas Gohr     * @param int $pos byte position in the original source file
6558b1b81beSAndreas Gohr     * @param string $type either 'code' or 'file'
6568b1b81beSAndreas Gohr     * @return bool mode handled?
6578b1b81beSAndreas Gohr     */
658faf3f01bSAndreas Gohr    public function code($match, $state, $pos, $type = 'code')
659faf3f01bSAndreas Gohr    {
6603d491f75SAndreas Gohr        if ($state == DOKU_LEXER_UNMATCHED) {
661ec34bb30SAndreas Gohr            $matches = sexplode('>', $match, 2, '');
662e2d88156SLarsDW223            // Cut out variable options enclosed in []
663e2d88156SLarsDW223            preg_match('/\[.*\]/', $matches[0], $options);
664e2d88156SLarsDW223            if (!empty($options[0])) {
665e2d88156SLarsDW223                $matches[0] = str_replace($options[0], '', $matches[0]);
666e2d88156SLarsDW223            }
6670139312bSAdrian Lang            $param = preg_split('/\s+/', $matches[0], 2, PREG_SPLIT_NO_EMPTY);
668faf3f01bSAndreas Gohr            while (count($param) < 2) $param[] = null;
6690139312bSAdrian Lang            // We shortcut html here.
6700139312bSAdrian Lang            if ($param[0] == 'html') $param[0] = 'html4strict';
6710139312bSAdrian Lang            if ($param[0] == '-') $param[0] = null;
6720139312bSAdrian Lang            array_unshift($param, $matches[1]);
673e2d88156SLarsDW223            if (!empty($options[0])) {
674e2d88156SLarsDW223                $param [] = $this->parse_highlight_options($options[0]);
675e2d88156SLarsDW223            }
6768b1b81beSAndreas Gohr            $this->addCall($type, $param, $pos);
6770cecf9d5Sandi        }
67844881bd0Shenning.noren        return true;
6790cecf9d5Sandi    }
6800cecf9d5Sandi
6818b1b81beSAndreas Gohr    /**
6828b1b81beSAndreas Gohr     * @param string $match matched syntax
6838b1b81beSAndreas Gohr     * @param int $state a LEXER_STATE_* constant
6848b1b81beSAndreas Gohr     * @param int $pos byte position in the original source file
6858b1b81beSAndreas Gohr     * @return bool mode handled?
6868b1b81beSAndreas Gohr     */
687faf3f01bSAndreas Gohr    public function acronym($match, $state, $pos)
688faf3f01bSAndreas Gohr    {
689faf3f01bSAndreas Gohr        $this->addCall('acronym', [$match], $pos);
69044881bd0Shenning.noren        return true;
6910cecf9d5Sandi    }
6920cecf9d5Sandi
6938b1b81beSAndreas Gohr    /**
6948b1b81beSAndreas Gohr     * @param string $match matched syntax
6958b1b81beSAndreas Gohr     * @param int $state a LEXER_STATE_* constant
6968b1b81beSAndreas Gohr     * @param int $pos byte position in the original source file
6978b1b81beSAndreas Gohr     * @return bool mode handled?
6988b1b81beSAndreas Gohr     */
699faf3f01bSAndreas Gohr    public function smiley($match, $state, $pos)
700faf3f01bSAndreas Gohr    {
701faf3f01bSAndreas Gohr        $this->addCall('smiley', [$match], $pos);
70244881bd0Shenning.noren        return true;
7030cecf9d5Sandi    }
7040cecf9d5Sandi
7058b1b81beSAndreas Gohr    /**
7068b1b81beSAndreas Gohr     * @param string $match matched syntax
7078b1b81beSAndreas Gohr     * @param int $state a LEXER_STATE_* constant
7088b1b81beSAndreas Gohr     * @param int $pos byte position in the original source file
7098b1b81beSAndreas Gohr     * @return bool mode handled?
7108b1b81beSAndreas Gohr     */
711faf3f01bSAndreas Gohr    public function wordblock($match, $state, $pos)
712faf3f01bSAndreas Gohr    {
713faf3f01bSAndreas Gohr        $this->addCall('wordblock', [$match], $pos);
71444881bd0Shenning.noren        return true;
7150cecf9d5Sandi    }
7160cecf9d5Sandi
7178b1b81beSAndreas Gohr    /**
7188b1b81beSAndreas Gohr     * @param string $match matched syntax
7198b1b81beSAndreas Gohr     * @param int $state a LEXER_STATE_* constant
7208b1b81beSAndreas Gohr     * @param int $pos byte position in the original source file
7218b1b81beSAndreas Gohr     * @return bool mode handled?
7228b1b81beSAndreas Gohr     */
723faf3f01bSAndreas Gohr    public function entity($match, $state, $pos)
724faf3f01bSAndreas Gohr    {
725faf3f01bSAndreas Gohr        $this->addCall('entity', [$match], $pos);
72644881bd0Shenning.noren        return true;
7270cecf9d5Sandi    }
7280cecf9d5Sandi
7298b1b81beSAndreas Gohr    /**
7308b1b81beSAndreas Gohr     * @param string $match matched syntax
7318b1b81beSAndreas Gohr     * @param int $state a LEXER_STATE_* constant
7328b1b81beSAndreas Gohr     * @param int $pos byte position in the original source file
7338b1b81beSAndreas Gohr     * @return bool mode handled?
7348b1b81beSAndreas Gohr     */
735faf3f01bSAndreas Gohr    public function multiplyentity($match, $state, $pos)
736faf3f01bSAndreas Gohr    {
7370cecf9d5Sandi        preg_match_all('/\d+/', $match, $matches);
738faf3f01bSAndreas Gohr        $this->addCall('multiplyentity', [$matches[0][0], $matches[0][1]], $pos);
73944881bd0Shenning.noren        return true;
7400cecf9d5Sandi    }
7410cecf9d5Sandi
7428b1b81beSAndreas Gohr    /**
7438b1b81beSAndreas Gohr     * @param string $match matched syntax
7448b1b81beSAndreas Gohr     * @param int $state a LEXER_STATE_* constant
7458b1b81beSAndreas Gohr     * @param int $pos byte position in the original source file
7468b1b81beSAndreas Gohr     * @return bool mode handled?
7478b1b81beSAndreas Gohr     */
748faf3f01bSAndreas Gohr    public function singlequoteopening($match, $state, $pos)
749faf3f01bSAndreas Gohr    {
750faf3f01bSAndreas Gohr        $this->addCall('singlequoteopening', [], $pos);
75144881bd0Shenning.noren        return true;
7520cecf9d5Sandi    }
7530cecf9d5Sandi
7548b1b81beSAndreas Gohr    /**
7558b1b81beSAndreas Gohr     * @param string $match matched syntax
7568b1b81beSAndreas Gohr     * @param int $state a LEXER_STATE_* constant
7578b1b81beSAndreas Gohr     * @param int $pos byte position in the original source file
7588b1b81beSAndreas Gohr     * @return bool mode handled?
7598b1b81beSAndreas Gohr     */
760faf3f01bSAndreas Gohr    public function singlequoteclosing($match, $state, $pos)
761faf3f01bSAndreas Gohr    {
762faf3f01bSAndreas Gohr        $this->addCall('singlequoteclosing', [], $pos);
76344881bd0Shenning.noren        return true;
7640cecf9d5Sandi    }
7650cecf9d5Sandi
7668b1b81beSAndreas Gohr    /**
7678b1b81beSAndreas Gohr     * @param string $match matched syntax
7688b1b81beSAndreas Gohr     * @param int $state a LEXER_STATE_* constant
7698b1b81beSAndreas Gohr     * @param int $pos byte position in the original source file
7708b1b81beSAndreas Gohr     * @return bool mode handled?
7718b1b81beSAndreas Gohr     */
772faf3f01bSAndreas Gohr    public function apostrophe($match, $state, $pos)
773faf3f01bSAndreas Gohr    {
774faf3f01bSAndreas Gohr        $this->addCall('apostrophe', [], $pos);
77557d757d1SAndreas Gohr        return true;
77657d757d1SAndreas Gohr    }
77757d757d1SAndreas Gohr
7788b1b81beSAndreas Gohr    /**
7798b1b81beSAndreas Gohr     * @param string $match matched syntax
7808b1b81beSAndreas Gohr     * @param int $state a LEXER_STATE_* constant
7818b1b81beSAndreas Gohr     * @param int $pos byte position in the original source file
7828b1b81beSAndreas Gohr     * @return bool mode handled?
7838b1b81beSAndreas Gohr     */
784faf3f01bSAndreas Gohr    public function doublequoteopening($match, $state, $pos)
785faf3f01bSAndreas Gohr    {
786faf3f01bSAndreas Gohr        $this->addCall('doublequoteopening', [], $pos);
787e950d12fSChristopher Smith        $this->status['doublequote']++;
78844881bd0Shenning.noren        return true;
7890cecf9d5Sandi    }
7900cecf9d5Sandi
7918b1b81beSAndreas Gohr    /**
7928b1b81beSAndreas Gohr     * @param string $match matched syntax
7938b1b81beSAndreas Gohr     * @param int $state a LEXER_STATE_* constant
7948b1b81beSAndreas Gohr     * @param int $pos byte position in the original source file
7958b1b81beSAndreas Gohr     * @return bool mode handled?
7968b1b81beSAndreas Gohr     */
797faf3f01bSAndreas Gohr    public function doublequoteclosing($match, $state, $pos)
798faf3f01bSAndreas Gohr    {
799e950d12fSChristopher Smith        if ($this->status['doublequote'] <= 0) {
800e950d12fSChristopher Smith            $this->doublequoteopening($match, $state, $pos);
801e950d12fSChristopher Smith        } else {
802faf3f01bSAndreas Gohr            $this->addCall('doublequoteclosing', [], $pos);
803e950d12fSChristopher Smith            $this->status['doublequote'] = max(0, --$this->status['doublequote']);
804e950d12fSChristopher Smith        }
80544881bd0Shenning.noren        return true;
8060cecf9d5Sandi    }
8070cecf9d5Sandi
8088b1b81beSAndreas Gohr    /**
8098b1b81beSAndreas Gohr     * @param string $match matched syntax
8108b1b81beSAndreas Gohr     * @param int $state a LEXER_STATE_* constant
8118b1b81beSAndreas Gohr     * @param int $pos byte position in the original source file
8128b1b81beSAndreas Gohr     * @return bool mode handled?
8138b1b81beSAndreas Gohr     */
814faf3f01bSAndreas Gohr    public function camelcaselink($match, $state, $pos)
815faf3f01bSAndreas Gohr    {
816faf3f01bSAndreas Gohr        $this->addCall('camelcaselink', [$match], $pos);
81744881bd0Shenning.noren        return true;
8180cecf9d5Sandi    }
8190cecf9d5Sandi
8208b1b81beSAndreas Gohr    /**
8218b1b81beSAndreas Gohr     * @param string $match matched syntax
8228b1b81beSAndreas Gohr     * @param int $state a LEXER_STATE_* constant
8238b1b81beSAndreas Gohr     * @param int $pos byte position in the original source file
8248b1b81beSAndreas Gohr     * @return bool mode handled?
8250cecf9d5Sandi     */
826faf3f01bSAndreas Gohr    public function internallink($match, $state, $pos)
827faf3f01bSAndreas Gohr    {
8280cecf9d5Sandi        // Strip the opening and closing markup
829faf3f01bSAndreas Gohr        $link = preg_replace(['/^\[\[/', '/\]\]$/u'], '', $match);
8300cecf9d5Sandi
8310cecf9d5Sandi        // Split title from URL
832ec34bb30SAndreas Gohr        $link = sexplode('|', $link, 2);
833ec34bb30SAndreas Gohr        if ($link[1] === null) {
8340ea51e63SMatt Perry            $link[1] = null;
8350cecf9d5Sandi        } elseif (preg_match('/^\{\{[^\}]+\}\}$/', $link[1])) {
8365578eb8fSandi            // If the title is an image, convert it to an array containing the image details
837b625487dSandi            $link[1] = Doku_Handler_Parse_Media($link[1]);
8380cecf9d5Sandi        }
8390b7c14c2Sandi        $link[0] = trim($link[0]);
8400cecf9d5Sandi
8410e1c636eSandi        //decide which kind of link it is
8420e1c636eSandi
8436efc45a2SDmitry Katsubo        if (link_isinterwiki($link[0])) {
8440e1c636eSandi            // Interwiki
845ec34bb30SAndreas Gohr            $interwiki = sexplode('>', $link[0], 2, '');
8468b1b81beSAndreas Gohr            $this->addCall(
8470cecf9d5Sandi                'interwikilink',
848faf3f01bSAndreas Gohr                [$link[0], $link[1], strtolower($interwiki[0]), $interwiki[1]],
8490cecf9d5Sandi                $pos
8500cecf9d5Sandi            );
85115f1b77cSAndreas Gohr        } elseif (preg_match('/^\\\\\\\\[^\\\\]+?\\\\/u', $link[0])) {
8520e1c636eSandi            // Windows Share
8538b1b81beSAndreas Gohr            $this->addCall(
8540cecf9d5Sandi                'windowssharelink',
855faf3f01bSAndreas Gohr                [$link[0], $link[1]],
8560cecf9d5Sandi                $pos
8570cecf9d5Sandi            );
8584468cb4cSAndreas Gohr        } elseif (preg_match('#^([a-z0-9\-\.+]+?)://#i', $link[0])) {
8590e1c636eSandi            // external link (accepts all protocols)
8608b1b81beSAndreas Gohr            $this->addCall(
8610cecf9d5Sandi                'externallink',
862faf3f01bSAndreas Gohr                [$link[0], $link[1]],
8630cecf9d5Sandi                $pos
8640cecf9d5Sandi            );
8650a1d30bfSchris        } elseif (preg_match('<' . PREG_PATTERN_VALID_EMAIL . '>', $link[0])) {
8660a1d30bfSchris            // E-Mail (pattern above is defined in inc/mail.php)
8678b1b81beSAndreas Gohr            $this->addCall(
868a6755281Sandi                'emaillink',
869faf3f01bSAndreas Gohr                [$link[0], $link[1]],
870a6755281Sandi                $pos
871a6755281Sandi            );
8720b7c14c2Sandi        } elseif (preg_match('!^#.+!', $link[0])) {
8730b7c14c2Sandi            // local link
8748b1b81beSAndreas Gohr            $this->addCall(
8750b7c14c2Sandi                'locallink',
876faf3f01bSAndreas Gohr                [substr($link[0], 1), $link[1]],
8770b7c14c2Sandi                $pos
8780b7c14c2Sandi            );
8790e1c636eSandi        } else {
8800e1c636eSandi            // internal link
8818b1b81beSAndreas Gohr            $this->addCall(
8820e1c636eSandi                'internallink',
883faf3f01bSAndreas Gohr                [$link[0], $link[1]],
8840e1c636eSandi                $pos
8850e1c636eSandi            );
8860cecf9d5Sandi        }
8870e1c636eSandi
88844881bd0Shenning.noren        return true;
8890cecf9d5Sandi    }
8900cecf9d5Sandi
8918b1b81beSAndreas Gohr    /**
8928b1b81beSAndreas Gohr     * @param string $match matched syntax
8938b1b81beSAndreas Gohr     * @param int $state a LEXER_STATE_* constant
8948b1b81beSAndreas Gohr     * @param int $pos byte position in the original source file
8958b1b81beSAndreas Gohr     * @return bool mode handled?
8968b1b81beSAndreas Gohr     */
897faf3f01bSAndreas Gohr    public function filelink($match, $state, $pos)
898faf3f01bSAndreas Gohr    {
899faf3f01bSAndreas Gohr        $this->addCall('filelink', [$match, null], $pos);
90044881bd0Shenning.noren        return true;
9010cecf9d5Sandi    }
9020cecf9d5Sandi
9038b1b81beSAndreas Gohr    /**
9048b1b81beSAndreas Gohr     * @param string $match matched syntax
9058b1b81beSAndreas Gohr     * @param int $state a LEXER_STATE_* constant
9068b1b81beSAndreas Gohr     * @param int $pos byte position in the original source file
9078b1b81beSAndreas Gohr     * @return bool mode handled?
9088b1b81beSAndreas Gohr     */
909faf3f01bSAndreas Gohr    public function windowssharelink($match, $state, $pos)
910faf3f01bSAndreas Gohr    {
911faf3f01bSAndreas Gohr        $this->addCall('windowssharelink', [$match, null], $pos);
91244881bd0Shenning.noren        return true;
9130cecf9d5Sandi    }
9140cecf9d5Sandi
9158b1b81beSAndreas Gohr    /**
9168b1b81beSAndreas Gohr     * @param string $match matched syntax
9178b1b81beSAndreas Gohr     * @param int $state a LEXER_STATE_* constant
9188b1b81beSAndreas Gohr     * @param int $pos byte position in the original source file
9198b1b81beSAndreas Gohr     * @return bool mode handled?
9208b1b81beSAndreas Gohr     */
921faf3f01bSAndreas Gohr    public function media($match, $state, $pos)
922faf3f01bSAndreas Gohr    {
9230cecf9d5Sandi        $p = Doku_Handler_Parse_Media($match);
9240cecf9d5Sandi
9258b1b81beSAndreas Gohr        $this->addCall(
9260cecf9d5Sandi            $p['type'],
927faf3f01bSAndreas Gohr            [$p['src'], $p['title'], $p['align'], $p['width'], $p['height'], $p['cache'], $p['linking']],
9280cecf9d5Sandi            $pos
9290cecf9d5Sandi        );
93044881bd0Shenning.noren        return true;
9310cecf9d5Sandi    }
9320cecf9d5Sandi
9338b1b81beSAndreas Gohr    /**
9348b1b81beSAndreas Gohr     * @param string $match matched syntax
9358b1b81beSAndreas Gohr     * @param int $state a LEXER_STATE_* constant
9368b1b81beSAndreas Gohr     * @param int $pos byte position in the original source file
9378b1b81beSAndreas Gohr     * @return bool mode handled?
9388b1b81beSAndreas Gohr     */
939faf3f01bSAndreas Gohr    public function rss($match, $state, $pos)
940faf3f01bSAndreas Gohr    {
941faf3f01bSAndreas Gohr        $link = preg_replace(['/^\{\{rss>/', '/\}\}$/'], '', $match);
9423db95becSAndreas Gohr
9433db95becSAndreas Gohr        // get params
944faf3f01bSAndreas Gohr        [$link, $params] = sexplode(' ', $link, 2, '');
9453db95becSAndreas Gohr
946faf3f01bSAndreas Gohr        $p = [];
9473db95becSAndreas Gohr        if (preg_match('/\b(\d+)\b/', $params, $match)) {
9483db95becSAndreas Gohr            $p['max'] = $match[1];
9493db95becSAndreas Gohr        } else {
9503db95becSAndreas Gohr            $p['max'] = 8;
9513db95becSAndreas Gohr        }
9523db95becSAndreas Gohr        $p['reverse'] = (preg_match('/rev/', $params));
9533db95becSAndreas Gohr        $p['author'] = (preg_match('/\b(by|author)/', $params));
9543db95becSAndreas Gohr        $p['date'] = (preg_match('/\b(date)/', $params));
9553db95becSAndreas Gohr        $p['details'] = (preg_match('/\b(desc|detail)/', $params));
95638c6f603SRobin H. Johnson        $p['nosort'] = (preg_match('/\b(nosort)\b/', $params));
9573db95becSAndreas Gohr
9580a69dff7Schris        if (preg_match('/\b(\d+)([dhm])\b/', $params, $match)) {
959faf3f01bSAndreas Gohr            $period = ['d' => 86400, 'h' => 3600, 'm' => 60];
9600a69dff7Schris            $p['refresh'] = max(600, $match[1] * $period[$match[2]]);  // n * period in seconds, minimum 10 minutes
9610a69dff7Schris        } else {
9620a69dff7Schris            $p['refresh'] = 14400;   // default to 4 hours
9630a69dff7Schris        }
9640a69dff7Schris
965faf3f01bSAndreas Gohr        $this->addCall('rss', [$link, $p], $pos);
96644881bd0Shenning.noren        return true;
967b625487dSandi    }
968b625487dSandi
9698b1b81beSAndreas Gohr    /**
9708b1b81beSAndreas Gohr     * @param string $match matched syntax
9718b1b81beSAndreas Gohr     * @param int $state a LEXER_STATE_* constant
9728b1b81beSAndreas Gohr     * @param int $pos byte position in the original source file
9738b1b81beSAndreas Gohr     * @return bool mode handled?
9748b1b81beSAndreas Gohr     */
975faf3f01bSAndreas Gohr    public function externallink($match, $state, $pos)
976faf3f01bSAndreas Gohr    {
977da9f31c5SAndreas Gohr        $url = $match;
978da9f31c5SAndreas Gohr        $title = null;
9790cecf9d5Sandi
980da9f31c5SAndreas Gohr        // add protocol on simple short URLs
981*5c46ca3dSAndreas Gohr        if (str_starts_with($url, 'ftp') && !str_starts_with($url, 'ftp://')) {
982da9f31c5SAndreas Gohr            $title = $url;
983da9f31c5SAndreas Gohr            $url = 'ftp://' . $url;
984da9f31c5SAndreas Gohr        }
985*5c46ca3dSAndreas Gohr        if (str_starts_with($url, 'www')) {
986da9f31c5SAndreas Gohr            $title = $url;
987da9f31c5SAndreas Gohr            $url = 'http://' . $url;
988da9f31c5SAndreas Gohr        }
989da9f31c5SAndreas Gohr
990faf3f01bSAndreas Gohr        $this->addCall('externallink', [$url, $title], $pos);
99144881bd0Shenning.noren        return true;
9920cecf9d5Sandi    }
9930cecf9d5Sandi
9948b1b81beSAndreas Gohr    /**
9958b1b81beSAndreas Gohr     * @param string $match matched syntax
9968b1b81beSAndreas Gohr     * @param int $state a LEXER_STATE_* constant
9978b1b81beSAndreas Gohr     * @param int $pos byte position in the original source file
9988b1b81beSAndreas Gohr     * @return bool mode handled?
9998b1b81beSAndreas Gohr     */
1000faf3f01bSAndreas Gohr    public function emaillink($match, $state, $pos)
1001faf3f01bSAndreas Gohr    {
1002faf3f01bSAndreas Gohr        $email = preg_replace(['/^</', '/>$/'], '', $match);
1003faf3f01bSAndreas Gohr        $this->addCall('emaillink', [$email, null], $pos);
100444881bd0Shenning.noren        return true;
10050cecf9d5Sandi    }
10060cecf9d5Sandi
10078b1b81beSAndreas Gohr    /**
10088b1b81beSAndreas Gohr     * @param string $match matched syntax
10098b1b81beSAndreas Gohr     * @param int $state a LEXER_STATE_* constant
10108b1b81beSAndreas Gohr     * @param int $pos byte position in the original source file
10118b1b81beSAndreas Gohr     * @return bool mode handled?
10128b1b81beSAndreas Gohr     */
1013faf3f01bSAndreas Gohr    public function table($match, $state, $pos)
1014faf3f01bSAndreas Gohr    {
10150cecf9d5Sandi        switch ($state) {
10160cecf9d5Sandi            case DOKU_LEXER_ENTER:
10178b1b81beSAndreas Gohr                $this->callWriter = new Table($this->callWriter);
10180cecf9d5Sandi
1019faf3f01bSAndreas Gohr                $this->addCall('table_start', [$pos + 1], $pos);
10200cecf9d5Sandi                if (trim($match) == '^') {
1021faf3f01bSAndreas Gohr                    $this->addCall('tableheader', [], $pos);
10220cecf9d5Sandi                } else {
1023faf3f01bSAndreas Gohr                    $this->addCall('tablecell', [], $pos);
10240cecf9d5Sandi                }
10250cecf9d5Sandi                break;
10260cecf9d5Sandi
10270cecf9d5Sandi            case DOKU_LEXER_EXIT:
1028faf3f01bSAndreas Gohr                $this->addCall('table_end', [$pos], $pos);
10295c2aad12SAndreas Gohr                /** @var Table $reWriter */
10308b1b81beSAndreas Gohr                $reWriter = $this->callWriter;
10318b1b81beSAndreas Gohr                $this->callWriter = $reWriter->process();
10320cecf9d5Sandi                break;
10330cecf9d5Sandi
10340cecf9d5Sandi            case DOKU_LEXER_UNMATCHED:
10350cecf9d5Sandi                if (trim($match) != '') {
1036faf3f01bSAndreas Gohr                    $this->addCall('cdata', [$match], $pos);
10370cecf9d5Sandi                }
10380cecf9d5Sandi                break;
10390cecf9d5Sandi
10400cecf9d5Sandi            case DOKU_LEXER_MATCHED:
10419ab75d9eSAndreas Gohr                if ($match == ' ') {
1042faf3f01bSAndreas Gohr                    $this->addCall('cdata', [$match], $pos);
104325b97867Shakan.sandell                } elseif (preg_match('/:::/', $match)) {
1044faf3f01bSAndreas Gohr                    $this->addCall('rowspan', [$match], $pos);
1045e205b721SAndreas Gohr                } elseif (preg_match('/\t+/', $match)) {
1046faf3f01bSAndreas Gohr                    $this->addCall('table_align', [$match], $pos);
1047e205b721SAndreas Gohr                } elseif (preg_match('/ {2,}/', $match)) {
1048faf3f01bSAndreas Gohr                    $this->addCall('table_align', [$match], $pos);
10490cecf9d5Sandi                } elseif ($match == "\n|") {
1050faf3f01bSAndreas Gohr                    $this->addCall('table_row', [], $pos);
1051faf3f01bSAndreas Gohr                    $this->addCall('tablecell', [], $pos);
10520cecf9d5Sandi                } elseif ($match == "\n^") {
1053faf3f01bSAndreas Gohr                    $this->addCall('table_row', [], $pos);
1054faf3f01bSAndreas Gohr                    $this->addCall('tableheader', [], $pos);
10550cecf9d5Sandi                } elseif ($match == '|') {
1056faf3f01bSAndreas Gohr                    $this->addCall('tablecell', [], $pos);
10570cecf9d5Sandi                } elseif ($match == '^') {
1058faf3f01bSAndreas Gohr                    $this->addCall('tableheader', [], $pos);
10590cecf9d5Sandi                }
10600cecf9d5Sandi                break;
10610cecf9d5Sandi        }
106244881bd0Shenning.noren        return true;
10630cecf9d5Sandi    }
10648b1b81beSAndreas Gohr
10658b1b81beSAndreas Gohr    // endregion modes
10660cecf9d5Sandi}
10670cecf9d5Sandi
10680cecf9d5Sandi//------------------------------------------------------------------------
1069faf3f01bSAndreas Gohrfunction Doku_Handler_Parse_Media($match)
1070faf3f01bSAndreas Gohr{
1071d8ab8746SAndreas Gohr
10720cecf9d5Sandi    // Strip the opening and closing markup
1073faf3f01bSAndreas Gohr    $link = preg_replace(['/^\{\{/', '/\}\}$/u'], '', $match);
10740cecf9d5Sandi
10750cecf9d5Sandi    // Split title from URL
1076ec34bb30SAndreas Gohr    $link = sexplode('|', $link, 2);
10770cecf9d5Sandi
10780cecf9d5Sandi    // Check alignment
10790cecf9d5Sandi    $ralign = (bool)preg_match('/^ /', $link[0]);
10800cecf9d5Sandi    $lalign = (bool)preg_match('/ $/', $link[0]);
10810cecf9d5Sandi
10820cecf9d5Sandi    // Logic = what's that ;)...
10830cecf9d5Sandi    if ($lalign & $ralign) {
10840cecf9d5Sandi        $align = 'center';
10850cecf9d5Sandi    } elseif ($ralign) {
10860cecf9d5Sandi        $align = 'right';
10870cecf9d5Sandi    } elseif ($lalign) {
10880cecf9d5Sandi        $align = 'left';
10890cecf9d5Sandi    } else {
10900ea51e63SMatt Perry        $align = null;
10910cecf9d5Sandi    }
10920cecf9d5Sandi
10930cecf9d5Sandi    // The title...
10940cecf9d5Sandi    if (!isset($link[1])) {
10950ea51e63SMatt Perry        $link[1] = null;
10960cecf9d5Sandi    }
10970cecf9d5Sandi
10984826ab45Sandi    //remove aligning spaces
10994826ab45Sandi    $link[0] = trim($link[0]);
11000cecf9d5Sandi
11014826ab45Sandi    //split into src and parameters (using the very last questionmark)
11024826ab45Sandi    $pos = strrpos($link[0], '?');
11034826ab45Sandi    if ($pos !== false) {
11044826ab45Sandi        $src = substr($link[0], 0, $pos);
11054826ab45Sandi        $param = substr($link[0], $pos + 1);
11060cecf9d5Sandi    } else {
11074826ab45Sandi        $src = $link[0];
11084826ab45Sandi        $param = '';
11090cecf9d5Sandi    }
11100cecf9d5Sandi
11114826ab45Sandi    //parse width and height
11124826ab45Sandi    if (preg_match('#(\d+)(x(\d+))?#i', $param, $size)) {
1113faf3f01bSAndreas Gohr        $w = empty($size[1]) ? null : $size[1];
1114faf3f01bSAndreas Gohr        $h = empty($size[3]) ? null : $size[3];
1115fc1c55b1Shfuecks    } else {
11160ea51e63SMatt Perry        $w = null;
11170ea51e63SMatt Perry        $h = null;
11180cecf9d5Sandi    }
11190cecf9d5Sandi
1120dc673a5bSjoe.lapp    //get linking command
1121d35ab615Shenning.noren    if (preg_match('/nolink/i', $param)) {
1122dc673a5bSjoe.lapp        $linking = 'nolink';
1123d35ab615Shenning.noren    } elseif (preg_match('/direct/i', $param)) {
1124dc673a5bSjoe.lapp        $linking = 'direct';
11258acb3108SAndreas Gohr    } elseif (preg_match('/linkonly/i', $param)) {
11268acb3108SAndreas Gohr        $linking = 'linkonly';
1127dc673a5bSjoe.lapp    } else {
1128dc673a5bSjoe.lapp        $linking = 'details';
1129dc673a5bSjoe.lapp    }
1130dc673a5bSjoe.lapp
11314826ab45Sandi    //get caching command
11324826ab45Sandi    if (preg_match('/(nocache|recache)/i', $param, $cachemode)) {
11334826ab45Sandi        $cache = $cachemode[1];
11340cecf9d5Sandi    } else {
11354826ab45Sandi        $cache = 'cache';
11360cecf9d5Sandi    }
1137d8ab8746SAndreas Gohr
11386efc45a2SDmitry Katsubo    // Check whether this is a local or remote image or interwiki
11396efc45a2SDmitry Katsubo    if (media_isexternal($src) || link_isinterwiki($src)) {
11404826ab45Sandi        $call = 'externalmedia';
11410cecf9d5Sandi    } else {
11424826ab45Sandi        $call = 'internalmedia';
11430cecf9d5Sandi    }
11440cecf9d5Sandi
1145faf3f01bSAndreas Gohr    $params = [
11460cecf9d5Sandi        'type' => $call,
11474826ab45Sandi        'src' => $src,
11480cecf9d5Sandi        'title' => $link[1],
11490cecf9d5Sandi        'align' => $align,
11504826ab45Sandi        'width' => $w,
11514826ab45Sandi        'height' => $h,
11520cecf9d5Sandi        'cache' => $cache,
1153faf3f01bSAndreas Gohr        'linking' => $linking
1154faf3f01bSAndreas Gohr    ];
11550cecf9d5Sandi
11560cecf9d5Sandi    return $params;
11570cecf9d5Sandi}
1158