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