1<?php
2/**
3 * DokuWiki Plugin KaTeX (Syntax Component: protect)
4 *
5 * protect TeX expressions before DokuWiki trying to parse them.
6 *
7 * @license GPLv3 https://www.gnu.org/licenses/gpl-3.0.html
8 * @author  H.-H. PENG (Hsins) <hsinspeng@gmail.com>
9 * @author  Mark Liffiton <liffiton@gmail.com>
10 */
11
12// must be run within Dokuwiki
13if ( !defined( 'DOKU_INC' ) ) {
14    die();
15}
16
17/**
18 * Class syntax_plugin_katex
19 */
20class syntax_plugin_katex_protect extends DokuWiki_Syntax_Plugin
21{
22    # We need to grab any math before dokuwiki tries to parse it.
23    # Once it's 'claimed' by this plugin (type: protected), it won't be altered.
24
25    # Set of environments that this plugin will protect from Dokuwiki parsing
26    # * is escaped to work in regexp below
27    private static $ENVIRONMENTS = array(
28        "math",
29        "displaymath",
30        "equation",
31        "equation\*",
32        "eqnarray",
33        "eqnarray\*",
34        "align",
35        "align\*",
36        "flalign",
37        "flalign\*",
38        "alignat",
39        "alignat\*",
40        "multline",
41        "multline\*",
42        "gather",
43        "gather\*",
44    );
45
46    /**
47     * Syntax Type
48     *
49     * Needs to return one of the mode types defined in $PARSER_MODES in parser.php
50     *
51     * @return string
52     */
53    public function getType()
54    {
55        return 'protected';
56    }
57
58    /**
59     * Sort for applying this mode
60     *
61     * @return int
62     */
63    public function getSort()
64    {
65        return 65;
66    }
67
68    /**
69     * regexp patterns adapted from jsMath plugin: https://www.dokuwiki.org/plugin:jsmath
70     *
71     * @param string $mode
72     */
73    public function connectTo( $mode )
74    {
75        $this->Lexer->addEntryPattern( '(?<!\\\\)\$(?=[^\$][^\r\n]*?\$)', $mode, 'plugin_katex_protect' );
76        $this->Lexer->addEntryPattern( '\$\$(?=.*?\$\$)', $mode, 'plugin_katex_protect' );
77        $this->Lexer->addEntryPattern( '\\\\\((?=.*?\\\\\))', $mode, 'plugin_katex_protect' );
78        $this->Lexer->addEntryPattern( '\\\\\[(?=.*?\\\\])', $mode, 'plugin_katex_protect' );
79        foreach ( self::$ENVIRONMENTS as $env ) {
80            $this->Lexer->addEntryPattern( '\\\\begin{' . $env . '}(?=.*?\\\\end{' . $env . '})', $mode, 'plugin_katex_protect' );
81        }
82
83        if ( $this->getConf( 'asciimath' ) ) {
84            // Protect the default AsciiMath delimiter
85            $this->Lexer->addEntryPattern( '`(?=.*?`)', $mode, 'plugin_katex_protect' );
86        }
87
88        // Protect specified tags, if any
89        $conf_mathtags = $this->getConf( 'mathtags' );
90        $mathtags      = explode( ',', $conf_mathtags );
91        foreach ( $mathtags as $tag ) {
92            $tag = trim( $tag );
93            if ( $tag == "" ) {continue;}
94            $this->Lexer->addEntryPattern( '<' . $tag . '.*?>(?=.*?</' . $tag . '>)', $mode, 'plugin_katex_protect' );
95        }
96    }
97
98    public function postConnect()
99    {
100        $this->Lexer->addExitPattern( '\$(?!\$)', 'plugin_katex_protect' );
101        $this->Lexer->addExitPattern( '\\\\\)', 'plugin_katex_protect' );
102        $this->Lexer->addExitPattern( '\\\\\]', 'plugin_katex_protect' );
103        foreach ( self::$ENVIRONMENTS as $env ) {
104            $this->Lexer->addExitPattern( '\\\\end{' . $env . '}', 'plugin_katex_protect' );
105        }
106
107        if ( $this->getConf( 'asciimath' ) ) {
108            // Protect the default AsciiMath delimiter
109            $this->Lexer->addExitPattern( '`', 'plugin_katex_protect' );
110        }
111
112        // Protect specified tags, if any
113        $conf_mathtags = $this->getConf( 'mathtags' );
114        $mathtags      = explode( ',', $conf_mathtags );
115        foreach ( $mathtags as $tag ) {
116            $tag = trim( $tag );
117            if ( $tag == "" ) {continue;}
118            $this->Lexer->addExitPattern( '</' . $tag . '>', 'plugin_katex_protect' );
119        }
120    }
121
122    /**
123     * Handler to prepare matched data for the rendering process
124     *
125     * This function can only pass data to render() via its return value - render()
126     * may be not be run during the object's current life.
127     *
128     * Usually you should only need the $match param.
129     *
130     * @param   string       $match   The text matched by the patterns
131     * @param   int          $state   The lexer state for the match
132     * @param   int          $pos     The character position of the matched text
133     * @param   Doku_Handler $handler The Doku_Handler object
134     * @return  array Return an array with all data you want to use in render
135     */
136    public function handle( $match, $state, $pos, Doku_Handler $handler )
137    {
138        // Just pass it through...
139        return $match;
140    }
141
142    /**
143     * Handles the actual output creation.
144     *
145     * @param   $mode     string        output format being rendered
146     * @param   $renderer Doku_Renderer the current renderer object
147     * @param   $data     array         data created by handler()
148     * @return  boolean                 rendered correctly?
149     */
150    public function render( $mode, Doku_Renderer $renderer, $data )
151    {
152        if ( $mode == 'xhtml' || $mode == 'odt' ) {
153            /** @var Doku_Renderer_xhtml $renderer */
154
155            // Just pass it through, but escape xml entities...
156            $renderer->doc .= $renderer->_xmlEntities( $data );
157            return true;
158        }
159        if ( $mode == 'latexport' ) {
160            // Pass math expressions to latexport renderer
161            $renderer->mathjax_content( $data );
162            return true;
163        }
164
165        // For all other modes, pass through unchanged.
166        $renderer->doc .= $data;
167        return true;
168    }
169}
170