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    function protect_brackets_from_dokuwiki($text)
18    {
19        $splitText = explode(self::DOKUWIKI_LINK_SPLITTER, $text);
20        foreach ($splitText as $key => $line)
21        {
22            $splitText[$key] = preg_replace('/(?<!["\[(\s])(\[\[)(.*)(\]\])/', self::DOKUWIKI_LINK_START_MERMAID . '$2' . self::DOKUWIKI_LINK_END_MERMAID, $line);
23        }
24        $text = implode(self::DOKUWIKI_LINK_SPLITTER, $splitText);
25        return $text;
26    }
27
28    function remove_protection_of_brackets_from_dokuwiki($text)
29    {
30        return str_replace(self::DOKUWIKI_LINK_START_MERMAID, '[[', str_replace(self::DOKUWIKI_LINK_END_MERMAID, ']]', $text));
31    }
32
33   	/** @inheritDoc */
34    function getType()
35	{
36		return 'container';
37	}
38
39    /** @inheritDoc */
40    function getSort()
41	{
42		return 150;
43	}
44
45    /**
46    * Connect lookup pattern to lexer.
47    *
48    * @param string $mode Parser mode
49    */
50    function connectTo($mode)
51    {
52        $this->Lexer->addEntryPattern('<mermaid.*?>(?=.*?</mermaid>)',$mode,'plugin_mermaid');
53    }
54
55    function postConnect()
56    {
57        $this->Lexer->addExitPattern('</mermaid>','plugin_mermaid');
58    }
59
60    /**
61     * Handle matches of the Mermaid syntax
62     */
63    function handle($match, $state, $pos, Doku_Handler $handler)
64    {
65        switch ($state) {
66            case DOKU_LEXER_ENTER:
67                return array($state, $match);
68            case DOKU_LEXER_UNMATCHED:
69                return array($state, $match);
70            case DOKU_LEXER_EXIT:
71                return array($state, '');
72        }
73        return false;
74    }
75
76    /**
77     * Render xhtml output or metadata
78     */
79    function render($mode, Doku_Renderer $renderer, $indata)
80    {
81        if($mode == 'xhtml'){
82            list($state, $match) = $indata;
83            switch ($state) {
84                case DOKU_LEXER_ENTER:
85                    $values = explode(" ", $match);
86                    $divwidth = count($values) < 2 ? 'auto' : $values[1];
87                    $divheight = count($values) < 3 ? 'auto' : substr($values[2], 0, -1);
88                    $renderer->doc .= '<div class="mermaid" style="width:'.$divwidth.'; height:'.$divheight.'">';
89                break;
90                case DOKU_LEXER_UNMATCHED:
91                    $explodedMatch = explode("\n", $match);
92                    $israwmode = isset($explodedMatch[1]) && strpos($explodedMatch[1], 'raw') !== false;
93                    if($israwmode)
94                    {
95                        array_shift($explodedMatch);
96                        array_shift($explodedMatch);
97                        $actualContent = implode("\n", $explodedMatch);
98                        $renderer->doc .= $actualContent;
99                    }
100                    else
101                    {
102                        $instructions = $this->p_get_instructions($this->protect_brackets_from_dokuwiki($match));
103                        $xhtml = $this->remove_protection_of_brackets_from_dokuwiki($this->p_render($instructions));
104                        $renderer->doc .= preg_replace("/(^[\r\n]*|[\r\n]+)[\s\t]*[\r\n]+/", "\n", $xhtml);
105                    }
106                break;
107                case DOKU_LEXER_EXIT:
108                    $renderer->doc .= "\r\n</div>";
109                break;
110            }
111            return true;
112        }
113        return false;
114    }
115
116    /*
117     * Get the parser instructions suitable for the mermaid
118     *
119     */
120    function p_get_instructions($text)
121    {
122        //import parser classes and mode definitions
123        require_once DOKU_INC . 'inc/parser/parser.php';
124
125        // https://www.dokuwiki.org/devel:parser
126        // https://www.dokuwiki.org/devel:parser#basic_invocation
127        // Create the parser and the handler
128        $Parser = new Parser(new Doku_Handler());
129
130        $modes = array();
131
132        // add default modes
133        $std_modes = array( 'internallink', 'media', 'externallink');
134
135        foreach($std_modes as $m)
136        {
137            $class = 'dokuwiki\\Parsing\\ParserMode\\'.ucfirst($m);
138            $obj   = new $class();
139            $modes[] = array(
140                'sort' => $obj->getSort(),
141                'mode' => $m,
142                'obj'  => $obj
143            );
144        }
145
146        // add formatting modes
147        $fmt_modes = array( 'strong', 'emphasis', 'underline', 'monospace', 'subscript', 'superscript', 'deleted');
148        foreach($fmt_modes as $m)
149        {
150            $obj   = new \dokuwiki\Parsing\ParserMode\Formatting($m);
151            $modes[] = array(
152                'sort' => $obj->getSort(),
153                'mode' => $m,
154                'obj'  => $obj
155            );
156        }
157
158        //add modes to parser
159        foreach($modes as $mode)
160        {
161            $Parser->addMode($mode['mode'],$mode['obj']);
162        }
163
164        // Do the parsing
165        $p = $Parser->parse($text);
166
167        return $p;
168    }
169
170    public function p_render($instructions)
171    {
172        $Renderer = p_get_renderer('mermaid');
173
174        // Loop through the instructions
175        foreach ($instructions as $instruction) {
176            if(method_exists($Renderer, $instruction[0])){
177                call_user_func_array(array(&$Renderer, $instruction[0]), $instruction[1] ? $instruction[1] : array());
178            }
179        }
180
181        return $Renderer->doc;
182    }
183}
184