xref: /dokuwiki/inc/parser/handler.php (revision faf3f01b9af152b16aca96437c109c41b8250689)
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 */
17*faf3f01bSAndreas Gohrclass Doku_Handler
18*faf3f01bSAndreas Gohr{
198b1b81beSAndreas Gohr    /** @var CallWriterInterface */
20*faf3f01bSAndreas Gohr    protected $callWriter;
210cecf9d5Sandi
228b1b81beSAndreas Gohr    /** @var array The current CallWriter will write directly to this list of calls, Parser reads it */
23*faf3f01bSAndreas Gohr    public $calls = [];
240cecf9d5Sandi
258b1b81beSAndreas Gohr    /** @var array internal status holders for some modes */
26*faf3f01bSAndreas Gohr    protected $status = [
2744881bd0Shenning.noren        'section' => false,
28*faf3f01bSAndreas Gohr        'doublequote' => 0
29*faf3f01bSAndreas 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     */
42*faf3f01bSAndreas Gohr    public function __construct()
43*faf3f01bSAndreas 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     */
54*faf3f01bSAndreas Gohr    public function addCall($handler, $args, $pos)
55*faf3f01bSAndreas Gohr    {
56*faf3f01bSAndreas 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     */
65*faf3f01bSAndreas Gohr    public function getCallWriter()
66*faf3f01bSAndreas 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     */
75*faf3f01bSAndreas Gohr    public function setCallWriter($callWriter)
76*faf3f01bSAndreas 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     */
86*faf3f01bSAndreas Gohr    public function getStatus($status)
87*faf3f01bSAndreas 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     */
98*faf3f01bSAndreas Gohr    public function setStatus($status, $value)
99*faf3f01bSAndreas Gohr    {
100eb33b670SAndreas Gohr        $this->status[$status] = $value;
101eb33b670SAndreas Gohr    }
102eb33b670SAndreas Gohr
10331c0895aSAndreas Gohr    /** @deprecated 2019-10-31 use addCall() instead */
104*faf3f01bSAndreas Gohr    public function _addCall($handler, $args, $pos)
105*faf3f01bSAndreas 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     */
119*faf3f01bSAndreas Gohr    public function addPluginCall($plugin, $args, $state, $pos, $match)
120*faf3f01bSAndreas Gohr    {
121*faf3f01bSAndreas 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     */
133*faf3f01bSAndreas Gohr    public function finalize()
134*faf3f01bSAndreas Gohr    {
1358b1b81beSAndreas Gohr        $this->callWriter->finalise();
136f4f02a0fSchris
137e1c10e4dSchris        if ($this->status['section']) {
138e1c10e4dSchris            $last_call = end($this->calls);
139*faf3f01bSAndreas 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
149*faf3f01bSAndreas Gohr        array_unshift($this->calls, ['document_start', [], 0]);
1500cecf9d5Sandi        $last_call = end($this->calls);
151*faf3f01bSAndreas 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     */
160*faf3f01bSAndreas Gohr    public function fetch()
161*faf3f01bSAndreas 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     */
183*faf3f01bSAndreas Gohr    protected function parse_highlight_options($options)
184*faf3f01bSAndreas Gohr    {
185*faf3f01bSAndreas 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,
207*faf3f01bSAndreas Gohr            array_flip([
208e2d88156SLarsDW223                'enable_line_numbers',
209e2d88156SLarsDW223                'start_line_numbers_at',
210e2d88156SLarsDW223                'highlight_lines_extra',
211*faf3f01bSAndreas Gohr                'enable_keyword_links'
212*faf3f01bSAndreas 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     */
251*faf3f01bSAndreas Gohr    protected function nestingTag($match, $state, $pos, $name)
252*faf3f01bSAndreas Gohr    {
2538b1b81beSAndreas Gohr        switch ($state) {
2548b1b81beSAndreas Gohr            case DOKU_LEXER_ENTER:
255*faf3f01bSAndreas Gohr                $this->addCall($name . '_open', [], $pos);
2568b1b81beSAndreas Gohr                break;
2578b1b81beSAndreas Gohr            case DOKU_LEXER_EXIT:
258*faf3f01bSAndreas Gohr                $this->addCall($name . '_close', [], $pos);
2598b1b81beSAndreas Gohr                break;
2608b1b81beSAndreas Gohr            case DOKU_LEXER_UNMATCHED:
261*faf3f01bSAndreas 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?
289*faf3f01bSAndreas Gohr     * @author Andreas Gohr <andi@splitbrain.org>
290*faf3f01bSAndreas Gohr     *
2918b1b81beSAndreas Gohr     */
292*faf3f01bSAndreas Gohr    public function plugin($match, $state, $pos, $pluginname)
293*faf3f01bSAndreas Gohr    {
294*faf3f01bSAndreas 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     */
312*faf3f01bSAndreas Gohr    public function base($match, $state, $pos)
313*faf3f01bSAndreas Gohr    {
314*faf3f01bSAndreas Gohr        if ($state === DOKU_LEXER_UNMATCHED) {
315*faf3f01bSAndreas 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     */
327*faf3f01bSAndreas Gohr    public function header($match, $state, $pos)
328*faf3f01bSAndreas 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
336*faf3f01bSAndreas Gohr        if ($this->status['section']) $this->addCall('section_close', [], $pos);
3378b1b81beSAndreas Gohr
338*faf3f01bSAndreas Gohr        $this->addCall('header', [$title, $level, $pos], $pos);
3398b1b81beSAndreas Gohr
340*faf3f01bSAndreas 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     */
351*faf3f01bSAndreas Gohr    public function notoc($match, $state, $pos)
352*faf3f01bSAndreas Gohr    {
353*faf3f01bSAndreas 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     */
363*faf3f01bSAndreas Gohr    public function nocache($match, $state, $pos)
364*faf3f01bSAndreas Gohr    {
365*faf3f01bSAndreas 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     */
375*faf3f01bSAndreas Gohr    public function linebreak($match, $state, $pos)
376*faf3f01bSAndreas Gohr    {
377*faf3f01bSAndreas 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     */
387*faf3f01bSAndreas Gohr    public function eol($match, $state, $pos)
388*faf3f01bSAndreas Gohr    {
389*faf3f01bSAndreas 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     */
399*faf3f01bSAndreas Gohr    public function hr($match, $state, $pos)
400*faf3f01bSAndreas Gohr    {
401*faf3f01bSAndreas 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     */
411*faf3f01bSAndreas Gohr    public function strong($match, $state, $pos)
412*faf3f01bSAndreas 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     */
423*faf3f01bSAndreas Gohr    public function emphasis($match, $state, $pos)
424*faf3f01bSAndreas 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     */
435*faf3f01bSAndreas Gohr    public function underline($match, $state, $pos)
436*faf3f01bSAndreas 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     */
447*faf3f01bSAndreas Gohr    public function monospace($match, $state, $pos)
448*faf3f01bSAndreas 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     */
459*faf3f01bSAndreas Gohr    public function subscript($match, $state, $pos)
460*faf3f01bSAndreas 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     */
471*faf3f01bSAndreas Gohr    public function superscript($match, $state, $pos)
472*faf3f01bSAndreas 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     */
483*faf3f01bSAndreas Gohr    public function deleted($match, $state, $pos)
484*faf3f01bSAndreas 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     */
495*faf3f01bSAndreas Gohr    public function footnote($match, $state, $pos)
496*faf3f01bSAndreas 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) {
504*faf3f01bSAndreas 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');
510*faf3f01bSAndreas 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) {
515*faf3f01bSAndreas Gohr                    $this->addCall('cdata', [$match], $pos);
5168b1b81beSAndreas Gohr                    break;
5178b1b81beSAndreas Gohr                }
5188b1b81beSAndreas Gohr
519ac1d8211SAndreas Gohr                $this->footnote = false;
520*faf3f01bSAndreas 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:
527*faf3f01bSAndreas 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     */
539*faf3f01bSAndreas Gohr    public function listblock($match, $state, $pos)
540*faf3f01bSAndreas Gohr    {
5418b1b81beSAndreas Gohr        switch ($state) {
5428b1b81beSAndreas Gohr            case DOKU_LEXER_ENTER:
5438b1b81beSAndreas Gohr                $this->callWriter = new Lists($this->callWriter);
544*faf3f01bSAndreas Gohr                $this->addCall('list_open', [$match], $pos);
5458b1b81beSAndreas Gohr                break;
5468b1b81beSAndreas Gohr            case DOKU_LEXER_EXIT:
547*faf3f01bSAndreas 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:
553*faf3f01bSAndreas Gohr                $this->addCall('list_item', [$match], $pos);
5548b1b81beSAndreas Gohr                break;
5558b1b81beSAndreas Gohr            case DOKU_LEXER_UNMATCHED:
556*faf3f01bSAndreas 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     */
568*faf3f01bSAndreas Gohr    public function unformatted($match, $state, $pos)
569*faf3f01bSAndreas Gohr    {
5708b1b81beSAndreas Gohr        if ($state == DOKU_LEXER_UNMATCHED) {
571*faf3f01bSAndreas 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     */
582*faf3f01bSAndreas Gohr    public function preformatted($match, $state, $pos)
583*faf3f01bSAndreas Gohr    {
5848b1b81beSAndreas Gohr        switch ($state) {
5858b1b81beSAndreas Gohr            case DOKU_LEXER_ENTER:
5868b1b81beSAndreas Gohr                $this->callWriter = new Preformatted($this->callWriter);
587*faf3f01bSAndreas Gohr                $this->addCall('preformatted_start', [], $pos);
5888b1b81beSAndreas Gohr                break;
5898b1b81beSAndreas Gohr            case DOKU_LEXER_EXIT:
590*faf3f01bSAndreas 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:
596*faf3f01bSAndreas Gohr                $this->addCall('preformatted_newline', [], $pos);
5978b1b81beSAndreas Gohr                break;
5988b1b81beSAndreas Gohr            case DOKU_LEXER_UNMATCHED:
599*faf3f01bSAndreas 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     */
612*faf3f01bSAndreas Gohr    public function quote($match, $state, $pos)
613*faf3f01bSAndreas Gohr    {
6148b1b81beSAndreas Gohr
6158b1b81beSAndreas Gohr        switch ($state) {
6168b1b81beSAndreas Gohr
6178b1b81beSAndreas Gohr            case DOKU_LEXER_ENTER:
6188b1b81beSAndreas Gohr                $this->callWriter = new Quote($this->callWriter);
619*faf3f01bSAndreas Gohr                $this->addCall('quote_start', [$match], $pos);
6208b1b81beSAndreas Gohr                break;
6218b1b81beSAndreas Gohr
6228b1b81beSAndreas Gohr            case DOKU_LEXER_EXIT:
623*faf3f01bSAndreas Gohr                $this->addCall('quote_end', [], $pos);
6248b1b81beSAndreas Gohr                /** @var Lists $reWriter */
6258b1b81beSAndreas Gohr                $reWriter = $this->callWriter;
6268b1b81beSAndreas Gohr                $this->callWriter = $reWriter->process();
6278b1b81beSAndreas Gohr                break;
6288b1b81beSAndreas Gohr
6298b1b81beSAndreas Gohr            case DOKU_LEXER_MATCHED:
630*faf3f01bSAndreas Gohr                $this->addCall('quote_newline', [$match], $pos);
6318b1b81beSAndreas Gohr                break;
6328b1b81beSAndreas Gohr
6338b1b81beSAndreas Gohr            case DOKU_LEXER_UNMATCHED:
634*faf3f01bSAndreas Gohr                $this->addCall('cdata', [$match], $pos);
6358b1b81beSAndreas Gohr                break;
6368b1b81beSAndreas Gohr
6378b1b81beSAndreas Gohr        }
6388b1b81beSAndreas Gohr
6398b1b81beSAndreas Gohr        return true;
6408b1b81beSAndreas Gohr    }
6418b1b81beSAndreas Gohr
6428b1b81beSAndreas Gohr    /**
6438b1b81beSAndreas Gohr     * @param string $match matched syntax
6448b1b81beSAndreas Gohr     * @param int $state a LEXER_STATE_* constant
6458b1b81beSAndreas Gohr     * @param int $pos byte position in the original source file
6468b1b81beSAndreas Gohr     * @return bool mode handled?
6478b1b81beSAndreas Gohr     */
648*faf3f01bSAndreas Gohr    public function file($match, $state, $pos)
649*faf3f01bSAndreas Gohr    {
6503d491f75SAndreas Gohr        return $this->code($match, $state, $pos, 'file');
6513d491f75SAndreas Gohr    }
6523d491f75SAndreas Gohr
6538b1b81beSAndreas Gohr    /**
6548b1b81beSAndreas Gohr     * @param string $match matched syntax
6558b1b81beSAndreas Gohr     * @param int $state a LEXER_STATE_* constant
6568b1b81beSAndreas Gohr     * @param int $pos byte position in the original source file
6578b1b81beSAndreas Gohr     * @param string $type either 'code' or 'file'
6588b1b81beSAndreas Gohr     * @return bool mode handled?
6598b1b81beSAndreas Gohr     */
660*faf3f01bSAndreas Gohr    public function code($match, $state, $pos, $type = 'code')
661*faf3f01bSAndreas Gohr    {
6623d491f75SAndreas Gohr        if ($state == DOKU_LEXER_UNMATCHED) {
663ec34bb30SAndreas Gohr            $matches = sexplode('>', $match, 2, '');
664e2d88156SLarsDW223            // Cut out variable options enclosed in []
665e2d88156SLarsDW223            preg_match('/\[.*\]/', $matches[0], $options);
666e2d88156SLarsDW223            if (!empty($options[0])) {
667e2d88156SLarsDW223                $matches[0] = str_replace($options[0], '', $matches[0]);
668e2d88156SLarsDW223            }
6690139312bSAdrian Lang            $param = preg_split('/\s+/', $matches[0], 2, PREG_SPLIT_NO_EMPTY);
670*faf3f01bSAndreas Gohr            while (count($param) < 2) $param[] = null;
6710139312bSAdrian Lang            // We shortcut html here.
6720139312bSAdrian Lang            if ($param[0] == 'html') $param[0] = 'html4strict';
6730139312bSAdrian Lang            if ($param[0] == '-') $param[0] = null;
6740139312bSAdrian Lang            array_unshift($param, $matches[1]);
675e2d88156SLarsDW223            if (!empty($options[0])) {
676e2d88156SLarsDW223                $param [] = $this->parse_highlight_options($options[0]);
677e2d88156SLarsDW223            }
6788b1b81beSAndreas Gohr            $this->addCall($type, $param, $pos);
6790cecf9d5Sandi        }
68044881bd0Shenning.noren        return true;
6810cecf9d5Sandi    }
6820cecf9d5Sandi
6838b1b81beSAndreas Gohr    /**
6848b1b81beSAndreas Gohr     * @param string $match matched syntax
6858b1b81beSAndreas Gohr     * @param int $state a LEXER_STATE_* constant
6868b1b81beSAndreas Gohr     * @param int $pos byte position in the original source file
6878b1b81beSAndreas Gohr     * @return bool mode handled?
6888b1b81beSAndreas Gohr     */
689*faf3f01bSAndreas Gohr    public function acronym($match, $state, $pos)
690*faf3f01bSAndreas Gohr    {
691*faf3f01bSAndreas Gohr        $this->addCall('acronym', [$match], $pos);
69244881bd0Shenning.noren        return true;
6930cecf9d5Sandi    }
6940cecf9d5Sandi
6958b1b81beSAndreas Gohr    /**
6968b1b81beSAndreas Gohr     * @param string $match matched syntax
6978b1b81beSAndreas Gohr     * @param int $state a LEXER_STATE_* constant
6988b1b81beSAndreas Gohr     * @param int $pos byte position in the original source file
6998b1b81beSAndreas Gohr     * @return bool mode handled?
7008b1b81beSAndreas Gohr     */
701*faf3f01bSAndreas Gohr    public function smiley($match, $state, $pos)
702*faf3f01bSAndreas Gohr    {
703*faf3f01bSAndreas Gohr        $this->addCall('smiley', [$match], $pos);
70444881bd0Shenning.noren        return true;
7050cecf9d5Sandi    }
7060cecf9d5Sandi
7078b1b81beSAndreas Gohr    /**
7088b1b81beSAndreas Gohr     * @param string $match matched syntax
7098b1b81beSAndreas Gohr     * @param int $state a LEXER_STATE_* constant
7108b1b81beSAndreas Gohr     * @param int $pos byte position in the original source file
7118b1b81beSAndreas Gohr     * @return bool mode handled?
7128b1b81beSAndreas Gohr     */
713*faf3f01bSAndreas Gohr    public function wordblock($match, $state, $pos)
714*faf3f01bSAndreas Gohr    {
715*faf3f01bSAndreas Gohr        $this->addCall('wordblock', [$match], $pos);
71644881bd0Shenning.noren        return true;
7170cecf9d5Sandi    }
7180cecf9d5Sandi
7198b1b81beSAndreas Gohr    /**
7208b1b81beSAndreas Gohr     * @param string $match matched syntax
7218b1b81beSAndreas Gohr     * @param int $state a LEXER_STATE_* constant
7228b1b81beSAndreas Gohr     * @param int $pos byte position in the original source file
7238b1b81beSAndreas Gohr     * @return bool mode handled?
7248b1b81beSAndreas Gohr     */
725*faf3f01bSAndreas Gohr    public function entity($match, $state, $pos)
726*faf3f01bSAndreas Gohr    {
727*faf3f01bSAndreas Gohr        $this->addCall('entity', [$match], $pos);
72844881bd0Shenning.noren        return true;
7290cecf9d5Sandi    }
7300cecf9d5Sandi
7318b1b81beSAndreas Gohr    /**
7328b1b81beSAndreas Gohr     * @param string $match matched syntax
7338b1b81beSAndreas Gohr     * @param int $state a LEXER_STATE_* constant
7348b1b81beSAndreas Gohr     * @param int $pos byte position in the original source file
7358b1b81beSAndreas Gohr     * @return bool mode handled?
7368b1b81beSAndreas Gohr     */
737*faf3f01bSAndreas Gohr    public function multiplyentity($match, $state, $pos)
738*faf3f01bSAndreas Gohr    {
7390cecf9d5Sandi        preg_match_all('/\d+/', $match, $matches);
740*faf3f01bSAndreas Gohr        $this->addCall('multiplyentity', [$matches[0][0], $matches[0][1]], $pos);
74144881bd0Shenning.noren        return true;
7420cecf9d5Sandi    }
7430cecf9d5Sandi
7448b1b81beSAndreas Gohr    /**
7458b1b81beSAndreas Gohr     * @param string $match matched syntax
7468b1b81beSAndreas Gohr     * @param int $state a LEXER_STATE_* constant
7478b1b81beSAndreas Gohr     * @param int $pos byte position in the original source file
7488b1b81beSAndreas Gohr     * @return bool mode handled?
7498b1b81beSAndreas Gohr     */
750*faf3f01bSAndreas Gohr    public function singlequoteopening($match, $state, $pos)
751*faf3f01bSAndreas Gohr    {
752*faf3f01bSAndreas Gohr        $this->addCall('singlequoteopening', [], $pos);
75344881bd0Shenning.noren        return true;
7540cecf9d5Sandi    }
7550cecf9d5Sandi
7568b1b81beSAndreas Gohr    /**
7578b1b81beSAndreas Gohr     * @param string $match matched syntax
7588b1b81beSAndreas Gohr     * @param int $state a LEXER_STATE_* constant
7598b1b81beSAndreas Gohr     * @param int $pos byte position in the original source file
7608b1b81beSAndreas Gohr     * @return bool mode handled?
7618b1b81beSAndreas Gohr     */
762*faf3f01bSAndreas Gohr    public function singlequoteclosing($match, $state, $pos)
763*faf3f01bSAndreas Gohr    {
764*faf3f01bSAndreas Gohr        $this->addCall('singlequoteclosing', [], $pos);
76544881bd0Shenning.noren        return true;
7660cecf9d5Sandi    }
7670cecf9d5Sandi
7688b1b81beSAndreas Gohr    /**
7698b1b81beSAndreas Gohr     * @param string $match matched syntax
7708b1b81beSAndreas Gohr     * @param int $state a LEXER_STATE_* constant
7718b1b81beSAndreas Gohr     * @param int $pos byte position in the original source file
7728b1b81beSAndreas Gohr     * @return bool mode handled?
7738b1b81beSAndreas Gohr     */
774*faf3f01bSAndreas Gohr    public function apostrophe($match, $state, $pos)
775*faf3f01bSAndreas Gohr    {
776*faf3f01bSAndreas Gohr        $this->addCall('apostrophe', [], $pos);
77757d757d1SAndreas Gohr        return true;
77857d757d1SAndreas Gohr    }
77957d757d1SAndreas Gohr
7808b1b81beSAndreas Gohr    /**
7818b1b81beSAndreas Gohr     * @param string $match matched syntax
7828b1b81beSAndreas Gohr     * @param int $state a LEXER_STATE_* constant
7838b1b81beSAndreas Gohr     * @param int $pos byte position in the original source file
7848b1b81beSAndreas Gohr     * @return bool mode handled?
7858b1b81beSAndreas Gohr     */
786*faf3f01bSAndreas Gohr    public function doublequoteopening($match, $state, $pos)
787*faf3f01bSAndreas Gohr    {
788*faf3f01bSAndreas Gohr        $this->addCall('doublequoteopening', [], $pos);
789e950d12fSChristopher Smith        $this->status['doublequote']++;
79044881bd0Shenning.noren        return true;
7910cecf9d5Sandi    }
7920cecf9d5Sandi
7938b1b81beSAndreas Gohr    /**
7948b1b81beSAndreas Gohr     * @param string $match matched syntax
7958b1b81beSAndreas Gohr     * @param int $state a LEXER_STATE_* constant
7968b1b81beSAndreas Gohr     * @param int $pos byte position in the original source file
7978b1b81beSAndreas Gohr     * @return bool mode handled?
7988b1b81beSAndreas Gohr     */
799*faf3f01bSAndreas Gohr    public function doublequoteclosing($match, $state, $pos)
800*faf3f01bSAndreas Gohr    {
801e950d12fSChristopher Smith        if ($this->status['doublequote'] <= 0) {
802e950d12fSChristopher Smith            $this->doublequoteopening($match, $state, $pos);
803e950d12fSChristopher Smith        } else {
804*faf3f01bSAndreas Gohr            $this->addCall('doublequoteclosing', [], $pos);
805e950d12fSChristopher Smith            $this->status['doublequote'] = max(0, --$this->status['doublequote']);
806e950d12fSChristopher Smith        }
80744881bd0Shenning.noren        return true;
8080cecf9d5Sandi    }
8090cecf9d5Sandi
8108b1b81beSAndreas Gohr    /**
8118b1b81beSAndreas Gohr     * @param string $match matched syntax
8128b1b81beSAndreas Gohr     * @param int $state a LEXER_STATE_* constant
8138b1b81beSAndreas Gohr     * @param int $pos byte position in the original source file
8148b1b81beSAndreas Gohr     * @return bool mode handled?
8158b1b81beSAndreas Gohr     */
816*faf3f01bSAndreas Gohr    public function camelcaselink($match, $state, $pos)
817*faf3f01bSAndreas Gohr    {
818*faf3f01bSAndreas Gohr        $this->addCall('camelcaselink', [$match], $pos);
81944881bd0Shenning.noren        return true;
8200cecf9d5Sandi    }
8210cecf9d5Sandi
8228b1b81beSAndreas Gohr    /**
8238b1b81beSAndreas Gohr     * @param string $match matched syntax
8248b1b81beSAndreas Gohr     * @param int $state a LEXER_STATE_* constant
8258b1b81beSAndreas Gohr     * @param int $pos byte position in the original source file
8268b1b81beSAndreas Gohr     * @return bool mode handled?
8270cecf9d5Sandi     */
828*faf3f01bSAndreas Gohr    public function internallink($match, $state, $pos)
829*faf3f01bSAndreas Gohr    {
8300cecf9d5Sandi        // Strip the opening and closing markup
831*faf3f01bSAndreas Gohr        $link = preg_replace(['/^\[\[/', '/\]\]$/u'], '', $match);
8320cecf9d5Sandi
8330cecf9d5Sandi        // Split title from URL
834ec34bb30SAndreas Gohr        $link = sexplode('|', $link, 2);
835ec34bb30SAndreas Gohr        if ($link[1] === null) {
8360ea51e63SMatt Perry            $link[1] = null;
8370cecf9d5Sandi        } elseif (preg_match('/^\{\{[^\}]+\}\}$/', $link[1])) {
8385578eb8fSandi            // If the title is an image, convert it to an array containing the image details
839b625487dSandi            $link[1] = Doku_Handler_Parse_Media($link[1]);
8400cecf9d5Sandi        }
8410b7c14c2Sandi        $link[0] = trim($link[0]);
8420cecf9d5Sandi
8430e1c636eSandi        //decide which kind of link it is
8440e1c636eSandi
8456efc45a2SDmitry Katsubo        if (link_isinterwiki($link[0])) {
8460e1c636eSandi            // Interwiki
847ec34bb30SAndreas Gohr            $interwiki = sexplode('>', $link[0], 2, '');
8488b1b81beSAndreas Gohr            $this->addCall(
8490cecf9d5Sandi                'interwikilink',
850*faf3f01bSAndreas Gohr                [$link[0], $link[1], strtolower($interwiki[0]), $interwiki[1]],
8510cecf9d5Sandi                $pos
8520cecf9d5Sandi            );
85315f1b77cSAndreas Gohr        } elseif (preg_match('/^\\\\\\\\[^\\\\]+?\\\\/u', $link[0])) {
8540e1c636eSandi            // Windows Share
8558b1b81beSAndreas Gohr            $this->addCall(
8560cecf9d5Sandi                'windowssharelink',
857*faf3f01bSAndreas Gohr                [$link[0], $link[1]],
8580cecf9d5Sandi                $pos
8590cecf9d5Sandi            );
8604468cb4cSAndreas Gohr        } elseif (preg_match('#^([a-z0-9\-\.+]+?)://#i', $link[0])) {
8610e1c636eSandi            // external link (accepts all protocols)
8628b1b81beSAndreas Gohr            $this->addCall(
8630cecf9d5Sandi                'externallink',
864*faf3f01bSAndreas Gohr                [$link[0], $link[1]],
8650cecf9d5Sandi                $pos
8660cecf9d5Sandi            );
8670a1d30bfSchris        } elseif (preg_match('<' . PREG_PATTERN_VALID_EMAIL . '>', $link[0])) {
8680a1d30bfSchris            // E-Mail (pattern above is defined in inc/mail.php)
8698b1b81beSAndreas Gohr            $this->addCall(
870a6755281Sandi                'emaillink',
871*faf3f01bSAndreas Gohr                [$link[0], $link[1]],
872a6755281Sandi                $pos
873a6755281Sandi            );
8740b7c14c2Sandi        } elseif (preg_match('!^#.+!', $link[0])) {
8750b7c14c2Sandi            // local link
8768b1b81beSAndreas Gohr            $this->addCall(
8770b7c14c2Sandi                'locallink',
878*faf3f01bSAndreas Gohr                [substr($link[0], 1), $link[1]],
8790b7c14c2Sandi                $pos
8800b7c14c2Sandi            );
8810e1c636eSandi        } else {
8820e1c636eSandi            // internal link
8838b1b81beSAndreas Gohr            $this->addCall(
8840e1c636eSandi                'internallink',
885*faf3f01bSAndreas Gohr                [$link[0], $link[1]],
8860e1c636eSandi                $pos
8870e1c636eSandi            );
8880cecf9d5Sandi        }
8890e1c636eSandi
89044881bd0Shenning.noren        return true;
8910cecf9d5Sandi    }
8920cecf9d5Sandi
8938b1b81beSAndreas Gohr    /**
8948b1b81beSAndreas Gohr     * @param string $match matched syntax
8958b1b81beSAndreas Gohr     * @param int $state a LEXER_STATE_* constant
8968b1b81beSAndreas Gohr     * @param int $pos byte position in the original source file
8978b1b81beSAndreas Gohr     * @return bool mode handled?
8988b1b81beSAndreas Gohr     */
899*faf3f01bSAndreas Gohr    public function filelink($match, $state, $pos)
900*faf3f01bSAndreas Gohr    {
901*faf3f01bSAndreas Gohr        $this->addCall('filelink', [$match, null], $pos);
90244881bd0Shenning.noren        return true;
9030cecf9d5Sandi    }
9040cecf9d5Sandi
9058b1b81beSAndreas Gohr    /**
9068b1b81beSAndreas Gohr     * @param string $match matched syntax
9078b1b81beSAndreas Gohr     * @param int $state a LEXER_STATE_* constant
9088b1b81beSAndreas Gohr     * @param int $pos byte position in the original source file
9098b1b81beSAndreas Gohr     * @return bool mode handled?
9108b1b81beSAndreas Gohr     */
911*faf3f01bSAndreas Gohr    public function windowssharelink($match, $state, $pos)
912*faf3f01bSAndreas Gohr    {
913*faf3f01bSAndreas Gohr        $this->addCall('windowssharelink', [$match, null], $pos);
91444881bd0Shenning.noren        return true;
9150cecf9d5Sandi    }
9160cecf9d5Sandi
9178b1b81beSAndreas Gohr    /**
9188b1b81beSAndreas Gohr     * @param string $match matched syntax
9198b1b81beSAndreas Gohr     * @param int $state a LEXER_STATE_* constant
9208b1b81beSAndreas Gohr     * @param int $pos byte position in the original source file
9218b1b81beSAndreas Gohr     * @return bool mode handled?
9228b1b81beSAndreas Gohr     */
923*faf3f01bSAndreas Gohr    public function media($match, $state, $pos)
924*faf3f01bSAndreas Gohr    {
9250cecf9d5Sandi        $p = Doku_Handler_Parse_Media($match);
9260cecf9d5Sandi
9278b1b81beSAndreas Gohr        $this->addCall(
9280cecf9d5Sandi            $p['type'],
929*faf3f01bSAndreas Gohr            [$p['src'], $p['title'], $p['align'], $p['width'], $p['height'], $p['cache'], $p['linking']],
9300cecf9d5Sandi            $pos
9310cecf9d5Sandi        );
93244881bd0Shenning.noren        return true;
9330cecf9d5Sandi    }
9340cecf9d5Sandi
9358b1b81beSAndreas Gohr    /**
9368b1b81beSAndreas Gohr     * @param string $match matched syntax
9378b1b81beSAndreas Gohr     * @param int $state a LEXER_STATE_* constant
9388b1b81beSAndreas Gohr     * @param int $pos byte position in the original source file
9398b1b81beSAndreas Gohr     * @return bool mode handled?
9408b1b81beSAndreas Gohr     */
941*faf3f01bSAndreas Gohr    public function rss($match, $state, $pos)
942*faf3f01bSAndreas Gohr    {
943*faf3f01bSAndreas Gohr        $link = preg_replace(['/^\{\{rss>/', '/\}\}$/'], '', $match);
9443db95becSAndreas Gohr
9453db95becSAndreas Gohr        // get params
946*faf3f01bSAndreas Gohr        [$link, $params] = sexplode(' ', $link, 2, '');
9473db95becSAndreas Gohr
948*faf3f01bSAndreas Gohr        $p = [];
9493db95becSAndreas Gohr        if (preg_match('/\b(\d+)\b/', $params, $match)) {
9503db95becSAndreas Gohr            $p['max'] = $match[1];
9513db95becSAndreas Gohr        } else {
9523db95becSAndreas Gohr            $p['max'] = 8;
9533db95becSAndreas Gohr        }
9543db95becSAndreas Gohr        $p['reverse'] = (preg_match('/rev/', $params));
9553db95becSAndreas Gohr        $p['author'] = (preg_match('/\b(by|author)/', $params));
9563db95becSAndreas Gohr        $p['date'] = (preg_match('/\b(date)/', $params));
9573db95becSAndreas Gohr        $p['details'] = (preg_match('/\b(desc|detail)/', $params));
95838c6f603SRobin H. Johnson        $p['nosort'] = (preg_match('/\b(nosort)\b/', $params));
9593db95becSAndreas Gohr
9600a69dff7Schris        if (preg_match('/\b(\d+)([dhm])\b/', $params, $match)) {
961*faf3f01bSAndreas Gohr            $period = ['d' => 86400, 'h' => 3600, 'm' => 60];
9620a69dff7Schris            $p['refresh'] = max(600, $match[1] * $period[$match[2]]);  // n * period in seconds, minimum 10 minutes
9630a69dff7Schris        } else {
9640a69dff7Schris            $p['refresh'] = 14400;   // default to 4 hours
9650a69dff7Schris        }
9660a69dff7Schris
967*faf3f01bSAndreas Gohr        $this->addCall('rss', [$link, $p], $pos);
96844881bd0Shenning.noren        return true;
969b625487dSandi    }
970b625487dSandi
9718b1b81beSAndreas Gohr    /**
9728b1b81beSAndreas Gohr     * @param string $match matched syntax
9738b1b81beSAndreas Gohr     * @param int $state a LEXER_STATE_* constant
9748b1b81beSAndreas Gohr     * @param int $pos byte position in the original source file
9758b1b81beSAndreas Gohr     * @return bool mode handled?
9768b1b81beSAndreas Gohr     */
977*faf3f01bSAndreas Gohr    public function externallink($match, $state, $pos)
978*faf3f01bSAndreas Gohr    {
979da9f31c5SAndreas Gohr        $url = $match;
980da9f31c5SAndreas Gohr        $title = null;
9810cecf9d5Sandi
982da9f31c5SAndreas Gohr        // add protocol on simple short URLs
983da9f31c5SAndreas Gohr        if (substr($url, 0, 3) == 'ftp' && (substr($url, 0, 6) != 'ftp://')) {
984da9f31c5SAndreas Gohr            $title = $url;
985da9f31c5SAndreas Gohr            $url = 'ftp://' . $url;
986da9f31c5SAndreas Gohr        }
987da9f31c5SAndreas Gohr        if (substr($url, 0, 3) == 'www' && (substr($url, 0, 7) != 'http://')) {
988da9f31c5SAndreas Gohr            $title = $url;
989da9f31c5SAndreas Gohr            $url = 'http://' . $url;
990da9f31c5SAndreas Gohr        }
991da9f31c5SAndreas Gohr
992*faf3f01bSAndreas Gohr        $this->addCall('externallink', [$url, $title], $pos);
99344881bd0Shenning.noren        return true;
9940cecf9d5Sandi    }
9950cecf9d5Sandi
9968b1b81beSAndreas Gohr    /**
9978b1b81beSAndreas Gohr     * @param string $match matched syntax
9988b1b81beSAndreas Gohr     * @param int $state a LEXER_STATE_* constant
9998b1b81beSAndreas Gohr     * @param int $pos byte position in the original source file
10008b1b81beSAndreas Gohr     * @return bool mode handled?
10018b1b81beSAndreas Gohr     */
1002*faf3f01bSAndreas Gohr    public function emaillink($match, $state, $pos)
1003*faf3f01bSAndreas Gohr    {
1004*faf3f01bSAndreas Gohr        $email = preg_replace(['/^</', '/>$/'], '', $match);
1005*faf3f01bSAndreas Gohr        $this->addCall('emaillink', [$email, null], $pos);
100644881bd0Shenning.noren        return true;
10070cecf9d5Sandi    }
10080cecf9d5Sandi
10098b1b81beSAndreas Gohr    /**
10108b1b81beSAndreas Gohr     * @param string $match matched syntax
10118b1b81beSAndreas Gohr     * @param int $state a LEXER_STATE_* constant
10128b1b81beSAndreas Gohr     * @param int $pos byte position in the original source file
10138b1b81beSAndreas Gohr     * @return bool mode handled?
10148b1b81beSAndreas Gohr     */
1015*faf3f01bSAndreas Gohr    public function table($match, $state, $pos)
1016*faf3f01bSAndreas Gohr    {
10170cecf9d5Sandi        switch ($state) {
10180cecf9d5Sandi
10190cecf9d5Sandi            case DOKU_LEXER_ENTER:
10200cecf9d5Sandi
10218b1b81beSAndreas Gohr                $this->callWriter = new Table($this->callWriter);
10220cecf9d5Sandi
1023*faf3f01bSAndreas Gohr                $this->addCall('table_start', [$pos + 1], $pos);
10240cecf9d5Sandi                if (trim($match) == '^') {
1025*faf3f01bSAndreas Gohr                    $this->addCall('tableheader', [], $pos);
10260cecf9d5Sandi                } else {
1027*faf3f01bSAndreas Gohr                    $this->addCall('tablecell', [], $pos);
10280cecf9d5Sandi                }
10290cecf9d5Sandi                break;
10300cecf9d5Sandi
10310cecf9d5Sandi            case DOKU_LEXER_EXIT:
1032*faf3f01bSAndreas Gohr                $this->addCall('table_end', [$pos], $pos);
10335c2aad12SAndreas Gohr                /** @var Table $reWriter */
10348b1b81beSAndreas Gohr                $reWriter = $this->callWriter;
10358b1b81beSAndreas Gohr                $this->callWriter = $reWriter->process();
10360cecf9d5Sandi                break;
10370cecf9d5Sandi
10380cecf9d5Sandi            case DOKU_LEXER_UNMATCHED:
10390cecf9d5Sandi                if (trim($match) != '') {
1040*faf3f01bSAndreas Gohr                    $this->addCall('cdata', [$match], $pos);
10410cecf9d5Sandi                }
10420cecf9d5Sandi                break;
10430cecf9d5Sandi
10440cecf9d5Sandi            case DOKU_LEXER_MATCHED:
10459ab75d9eSAndreas Gohr                if ($match == ' ') {
1046*faf3f01bSAndreas Gohr                    $this->addCall('cdata', [$match], $pos);
104725b97867Shakan.sandell                } elseif (preg_match('/:::/', $match)) {
1048*faf3f01bSAndreas Gohr                    $this->addCall('rowspan', [$match], $pos);
1049e205b721SAndreas Gohr                } elseif (preg_match('/\t+/', $match)) {
1050*faf3f01bSAndreas Gohr                    $this->addCall('table_align', [$match], $pos);
1051e205b721SAndreas Gohr                } elseif (preg_match('/ {2,}/', $match)) {
1052*faf3f01bSAndreas Gohr                    $this->addCall('table_align', [$match], $pos);
10530cecf9d5Sandi                } elseif ($match == "\n|") {
1054*faf3f01bSAndreas Gohr                    $this->addCall('table_row', [], $pos);
1055*faf3f01bSAndreas Gohr                    $this->addCall('tablecell', [], $pos);
10560cecf9d5Sandi                } elseif ($match == "\n^") {
1057*faf3f01bSAndreas Gohr                    $this->addCall('table_row', [], $pos);
1058*faf3f01bSAndreas Gohr                    $this->addCall('tableheader', [], $pos);
10590cecf9d5Sandi                } elseif ($match == '|') {
1060*faf3f01bSAndreas Gohr                    $this->addCall('tablecell', [], $pos);
10610cecf9d5Sandi                } elseif ($match == '^') {
1062*faf3f01bSAndreas Gohr                    $this->addCall('tableheader', [], $pos);
10630cecf9d5Sandi                }
10640cecf9d5Sandi                break;
10650cecf9d5Sandi        }
106644881bd0Shenning.noren        return true;
10670cecf9d5Sandi    }
10688b1b81beSAndreas Gohr
10698b1b81beSAndreas Gohr    // endregion modes
10700cecf9d5Sandi}
10710cecf9d5Sandi
10720cecf9d5Sandi//------------------------------------------------------------------------
1073*faf3f01bSAndreas Gohrfunction Doku_Handler_Parse_Media($match)
1074*faf3f01bSAndreas Gohr{
1075d8ab8746SAndreas Gohr
10760cecf9d5Sandi    // Strip the opening and closing markup
1077*faf3f01bSAndreas Gohr    $link = preg_replace(['/^\{\{/', '/\}\}$/u'], '', $match);
10780cecf9d5Sandi
10790cecf9d5Sandi    // Split title from URL
1080ec34bb30SAndreas Gohr    $link = sexplode('|', $link, 2);
10810cecf9d5Sandi
10820cecf9d5Sandi    // Check alignment
10830cecf9d5Sandi    $ralign = (bool)preg_match('/^ /', $link[0]);
10840cecf9d5Sandi    $lalign = (bool)preg_match('/ $/', $link[0]);
10850cecf9d5Sandi
10860cecf9d5Sandi    // Logic = what's that ;)...
10870cecf9d5Sandi    if ($lalign & $ralign) {
10880cecf9d5Sandi        $align = 'center';
10890cecf9d5Sandi    } elseif ($ralign) {
10900cecf9d5Sandi        $align = 'right';
10910cecf9d5Sandi    } elseif ($lalign) {
10920cecf9d5Sandi        $align = 'left';
10930cecf9d5Sandi    } else {
10940ea51e63SMatt Perry        $align = null;
10950cecf9d5Sandi    }
10960cecf9d5Sandi
10970cecf9d5Sandi    // The title...
10980cecf9d5Sandi    if (!isset($link[1])) {
10990ea51e63SMatt Perry        $link[1] = null;
11000cecf9d5Sandi    }
11010cecf9d5Sandi
11024826ab45Sandi    //remove aligning spaces
11034826ab45Sandi    $link[0] = trim($link[0]);
11040cecf9d5Sandi
11054826ab45Sandi    //split into src and parameters (using the very last questionmark)
11064826ab45Sandi    $pos = strrpos($link[0], '?');
11074826ab45Sandi    if ($pos !== false) {
11084826ab45Sandi        $src = substr($link[0], 0, $pos);
11094826ab45Sandi        $param = substr($link[0], $pos + 1);
11100cecf9d5Sandi    } else {
11114826ab45Sandi        $src = $link[0];
11124826ab45Sandi        $param = '';
11130cecf9d5Sandi    }
11140cecf9d5Sandi
11154826ab45Sandi    //parse width and height
11164826ab45Sandi    if (preg_match('#(\d+)(x(\d+))?#i', $param, $size)) {
1117*faf3f01bSAndreas Gohr        $w = empty($size[1]) ? null : $size[1];
1118*faf3f01bSAndreas Gohr        $h = empty($size[3]) ? null : $size[3];
1119fc1c55b1Shfuecks    } else {
11200ea51e63SMatt Perry        $w = null;
11210ea51e63SMatt Perry        $h = null;
11220cecf9d5Sandi    }
11230cecf9d5Sandi
1124dc673a5bSjoe.lapp    //get linking command
1125d35ab615Shenning.noren    if (preg_match('/nolink/i', $param)) {
1126dc673a5bSjoe.lapp        $linking = 'nolink';
1127d35ab615Shenning.noren    } elseif (preg_match('/direct/i', $param)) {
1128dc673a5bSjoe.lapp        $linking = 'direct';
11298acb3108SAndreas Gohr    } elseif (preg_match('/linkonly/i', $param)) {
11308acb3108SAndreas Gohr        $linking = 'linkonly';
1131dc673a5bSjoe.lapp    } else {
1132dc673a5bSjoe.lapp        $linking = 'details';
1133dc673a5bSjoe.lapp    }
1134dc673a5bSjoe.lapp
11354826ab45Sandi    //get caching command
11364826ab45Sandi    if (preg_match('/(nocache|recache)/i', $param, $cachemode)) {
11374826ab45Sandi        $cache = $cachemode[1];
11380cecf9d5Sandi    } else {
11394826ab45Sandi        $cache = 'cache';
11400cecf9d5Sandi    }
1141d8ab8746SAndreas Gohr
11426efc45a2SDmitry Katsubo    // Check whether this is a local or remote image or interwiki
11436efc45a2SDmitry Katsubo    if (media_isexternal($src) || link_isinterwiki($src)) {
11444826ab45Sandi        $call = 'externalmedia';
11450cecf9d5Sandi    } else {
11464826ab45Sandi        $call = 'internalmedia';
11470cecf9d5Sandi    }
11480cecf9d5Sandi
1149*faf3f01bSAndreas Gohr    $params = [
11500cecf9d5Sandi        'type' => $call,
11514826ab45Sandi        'src' => $src,
11520cecf9d5Sandi        'title' => $link[1],
11530cecf9d5Sandi        'align' => $align,
11544826ab45Sandi        'width' => $w,
11554826ab45Sandi        'height' => $h,
11560cecf9d5Sandi        'cache' => $cache,
1157*faf3f01bSAndreas Gohr        'linking' => $linking
1158*faf3f01bSAndreas Gohr    ];
11590cecf9d5Sandi
11600cecf9d5Sandi    return $params;
11610cecf9d5Sandi}
11620cecf9d5Sandi
1163