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