xref: /plugin/mermaid/syntax.php (revision 1da12d6e8ab019f8bc2014b3dc7bf0b52095a071)
1<?php
2/**
3 * DokuWiki Plugin mermaid (Syntax Component)
4 *
5 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
6 * @author  Robert Weinmeister <develop@weinmeister.org>
7 */
8
9use dokuwiki\Parsing\Parser;
10
11class syntax_plugin_mermaid extends \dokuwiki\Extension\SyntaxPlugin
12{
13    const DOKUWIKI_LINK_START_MERMAID = '<code>DOKUWIKILINKSTARTMERMAID</code>';
14    const DOKUWIKI_LINK_END_MERMAID = '<code>DOKUWIKILINKENDMERMAID</code>';
15    const DOKUWIKI_LINK_SPLITTER ='--';
16
17    private $mermaidCounter = -1;
18
19    function protect_brackets_from_dokuwiki($text)
20    {
21        $splitText = explode(self::DOKUWIKI_LINK_SPLITTER, $text);
22        foreach ($splitText as $key => $line)
23        {
24            $splitText[$key] = preg_replace('/(?<!["\[(\s])(\[\[)(.*)(\]\])/', self::DOKUWIKI_LINK_START_MERMAID . '$2' . self::DOKUWIKI_LINK_END_MERMAID, $line);
25        }
26        $text = implode(self::DOKUWIKI_LINK_SPLITTER, $splitText);
27        return $text;
28    }
29
30    function remove_protection_of_brackets_from_dokuwiki($text)
31    {
32        return str_replace(self::DOKUWIKI_LINK_START_MERMAID, '[[', str_replace(self::DOKUWIKI_LINK_END_MERMAID, ']]', $text));
33    }
34
35   	/** @inheritDoc */
36    function getType()
37	{
38		return 'container';
39	}
40
41    /** @inheritDoc */
42    function getSort()
43	{
44		return 150;
45	}
46
47    /**
48    * Connect lookup pattern to lexer.
49    *
50    * @param string $mode Parser mode
51    */
52    function connectTo($mode)
53    {
54        $this->Lexer->addEntryPattern('<mermaid.*?>(?=.*?</mermaid>)',$mode,'plugin_mermaid');
55    }
56
57    function postConnect()
58    {
59        $this->Lexer->addExitPattern('</mermaid>','plugin_mermaid');
60    }
61
62    /**
63     * Handle matches of the Mermaid syntax
64     */
65    function handle($match, $state, $pos, Doku_Handler $handler)
66    {
67        switch ($state) {
68            case DOKU_LEXER_ENTER:
69                return array($state, $match);
70            case DOKU_LEXER_UNMATCHED:
71                return array($state, $match);
72            case DOKU_LEXER_EXIT:
73                return array($state, '');
74        }
75        return false;
76    }
77
78    /**
79     * Render xhtml output or metadata
80     */
81    function render($mode, Doku_Renderer $renderer, $indata)
82    {
83        if($mode == 'xhtml'){
84            list($state, $match) = $indata;
85            switch ($state) {
86                case DOKU_LEXER_ENTER:
87                    $this->mermaidCounter++;
88                    $values = explode(" ", $match);
89                    $divwidth = count($values) < 2 ? 'auto' : $values[1];
90                    $divheight = count($values) < 3 ? 'auto' : substr($values[2], 0, -1);
91                    $renderer->doc .= '<div id="mermaidContainer'.$this->mermaidCounter.'" style="position: relative;"><span class="mermaid" id=mermaidContent'.$this->mermaidCounter.' style="width:'.$divwidth.'; height:'.$divheight.'">';
92                break;
93                case DOKU_LEXER_UNMATCHED:
94                    $explodedMatch = explode("\n", $match);
95                    $israwmode = isset($explodedMatch[1]) && strpos($explodedMatch[1], 'raw') !== false;
96                    if($israwmode)
97                    {
98                        array_shift($explodedMatch);
99                        array_shift($explodedMatch);
100                        $actualContent = implode("\n", $explodedMatch);
101                        $renderer->doc .= $actualContent;
102                    }
103                    else
104                    {
105                        $instructions = $this->p_get_instructions($this->protect_brackets_from_dokuwiki($match));
106                        $xhtml = $this->remove_protection_of_brackets_from_dokuwiki($this->p_render($instructions));
107                        $renderer->doc .= preg_replace("/(^[\r\n]*|[\r\n]+)[\s\t]*[\r\n]+/", "\n", $xhtml);
108                    }
109                break;
110                case DOKU_LEXER_EXIT:
111                    $renderer->doc .= "\r\n";
112                    $renderer->doc .= '</span><button id="mermaidButton'.$this->mermaidCounter.'" style="position: absolute; top: 0; left: 0; z-index: 10; display: none; padding: 0; margin: 0; border: none; background: none; width: 24px; height: 24px;"><svg fill="#000000" viewBox="0 0 52 52" enable-background="new 0 0 52 52" xml:space="preserve" style="width: 24px; height: 24px;"><path d="M37.1,4v13.6c0,1-0.8,1.9-1.9,1.9H13.9c-1,0-1.9-0.8-1.9-1.9V4H8C5.8,4,4,5.8,4,8v36c0,2.2,1.8,4,4,4h36  c2.2,0,4-1.8,4-4V11.2L40.8,4H37.1z M44.1,42.1c0,1-0.8,1.9-1.9,1.9H9.9c-1,0-1.9-0.8-1.9-1.9V25.4c0-1,0.8-1.9,1.9-1.9h32.3  c1,0,1.9,0.8,1.9,1.9V42.1z"/><path d="M24.8,13.6c0,1,0.8,1.9,1.9,1.9h4.6c1,0,1.9-0.8,1.9-1.9V4h-8.3L24.8,13.6L24.8,13.6z"/></svg></button></div>';
113                break;
114            }
115            return true;
116        }
117        return false;
118    }
119
120    /*
121     * Get the parser instructions suitable for the mermaid
122     *
123     */
124    function p_get_instructions($text)
125    {
126        //import parser classes and mode definitions
127        require_once DOKU_INC . 'inc/parser/parser.php';
128
129        // https://www.dokuwiki.org/devel:parser
130        // https://www.dokuwiki.org/devel:parser#basic_invocation
131        // Create the parser and the handler
132        $Parser = new Parser(new Doku_Handler());
133
134        $modes = array();
135
136        // add default modes
137        $std_modes = array( 'internallink', 'media', 'externallink');
138
139        foreach($std_modes as $m)
140        {
141            $class = 'dokuwiki\\Parsing\\ParserMode\\'.ucfirst($m);
142            $obj   = new $class();
143            $modes[] = array(
144                'sort' => $obj->getSort(),
145                'mode' => $m,
146                'obj'  => $obj
147            );
148        }
149
150        // add formatting modes
151        $fmt_modes = array( 'strong', 'emphasis', 'underline', 'monospace', 'subscript', 'superscript', 'deleted');
152        foreach($fmt_modes as $m)
153        {
154            $obj   = new \dokuwiki\Parsing\ParserMode\Formatting($m);
155            $modes[] = array(
156                'sort' => $obj->getSort(),
157                'mode' => $m,
158                'obj'  => $obj
159            );
160        }
161
162        //add modes to parser
163        foreach($modes as $mode)
164        {
165            $Parser->addMode($mode['mode'],$mode['obj']);
166        }
167
168        // Do the parsing
169        $p = $Parser->parse($text);
170
171        return $p;
172    }
173
174    public function p_render($instructions)
175    {
176        $Renderer = p_get_renderer('mermaid');
177
178        // Loop through the instructions
179        foreach ($instructions as $instruction) {
180            if(method_exists($Renderer, $instruction[0])){
181                call_user_func_array(array(&$Renderer, $instruction[0]), $instruction[1] ? $instruction[1] : array());
182            }
183        }
184
185        return $Renderer->doc;
186    }
187}
188