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 9*ea08b541SRobert Weinmeisterif (!defined('DOKU_INC')) die(); 10*ea08b541SRobert Weinmeister 11c6570b71SRobertWeinmeisteruse dokuwiki\Parsing\Parser; 12c6570b71SRobertWeinmeister 13c6570b71SRobertWeinmeisterclass syntax_plugin_mermaid extends \dokuwiki\Extension\SyntaxPlugin 14c6570b71SRobertWeinmeister{ 153543e422SRobert Weinmeister const DOKUWIKI_LINK_START_MERMAID = '<code>DOKUWIKILINKSTARTMERMAID</code>'; 163543e422SRobert Weinmeister const DOKUWIKI_LINK_END_MERMAID = '<code>DOKUWIKILINKENDMERMAID</code>'; 17f4ff867cSRobert Weinmeister const DOKUWIKI_LINK_SPLITTER ='--'; 18172fa282SRobert Weinmeister const DOKUWIKI_SVG_SAVE = '<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>'; 19172fa282SRobert Weinmeister const DOKUWIKI_SVG_LOCKED = '<svg viewBox="0 0 16 16" fill="none" style="width: 24px; height: 24px;"><path fill-rule="evenodd" clip-rule="evenodd" d="M4 6V4C4 1.79086 5.79086 0 8 0C10.2091 0 12 1.79086 12 4V6H14V16H2V6H4ZM6 4C6 2.89543 6.89543 2 8 2C9.10457 2 10 2.89543 10 4V6H6V4ZM7 13V9H9V13H7Z" fill="#000000"/></svg>'; 20172fa282SRobert Weinmeister const DOKUWIKI_SVG_UNLOCKED = '<svg style="width: 24px; height: 24px;" viewBox="0 0 16 16" fill="none"><path fill-rule="evenodd" clip-rule="evenodd" d="M11.5 2C10.6716 2 10 2.67157 10 3.5V6H13V16H1V6H8V3.5C8 1.567 9.567 0 11.5 0C13.433 0 15 1.567 15 3.5V4H13V3.5C13 2.67157 12.3284 2 11.5 2ZM9 10H5V12H9V10Z" fill="#000000"/></svg>'; 213543e422SRobert Weinmeister 221da12d6eSRobert Weinmeister private $mermaidCounter = -1; 23172fa282SRobert Weinmeister private $mermaidContent; 24172fa282SRobert Weinmeister private $currentMermaidIsLocked = false; 25172fa282SRobert Weinmeister private $mermaidContentIfLocked; 261da12d6eSRobert Weinmeister 27da837cc9SRobert Weinmeister function enable_gantt_links($instructions) 28da837cc9SRobert Weinmeister { 29da837cc9SRobert Weinmeister $modified_instructions = $instructions; 30da837cc9SRobert Weinmeister 31da837cc9SRobert Weinmeister for ($i = 0; $i < count($modified_instructions); $i++) 32da837cc9SRobert Weinmeister { 33da837cc9SRobert Weinmeister if (in_array($modified_instructions[$i][0], ["externallink", "internallink"])) 34da837cc9SRobert Weinmeister { 35da837cc9SRobert Weinmeister // use the appropriate link 36da837cc9SRobert Weinmeister $link = $modified_instructions[$i][0] == "externallink" ? $modified_instructions[$i][1][0] : wl($modified_instructions[$i][1][0], '', true); 37da837cc9SRobert Weinmeister 38da837cc9SRobert Weinmeister // change link here to just the name of the link 39da837cc9SRobert Weinmeister $modified_instructions[$i][0]= "cdata"; 40da837cc9SRobert Weinmeister if(!is_null($modified_instructions[$i][1][1])) 41da837cc9SRobert Weinmeister { 42da837cc9SRobert Weinmeister unset($modified_instructions[$i][1][0]); 43da837cc9SRobert Weinmeister } 44da837cc9SRobert Weinmeister 45da837cc9SRobert Weinmeister // insert the click event 46da837cc9SRobert Weinmeister if (preg_match('/(?<=:\s)\S+(?=,)/', $modified_instructions[$i+1][1][0], $output_array)) 47da837cc9SRobert Weinmeister { 48da837cc9SRobert Weinmeister $click_reference = $output_array[0]; 49da837cc9SRobert Weinmeister } 50da837cc9SRobert Weinmeister array_splice($modified_instructions, $i + 2, 0, [["cdata", ["\nclick ".$click_reference." href \"".$link."\"\n"]]]); 51da837cc9SRobert Weinmeister 52da837cc9SRobert Weinmeister // encode colons 53da837cc9SRobert Weinmeister $modified_instructions[$i][1][0] = str_replace(":", "#colon;", $modified_instructions[$i][1][0]); 54da837cc9SRobert Weinmeister } 55da837cc9SRobert Weinmeister } 56da837cc9SRobert Weinmeister 57da837cc9SRobert Weinmeister return $modified_instructions; 58da837cc9SRobert Weinmeister } 59da837cc9SRobert Weinmeister 603543e422SRobert Weinmeister function protect_brackets_from_dokuwiki($text) 613543e422SRobert Weinmeister { 62f4ff867cSRobert Weinmeister $splitText = explode(self::DOKUWIKI_LINK_SPLITTER, $text); 63f4ff867cSRobert Weinmeister foreach ($splitText as $key => $line) 64f4ff867cSRobert Weinmeister { 65f4ff867cSRobert Weinmeister $splitText[$key] = preg_replace('/(?<!["\[(\s])(\[\[)(.*)(\]\])/', self::DOKUWIKI_LINK_START_MERMAID . '$2' . self::DOKUWIKI_LINK_END_MERMAID, $line); 66f4ff867cSRobert Weinmeister } 67f4ff867cSRobert Weinmeister $text = implode(self::DOKUWIKI_LINK_SPLITTER, $splitText); 68f4ff867cSRobert Weinmeister return $text; 693543e422SRobert Weinmeister } 703543e422SRobert Weinmeister 713543e422SRobert Weinmeister function remove_protection_of_brackets_from_dokuwiki($text) 723543e422SRobert Weinmeister { 733543e422SRobert Weinmeister return str_replace(self::DOKUWIKI_LINK_START_MERMAID, '[[', str_replace(self::DOKUWIKI_LINK_END_MERMAID, ']]', $text)); 743543e422SRobert Weinmeister } 753543e422SRobert Weinmeister 76c6570b71SRobertWeinmeister /** @inheritDoc */ 77c6570b71SRobertWeinmeister function getType() 78c6570b71SRobertWeinmeister { 79c6570b71SRobertWeinmeister return 'container'; 80c6570b71SRobertWeinmeister } 81c6570b71SRobertWeinmeister 82c6570b71SRobertWeinmeister /** @inheritDoc */ 83c6570b71SRobertWeinmeister function getSort() 84c6570b71SRobertWeinmeister { 85c6570b71SRobertWeinmeister return 150; 86c6570b71SRobertWeinmeister } 87c6570b71SRobertWeinmeister 88c6570b71SRobertWeinmeister /** 89c6570b71SRobertWeinmeister * Connect lookup pattern to lexer. 90c6570b71SRobertWeinmeister * 91c6570b71SRobertWeinmeister * @param string $mode Parser mode 92c6570b71SRobertWeinmeister */ 93c6570b71SRobertWeinmeister function connectTo($mode) 94c6570b71SRobertWeinmeister { 95c6570b71SRobertWeinmeister $this->Lexer->addEntryPattern('<mermaid.*?>(?=.*?</mermaid>)',$mode,'plugin_mermaid'); 96c6570b71SRobertWeinmeister } 97c6570b71SRobertWeinmeister 98c6570b71SRobertWeinmeister function postConnect() 99c6570b71SRobertWeinmeister { 100c6570b71SRobertWeinmeister $this->Lexer->addExitPattern('</mermaid>','plugin_mermaid'); 101c6570b71SRobertWeinmeister } 102c6570b71SRobertWeinmeister 103c6570b71SRobertWeinmeister /** 1047d8a2661SRobert Weinmeister * Handle matches of the Mermaid syntax 105c6570b71SRobertWeinmeister */ 106c6570b71SRobertWeinmeister function handle($match, $state, $pos, Doku_Handler $handler) 107c6570b71SRobertWeinmeister { 108da837cc9SRobert Weinmeister switch ($state) 109da837cc9SRobert Weinmeister { 110c6570b71SRobertWeinmeister case DOKU_LEXER_ENTER: 111c6570b71SRobertWeinmeister return array($state, $match); 112c6570b71SRobertWeinmeister case DOKU_LEXER_UNMATCHED: 113c6570b71SRobertWeinmeister return array($state, $match); 114c6570b71SRobertWeinmeister case DOKU_LEXER_EXIT: 115c6570b71SRobertWeinmeister return array($state, ''); 116c6570b71SRobertWeinmeister } 117c6570b71SRobertWeinmeister return false; 118c6570b71SRobertWeinmeister } 119c6570b71SRobertWeinmeister 120c6570b71SRobertWeinmeister /** 121c6570b71SRobertWeinmeister * Render xhtml output or metadata 122c6570b71SRobertWeinmeister */ 123c6570b71SRobertWeinmeister function render($mode, Doku_Renderer $renderer, $indata) 124c6570b71SRobertWeinmeister { 125c6570b71SRobertWeinmeister if($mode == 'xhtml'){ 126c6570b71SRobertWeinmeister list($state, $match) = $indata; 127c6570b71SRobertWeinmeister switch ($state) { 128c6570b71SRobertWeinmeister case DOKU_LEXER_ENTER: 1291da12d6eSRobert Weinmeister $this->mermaidCounter++; 130c6570b71SRobertWeinmeister $values = explode(" ", $match); 131c6570b71SRobertWeinmeister $divwidth = count($values) < 2 ? 'auto' : $values[1]; 132c6570b71SRobertWeinmeister $divheight = count($values) < 3 ? 'auto' : substr($values[2], 0, -1); 133172fa282SRobert Weinmeister $this->mermaidContent .= '<div id="mermaidContainer'.$this->mermaidCounter.'" style="position: relative; width:'.$divwidth.'; height:'.$divheight.'">'; 134172fa282SRobert Weinmeister $this->mermaidContentIfLocked = $this->mermaidContent . '<span class="mermaidlocked" id=mermaidContent'.$this->mermaidCounter.' style="width:'.$divwidth.'; height:'.$divheight.'">'; 135172fa282SRobert Weinmeister $this->mermaidContent .= '<span class="mermaid" id=mermaidContent'.$this->mermaidCounter.' style="width:'.$divwidth.'; height:'.$divheight.'">'; 136c6570b71SRobertWeinmeister break; 137c6570b71SRobertWeinmeister case DOKU_LEXER_UNMATCHED: 138cc24cea2SRobert Weinmeister $explodedMatch = explode("\n", $match); 139172fa282SRobert Weinmeister 140172fa282SRobert Weinmeister if(str_starts_with($explodedMatch[1], '%%<svg')) 141172fa282SRobert Weinmeister { 142172fa282SRobert Weinmeister $this->currentMermaidIsLocked = true; 143172fa282SRobert Weinmeister $this->mermaidContent = $this->mermaidContentIfLocked . substr($explodedMatch[1], 2); 144172fa282SRobert Weinmeister break; 145172fa282SRobert Weinmeister } 146172fa282SRobert Weinmeister else 147172fa282SRobert Weinmeister { 148172fa282SRobert Weinmeister $this->currentMermaidIsLocked = false; 149172fa282SRobert Weinmeister } 150172fa282SRobert Weinmeister 151cc24cea2SRobert Weinmeister $israwmode = isset($explodedMatch[1]) && strpos($explodedMatch[1], 'raw') !== false; 152cc24cea2SRobert Weinmeister if($israwmode) 153cc24cea2SRobert Weinmeister { 154cc24cea2SRobert Weinmeister array_shift($explodedMatch); 155cc24cea2SRobert Weinmeister array_shift($explodedMatch); 156cc24cea2SRobert Weinmeister $actualContent = implode("\n", $explodedMatch); 157172fa282SRobert Weinmeister $this->mermaidContent .= $actualContent; 158cc24cea2SRobert Weinmeister } 159cc24cea2SRobert Weinmeister else 160cc24cea2SRobert Weinmeister { 1613543e422SRobert Weinmeister $instructions = $this->p_get_instructions($this->protect_brackets_from_dokuwiki($match)); 162da837cc9SRobert Weinmeister if (strpos($instructions[2][1][0], "gantt")) 163da837cc9SRobert Weinmeister { 164da837cc9SRobert Weinmeister $instructions = $this->enable_gantt_links($instructions); 165da837cc9SRobert Weinmeister } 1663543e422SRobert Weinmeister $xhtml = $this->remove_protection_of_brackets_from_dokuwiki($this->p_render($instructions)); 167172fa282SRobert Weinmeister $this->mermaidContent .= preg_replace("/(^[\r\n]*|[\r\n]+)[\s\t]*[\r\n]+/", "\n", $xhtml); 168cc24cea2SRobert Weinmeister } 169c6570b71SRobertWeinmeister break; 170c6570b71SRobertWeinmeister case DOKU_LEXER_EXIT: 171172fa282SRobert Weinmeister $this->mermaidContent .= "\r\n</span>"; 172172fa282SRobert Weinmeister $this->mermaidContent .= '<fieldset id="mermaidFieldset'.$this->mermaidCounter.'" style="position: absolute; top: 0; left: 0; display: none; width:auto; border: none">'; 173172fa282SRobert Weinmeister $this->mermaidContent .= '<button id="mermaidButtonSave'.$this->mermaidCounter.'" style="z-index: 10; display: block; padding: 0; margin: 0; border: none; background: none; width: 24px; height: 24px;">'.self::DOKUWIKI_SVG_SAVE.'</button>'; 174172fa282SRobert Weinmeister $this->mermaidContent .= '<button id="mermaidButtonPermanent'.$this->mermaidCounter.'" style="z-index: 10; display: block; padding: 0; margin: 0; border: none; background: none; width: 24px; height: 24px;">'.($this->currentMermaidIsLocked ? self::DOKUWIKI_SVG_LOCKED : self::DOKUWIKI_SVG_UNLOCKED).'</button>'; 175172fa282SRobert Weinmeister $this->mermaidContent .= '</fieldset></div>'; 176172fa282SRobert Weinmeister 177172fa282SRobert Weinmeister $renderer->doc .= $this->mermaidContent; 178172fa282SRobert Weinmeister $this->mermaidContent = ''; 179c6570b71SRobertWeinmeister break; 180c6570b71SRobertWeinmeister } 181c6570b71SRobertWeinmeister return true; 182c6570b71SRobertWeinmeister } 183c6570b71SRobertWeinmeister return false; 184c6570b71SRobertWeinmeister } 185c6570b71SRobertWeinmeister 186c6570b71SRobertWeinmeister /* 1877d8a2661SRobert Weinmeister * Get the parser instructions suitable for the mermaid 188c6570b71SRobertWeinmeister * 189c6570b71SRobertWeinmeister */ 190c6570b71SRobertWeinmeister function p_get_instructions($text) 191c6570b71SRobertWeinmeister { 192c6570b71SRobertWeinmeister //import parser classes and mode definitions 193c6570b71SRobertWeinmeister require_once DOKU_INC . 'inc/parser/parser.php'; 194c6570b71SRobertWeinmeister 195c6570b71SRobertWeinmeister // https://www.dokuwiki.org/devel:parser 196c6570b71SRobertWeinmeister // https://www.dokuwiki.org/devel:parser#basic_invocation 197c6570b71SRobertWeinmeister // Create the parser and the handler 198c6570b71SRobertWeinmeister $Parser = new Parser(new Doku_Handler()); 199c6570b71SRobertWeinmeister 200c6570b71SRobertWeinmeister $modes = array(); 201c6570b71SRobertWeinmeister 202c6570b71SRobertWeinmeister // add default modes 203c6570b71SRobertWeinmeister $std_modes = array( 'internallink', 'media', 'externallink'); 204c6570b71SRobertWeinmeister 2053543e422SRobert Weinmeister foreach($std_modes as $m) 2063543e422SRobert Weinmeister { 207c6570b71SRobertWeinmeister $class = 'dokuwiki\\Parsing\\ParserMode\\'.ucfirst($m); 208c6570b71SRobertWeinmeister $obj = new $class(); 209c6570b71SRobertWeinmeister $modes[] = array( 210c6570b71SRobertWeinmeister 'sort' => $obj->getSort(), 211c6570b71SRobertWeinmeister 'mode' => $m, 212c6570b71SRobertWeinmeister 'obj' => $obj 213c6570b71SRobertWeinmeister ); 214c6570b71SRobertWeinmeister } 215c6570b71SRobertWeinmeister 216c6570b71SRobertWeinmeister // add formatting modes 217c6570b71SRobertWeinmeister $fmt_modes = array( 'strong', 'emphasis', 'underline', 'monospace', 'subscript', 'superscript', 'deleted'); 218c6570b71SRobertWeinmeister foreach($fmt_modes as $m) 219c6570b71SRobertWeinmeister { 220c6570b71SRobertWeinmeister $obj = new \dokuwiki\Parsing\ParserMode\Formatting($m); 221c6570b71SRobertWeinmeister $modes[] = array( 222c6570b71SRobertWeinmeister 'sort' => $obj->getSort(), 223c6570b71SRobertWeinmeister 'mode' => $m, 224c6570b71SRobertWeinmeister 'obj' => $obj 225c6570b71SRobertWeinmeister ); 226c6570b71SRobertWeinmeister } 227c6570b71SRobertWeinmeister 228c6570b71SRobertWeinmeister //add modes to parser 229c6570b71SRobertWeinmeister foreach($modes as $mode) 230c6570b71SRobertWeinmeister { 231c6570b71SRobertWeinmeister $Parser->addMode($mode['mode'],$mode['obj']); 232c6570b71SRobertWeinmeister } 233c6570b71SRobertWeinmeister 234c6570b71SRobertWeinmeister // Do the parsing 235c6570b71SRobertWeinmeister $p = $Parser->parse($text); 236c6570b71SRobertWeinmeister 237c6570b71SRobertWeinmeister return $p; 238c6570b71SRobertWeinmeister } 239c6570b71SRobertWeinmeister 240c6570b71SRobertWeinmeister public function p_render($instructions) 241c6570b71SRobertWeinmeister { 242c6570b71SRobertWeinmeister $Renderer = p_get_renderer('mermaid'); 243c6570b71SRobertWeinmeister 244c6570b71SRobertWeinmeister // Loop through the instructions 245c6570b71SRobertWeinmeister foreach ($instructions as $instruction) { 246c6570b71SRobertWeinmeister if(method_exists($Renderer, $instruction[0])){ 247c6570b71SRobertWeinmeister call_user_func_array(array(&$Renderer, $instruction[0]), $instruction[1] ? $instruction[1] : array()); 248c6570b71SRobertWeinmeister } 249c6570b71SRobertWeinmeister } 250c6570b71SRobertWeinmeister 251c6570b71SRobertWeinmeister return $Renderer->doc; 252c6570b71SRobertWeinmeister } 253c6570b71SRobertWeinmeister}