xref: /plugin/mermaid/syntax.php (revision da837cc91dee62b41351c76aab1679dfb0835fe3)
1c6570b71SRobertWeinmeister<?php
2c6570b71SRobertWeinmeister/**
3c6570b71SRobertWeinmeister * DokuWiki Plugin mermaid (Syntax Component)
4c6570b71SRobertWeinmeister *
5c6570b71SRobertWeinmeister * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
6c6570b71SRobertWeinmeister * @author  Robert Weinmeister <develop@weinmeister.org>
7c6570b71SRobertWeinmeister */
8c6570b71SRobertWeinmeister
9c6570b71SRobertWeinmeisteruse dokuwiki\Parsing\Parser;
10c6570b71SRobertWeinmeister
11c6570b71SRobertWeinmeisterclass syntax_plugin_mermaid extends \dokuwiki\Extension\SyntaxPlugin
12c6570b71SRobertWeinmeister{
133543e422SRobert Weinmeister    const DOKUWIKI_LINK_START_MERMAID = '<code>DOKUWIKILINKSTARTMERMAID</code>';
143543e422SRobert Weinmeister    const DOKUWIKI_LINK_END_MERMAID = '<code>DOKUWIKILINKENDMERMAID</code>';
15f4ff867cSRobert Weinmeister    const DOKUWIKI_LINK_SPLITTER ='--';
163543e422SRobert Weinmeister
171da12d6eSRobert Weinmeister    private $mermaidCounter = -1;
181da12d6eSRobert Weinmeister
19*da837cc9SRobert Weinmeister    function enable_gantt_links($instructions)
20*da837cc9SRobert Weinmeister    {
21*da837cc9SRobert Weinmeister    $modified_instructions = $instructions;
22*da837cc9SRobert Weinmeister
23*da837cc9SRobert Weinmeister    for ($i = 0; $i < count($modified_instructions); $i++)
24*da837cc9SRobert Weinmeister    {
25*da837cc9SRobert Weinmeister        if (in_array($modified_instructions[$i][0], ["externallink", "internallink"]))
26*da837cc9SRobert Weinmeister        {
27*da837cc9SRobert Weinmeister            // use the appropriate link
28*da837cc9SRobert Weinmeister            $link = $modified_instructions[$i][0] == "externallink" ? $modified_instructions[$i][1][0] : wl($modified_instructions[$i][1][0], '', true);
29*da837cc9SRobert Weinmeister
30*da837cc9SRobert Weinmeister            // change link here to just the name of the link
31*da837cc9SRobert Weinmeister            $modified_instructions[$i][0]= "cdata";
32*da837cc9SRobert Weinmeister            if(!is_null($modified_instructions[$i][1][1]))
33*da837cc9SRobert Weinmeister            {
34*da837cc9SRobert Weinmeister                unset($modified_instructions[$i][1][0]);
35*da837cc9SRobert Weinmeister            }
36*da837cc9SRobert Weinmeister
37*da837cc9SRobert Weinmeister            // insert the click event
38*da837cc9SRobert Weinmeister            if (preg_match('/(?<=:\s)\S+(?=,)/', $modified_instructions[$i+1][1][0], $output_array))
39*da837cc9SRobert Weinmeister            {
40*da837cc9SRobert Weinmeister                $click_reference = $output_array[0];
41*da837cc9SRobert Weinmeister            }
42*da837cc9SRobert Weinmeister            array_splice($modified_instructions, $i + 2, 0, [["cdata", ["\nclick ".$click_reference." href \"".$link."\"\n"]]]);
43*da837cc9SRobert Weinmeister
44*da837cc9SRobert Weinmeister            // encode colons
45*da837cc9SRobert Weinmeister            $modified_instructions[$i][1][0] = str_replace(":", "#colon;", $modified_instructions[$i][1][0]);
46*da837cc9SRobert Weinmeister            }
47*da837cc9SRobert Weinmeister        }
48*da837cc9SRobert Weinmeister
49*da837cc9SRobert Weinmeister        return $modified_instructions;
50*da837cc9SRobert Weinmeister    }
51*da837cc9SRobert Weinmeister
523543e422SRobert Weinmeister    function protect_brackets_from_dokuwiki($text)
533543e422SRobert Weinmeister    {
54f4ff867cSRobert Weinmeister        $splitText = explode(self::DOKUWIKI_LINK_SPLITTER, $text);
55f4ff867cSRobert Weinmeister        foreach ($splitText as $key => $line)
56f4ff867cSRobert Weinmeister        {
57f4ff867cSRobert Weinmeister            $splitText[$key] = preg_replace('/(?<!["\[(\s])(\[\[)(.*)(\]\])/', self::DOKUWIKI_LINK_START_MERMAID . '$2' . self::DOKUWIKI_LINK_END_MERMAID, $line);
58f4ff867cSRobert Weinmeister        }
59f4ff867cSRobert Weinmeister        $text = implode(self::DOKUWIKI_LINK_SPLITTER, $splitText);
60f4ff867cSRobert Weinmeister        return $text;
613543e422SRobert Weinmeister    }
623543e422SRobert Weinmeister
633543e422SRobert Weinmeister    function remove_protection_of_brackets_from_dokuwiki($text)
643543e422SRobert Weinmeister    {
653543e422SRobert Weinmeister        return str_replace(self::DOKUWIKI_LINK_START_MERMAID, '[[', str_replace(self::DOKUWIKI_LINK_END_MERMAID, ']]', $text));
663543e422SRobert Weinmeister    }
673543e422SRobert Weinmeister
68c6570b71SRobertWeinmeister    /** @inheritDoc */
69c6570b71SRobertWeinmeister    function getType()
70c6570b71SRobertWeinmeister    {
71c6570b71SRobertWeinmeister        return 'container';
72c6570b71SRobertWeinmeister    }
73c6570b71SRobertWeinmeister
74c6570b71SRobertWeinmeister    /** @inheritDoc */
75c6570b71SRobertWeinmeister    function getSort()
76c6570b71SRobertWeinmeister    {
77c6570b71SRobertWeinmeister        return 150;
78c6570b71SRobertWeinmeister    }
79c6570b71SRobertWeinmeister
80c6570b71SRobertWeinmeister    /**
81c6570b71SRobertWeinmeister    * Connect lookup pattern to lexer.
82c6570b71SRobertWeinmeister    *
83c6570b71SRobertWeinmeister    * @param string $mode Parser mode
84c6570b71SRobertWeinmeister    */
85c6570b71SRobertWeinmeister    function connectTo($mode)
86c6570b71SRobertWeinmeister    {
87c6570b71SRobertWeinmeister        $this->Lexer->addEntryPattern('<mermaid.*?>(?=.*?</mermaid>)',$mode,'plugin_mermaid');
88c6570b71SRobertWeinmeister    }
89c6570b71SRobertWeinmeister
90c6570b71SRobertWeinmeister    function postConnect()
91c6570b71SRobertWeinmeister    {
92c6570b71SRobertWeinmeister        $this->Lexer->addExitPattern('</mermaid>','plugin_mermaid');
93c6570b71SRobertWeinmeister    }
94c6570b71SRobertWeinmeister
95c6570b71SRobertWeinmeister    /**
967d8a2661SRobert Weinmeister    * Handle matches of the Mermaid syntax
97c6570b71SRobertWeinmeister    */
98c6570b71SRobertWeinmeister    function handle($match, $state, $pos, Doku_Handler $handler)
99c6570b71SRobertWeinmeister    {
100*da837cc9SRobert Weinmeister        switch ($state)
101*da837cc9SRobert Weinmeister        {
102c6570b71SRobertWeinmeister            case DOKU_LEXER_ENTER:
103c6570b71SRobertWeinmeister            return array($state, $match);
104c6570b71SRobertWeinmeister            case DOKU_LEXER_UNMATCHED:
105c6570b71SRobertWeinmeister            return array($state, $match);
106c6570b71SRobertWeinmeister            case DOKU_LEXER_EXIT:
107c6570b71SRobertWeinmeister            return array($state, '');
108c6570b71SRobertWeinmeister        }
109c6570b71SRobertWeinmeister        return false;
110c6570b71SRobertWeinmeister    }
111c6570b71SRobertWeinmeister
112c6570b71SRobertWeinmeister    /**
113c6570b71SRobertWeinmeister    * Render xhtml output or metadata
114c6570b71SRobertWeinmeister    */
115c6570b71SRobertWeinmeister    function render($mode, Doku_Renderer $renderer, $indata)
116c6570b71SRobertWeinmeister    {
117c6570b71SRobertWeinmeister        if($mode == 'xhtml'){
118c6570b71SRobertWeinmeister            list($state, $match) = $indata;
119c6570b71SRobertWeinmeister            switch ($state) {
120c6570b71SRobertWeinmeister                case DOKU_LEXER_ENTER:
1211da12d6eSRobert Weinmeister                    $this->mermaidCounter++;
122c6570b71SRobertWeinmeister                    $values = explode(" ", $match);
123c6570b71SRobertWeinmeister                    $divwidth = count($values) < 2 ? 'auto' : $values[1];
124c6570b71SRobertWeinmeister                    $divheight = count($values) < 3 ? 'auto' : substr($values[2], 0, -1);
1251da12d6eSRobert Weinmeister                    $renderer->doc .= '<div id="mermaidContainer'.$this->mermaidCounter.'" style="position: relative;"><span class="mermaid" id=mermaidContent'.$this->mermaidCounter.' style="width:'.$divwidth.'; height:'.$divheight.'">';
126c6570b71SRobertWeinmeister                break;
127c6570b71SRobertWeinmeister                case DOKU_LEXER_UNMATCHED:
128cc24cea2SRobert Weinmeister                    $explodedMatch = explode("\n", $match);
129cc24cea2SRobert Weinmeister                    $israwmode = isset($explodedMatch[1]) && strpos($explodedMatch[1], 'raw') !== false;
130cc24cea2SRobert Weinmeister                    if($israwmode)
131cc24cea2SRobert Weinmeister                    {
132cc24cea2SRobert Weinmeister                        array_shift($explodedMatch);
133cc24cea2SRobert Weinmeister                        array_shift($explodedMatch);
134cc24cea2SRobert Weinmeister                        $actualContent = implode("\n", $explodedMatch);
135cc24cea2SRobert Weinmeister                        $renderer->doc .= $actualContent;
136cc24cea2SRobert Weinmeister                    }
137cc24cea2SRobert Weinmeister                    else
138cc24cea2SRobert Weinmeister                    {
1393543e422SRobert Weinmeister                        $instructions = $this->p_get_instructions($this->protect_brackets_from_dokuwiki($match));
140*da837cc9SRobert Weinmeister                        if (strpos($instructions[2][1][0], "gantt"))
141*da837cc9SRobert Weinmeister                        {
142*da837cc9SRobert Weinmeister                            $instructions = $this->enable_gantt_links($instructions);
143*da837cc9SRobert Weinmeister                        }
1443543e422SRobert Weinmeister                        $xhtml = $this->remove_protection_of_brackets_from_dokuwiki($this->p_render($instructions));
145c6570b71SRobertWeinmeister                        $renderer->doc .= preg_replace("/(^[\r\n]*|[\r\n]+)[\s\t]*[\r\n]+/", "\n", $xhtml);
146cc24cea2SRobert Weinmeister                    }
147c6570b71SRobertWeinmeister                break;
148c6570b71SRobertWeinmeister                case DOKU_LEXER_EXIT:
1491da12d6eSRobert Weinmeister                    $renderer->doc .= "\r\n";
1501da12d6eSRobert Weinmeister                    $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>';
151c6570b71SRobertWeinmeister                break;
152c6570b71SRobertWeinmeister            }
153c6570b71SRobertWeinmeister            return true;
154c6570b71SRobertWeinmeister        }
155c6570b71SRobertWeinmeister        return false;
156c6570b71SRobertWeinmeister    }
157c6570b71SRobertWeinmeister
158c6570b71SRobertWeinmeister    /*
1597d8a2661SRobert Weinmeister    * Get the parser instructions suitable for the mermaid
160c6570b71SRobertWeinmeister    *
161c6570b71SRobertWeinmeister    */
162c6570b71SRobertWeinmeister    function p_get_instructions($text)
163c6570b71SRobertWeinmeister    {
164c6570b71SRobertWeinmeister        //import parser classes and mode definitions
165c6570b71SRobertWeinmeister        require_once DOKU_INC . 'inc/parser/parser.php';
166c6570b71SRobertWeinmeister
167c6570b71SRobertWeinmeister        // https://www.dokuwiki.org/devel:parser
168c6570b71SRobertWeinmeister        // https://www.dokuwiki.org/devel:parser#basic_invocation
169c6570b71SRobertWeinmeister        // Create the parser and the handler
170c6570b71SRobertWeinmeister        $Parser = new Parser(new Doku_Handler());
171c6570b71SRobertWeinmeister
172c6570b71SRobertWeinmeister        $modes = array();
173c6570b71SRobertWeinmeister
174c6570b71SRobertWeinmeister        // add default modes
175c6570b71SRobertWeinmeister        $std_modes = array( 'internallink', 'media', 'externallink');
176c6570b71SRobertWeinmeister
1773543e422SRobert Weinmeister        foreach($std_modes as $m)
1783543e422SRobert Weinmeister        {
179c6570b71SRobertWeinmeister            $class = 'dokuwiki\\Parsing\\ParserMode\\'.ucfirst($m);
180c6570b71SRobertWeinmeister            $obj   = new $class();
181c6570b71SRobertWeinmeister            $modes[] = array(
182c6570b71SRobertWeinmeister            'sort' => $obj->getSort(),
183c6570b71SRobertWeinmeister            'mode' => $m,
184c6570b71SRobertWeinmeister            'obj'  => $obj
185c6570b71SRobertWeinmeister            );
186c6570b71SRobertWeinmeister        }
187c6570b71SRobertWeinmeister
188c6570b71SRobertWeinmeister        // add formatting modes
189c6570b71SRobertWeinmeister        $fmt_modes = array( 'strong', 'emphasis', 'underline', 'monospace', 'subscript', 'superscript', 'deleted');
190c6570b71SRobertWeinmeister        foreach($fmt_modes as $m)
191c6570b71SRobertWeinmeister        {
192c6570b71SRobertWeinmeister          $obj   = new \dokuwiki\Parsing\ParserMode\Formatting($m);
193c6570b71SRobertWeinmeister          $modes[] = array(
194c6570b71SRobertWeinmeister            'sort' => $obj->getSort(),
195c6570b71SRobertWeinmeister            'mode' => $m,
196c6570b71SRobertWeinmeister            'obj'  => $obj
197c6570b71SRobertWeinmeister          );
198c6570b71SRobertWeinmeister        }
199c6570b71SRobertWeinmeister
200c6570b71SRobertWeinmeister        //add modes to parser
201c6570b71SRobertWeinmeister        foreach($modes as $mode)
202c6570b71SRobertWeinmeister        {
203c6570b71SRobertWeinmeister            $Parser->addMode($mode['mode'],$mode['obj']);
204c6570b71SRobertWeinmeister        }
205c6570b71SRobertWeinmeister
206c6570b71SRobertWeinmeister        // Do the parsing
207c6570b71SRobertWeinmeister        $p = $Parser->parse($text);
208c6570b71SRobertWeinmeister
209c6570b71SRobertWeinmeister        return $p;
210c6570b71SRobertWeinmeister    }
211c6570b71SRobertWeinmeister
212c6570b71SRobertWeinmeister    public function p_render($instructions)
213c6570b71SRobertWeinmeister    {
214c6570b71SRobertWeinmeister        $Renderer = p_get_renderer('mermaid');
215c6570b71SRobertWeinmeister
216c6570b71SRobertWeinmeister        // Loop through the instructions
217c6570b71SRobertWeinmeister        foreach ($instructions as $instruction) {
218c6570b71SRobertWeinmeister            if(method_exists($Renderer, $instruction[0])){
219c6570b71SRobertWeinmeister            call_user_func_array(array(&$Renderer, $instruction[0]), $instruction[1] ? $instruction[1] : array());
220c6570b71SRobertWeinmeister            }
221c6570b71SRobertWeinmeister        }
222c6570b71SRobertWeinmeister
223c6570b71SRobertWeinmeister        return $Renderer->doc;
224c6570b71SRobertWeinmeister    }
225c6570b71SRobertWeinmeister}