1<?php 2/** 3 * DokuWiki Plugin latexcaption (Main Syntax Component) 4 * 5 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html 6 * @author Ben van Magill <ben.vanmagill16@gmail.com> 7 * @author Till Biskup <till@till-biskup> 8 */ 9 10 11class syntax_plugin_latexcaption_caption extends \dokuwiki\Extension\SyntaxPlugin 12{ 13 14 /** 15 * Static variables set to keep track when scope is left. 16 */ 17 private static $_types = array('figure', 'table','codeblock','fileblock'); 18 private $caption_count = array(); 19 private $_type = ''; 20 private $_incaption = false; 21 private $_label = ''; 22 private $_opts = array(); 23 private $_parOpts = array(); 24 private $_nested = false; 25 26 /** @var $helper helper_plugin_latexcaption */ 27 var $helper = null; 28 29 30 function getInfo(){ 31 return confToHash(dirname(__FILE__).'/../plugin.info.txt'); 32 } 33 34 public function getType() { 35 return 'container'; 36 } 37 38 public function getAllowedTypes() { 39 return array('formatting', 'substition', 'disabled', 'container', 'protected'); 40 } 41 42 public function getPType() { 43 return 'block'; 44 } 45 46 public function getSort() { 47 return 310; 48 } 49 50 51 public function connectTo($mode) { 52 $this->Lexer->addSpecialPattern('{{setcounter>[a-z0-9=]+?}}',$mode,'plugin_latexcaption_caption'); 53 $this->Lexer->addEntryPattern('<figure.*?>(?=.*</figure>)',$mode,'plugin_latexcaption_caption'); 54 $this->Lexer->addEntryPattern('<table.*?>(?=.*</table>)',$mode,'plugin_latexcaption_caption'); 55 $this->Lexer->addEntryPattern('<codeblock.*?>(?=.*</codeblock>)',$mode,'plugin_latexcaption_caption'); 56 $this->Lexer->addEntryPattern('<fileblock.*?>(?=.*</fileblock>)',$mode,'plugin_latexcaption_caption'); 57 } 58 59 public function postConnect() { 60 $this->Lexer->addExitPattern('</figure>','plugin_latexcaption_caption'); 61 $this->Lexer->addExitPattern('</table>','plugin_latexcaption_caption'); 62 $this->Lexer->addExitPattern('</codeblock>','plugin_latexcaption_caption'); 63 $this->Lexer->addExitPattern('</fileblock>','plugin_latexcaption_caption'); 64 65 $captiontag = $this->getConf('captiontag'); 66 $this->Lexer->addPattern('<'.$captiontag.'>(?=.*</'.$captiontag.'>)','plugin_latexcaption_caption'); 67 $this->Lexer->addPattern('</'.$captiontag.'>','plugin_latexcaption_caption'); 68 } 69 70 private function isSubType($type) { 71 return (substr($type, 0, 3) == 'sub'); 72 } 73 74 private function getParType($type) { 75 return substr($type, 3); 76 } 77 78 public function handle($match, $state, $pos, Doku_Handler $handler){ 79 global $caption_count; 80 81 $params = []; 82 if ($state == DOKU_LEXER_ENTER){ 83 // INPUT $match:<type opts|label> 84 // Strip the <> 85 $match = substr($match,1,-1); 86 // Retrieve the label name if any 87 $match_arr = explode('|', $match, 2); 88 $matches = $match_arr[0]; 89 $label = count($match_arr) == 2 ? $match_arr[1] : ''; 90 91 // retrieve type and options if any 92 $match_arr = explode(' ', trim($matches), 2); 93 $type = $match_arr[0]; 94 $opts = count($match_arr) == 2 ? $match_arr[1] : ''; 95 // explode again in the case of multiple options; 96 $opts = (!empty($opts) ? explode(' ', $opts) : ['noalign',]); 97 98 // Set dynamic class counter variable 99 $type_counter_def = '_'.$type.'_count'; 100 101 // Increment the counter of relevant type 102 // Store the type in class for caption match to determine what html tag to use 103 // This is ok since we will never have nested figures and more than one caption 104 $this->_type = $type; 105 $this->_label = $label; 106 $this->_opts = $opts; 107 $this->{$type_counter_def} = (!isset($this->{$type_counter_def}) ? 1 : $this->{$type_counter_def}+1); 108 109 // save params to class variables (cached for use in render) 110 $type_counter = $this->{$type_counter_def}; 111 112 // Set global variable if theres a label (used for ref) 113 if ($label) { 114 $label = trim($label); 115 // Check if we are counting a subtype to store parent in array for references 116 if ($this->isSubType($type)) { 117 $partype = $this->getParType($type); 118 $parcount = $this->{'_'.$partype.'_count'}; 119 } 120 // Get caption metadata ready for use in reference syntax. Also store in global array for preview to render correctly 121 $this->caption_count[$label] = array($type, $type_counter, $parcount); 122 $caption_count = $this->caption_count; 123 } 124 125 //Save parent options for use later 126 if (!$this->isSubType($type)){ 127 $this->_parOpts = $opts; 128 } else { 129 $this->_nested = true; 130 } 131 132 // Set the params 133 $params['xhtml']['tagtype'] = (in_array($type, ['figure', 'subfigure']) ? 'figure' : 'div'); 134 $params['label'] = $label; 135 $params['opts'] = $opts; 136 $params['type'] = $type; 137 $params['meta'] = $this->caption_count; 138 139 return array($state, $match, $pos, $params); 140 } 141 if ($state == DOKU_LEXER_MATCHED){ 142 // Case of caption. 143 // Toggle the incaption flag 144 $this->_incaption = !$this->_incaption; 145 $type = $this->_type; 146 $params['label'] = $this->_label; 147 $params['xhtml']['captagtype'] = (in_array($type, ['figure', 'subfigure']) ? 'figcaption' : 'div'); 148 $params['incaption'] = $this->_incaption; 149 $params['type_counter'] = $this->{'_'.$type.'_count'}; 150 $params['type'] = $type; 151 // Decide what caption options to send to renderer 152 $params['opts'] = ($this->_nested ? $this->_opts : $this->_parOpts); 153 154 155 return array($state, $match, $pos, $params); 156 } 157 if ($state == DOKU_LEXER_UNMATCHED){ 158 return array($state, $match, $pos, $params); 159 } 160 if ($state == DOKU_LEXER_EXIT){ 161 $type = $this->_type; 162 163 if (substr($type, 0, 3) == 'sub') { 164 // Change environment back to non sub type 165 $this->_type = substr($type, 3); 166 $this->_nested = false; 167 } 168 else { 169 // reset subtype counter 170 $this->{'_sub'.$type.'_count'} = 0; 171 } 172 $params['type'] = $type; 173 $params['xhtml']['tagtype'] = (in_array($type, ['figure', 'subfigure']) ? 'figure' : 'div'); 174 return array($state, $match, $pos, $params); 175 } 176 if ($state == DOKU_LEXER_SPECIAL){ 177 if (substr($match,0,13) != '{{setcounter>') { 178 return true; 179 } 180 181 $match = substr($match,13,-2); 182 list($type,$num) = explode('=',trim($match)); 183 184 $type = trim($type); 185 $num = (int) trim($num); 186 187 if (!in_array($type,$this::$_types)) { 188 return false; 189 } 190 191 // Update the counter. offset by 1 since counter is incremented on caption enter 192 $this->{'_'.$type.'_count'} = $num-1; 193 194 return true; 195 } 196 // this should never be reached 197 return true; 198 } 199 200 public function render($mode, Doku_Renderer $renderer, $data) { 201 202 if (empty($data)) { 203 return false; 204 } 205 206 list($state, $match, $pos, $params) = $data; 207 208 $langset = ($this->getConf('abbrev') ? 'abbrev' : 'long'); 209 210 if (!in_array($mode, ['metadata', 'xhtml','odt', 'latex'])) { 211 return true; 212 } 213 214 /** @var Doku_Renderer_metadata $renderer */ 215 // Store refs into metadata if there was any labels 216 if ($mode == 'metadata') { 217 if (empty($params['meta'])) return true; 218 219 $renderer->meta['plugin']['latexcaption']['references'] = $params['meta']; 220 return true; 221 } 222 223 /** @var Doku_Renderer_xhtml $renderer */ 224 elseif ($mode == 'xhtml') { 225 if ($state == DOKU_LEXER_ENTER) { 226 // We know we already have a valid type on entering this 227 $type = $params['type']; 228 $tagtype = $params['xhtml']['tagtype']; 229 $label = $params['label']; 230 $opts = $params['opts']; 231 232 // print alignment/additional classes 233 $classes = implode(' plugin_latexcaption_', $opts); 234 // Rendering 235 $markup = '<'.$tagtype.' class="plugin_latexcaption_'.$type.' plugin_latexcaption_'.$classes.'" '; 236 237 if ($label){ 238 $markup .= 'id="'.$renderer->_xmlEntities($label).'"'; 239 } 240 241 $markup .= '>'; 242 $renderer->doc .= $markup; 243 return true; 244 } 245 246 if ($state == DOKU_LEXER_MATCHED) { 247 $captagtype = $params['xhtml']['captagtype']; 248 $incaption = $params['incaption']; 249 $count = $params['type_counter']; 250 $type = $params['type']; 251 $label = $params['label']; 252 $opts = $params['opts']; 253 254 $separator = (in_array('blank', $opts) ? '' : ': '); 255 256 if (!$this->helper) 257 $this->helper = plugin_load('helper', 'latexcaption'); 258 259 // Rendering a caption 260 if ($incaption) { 261 $markup .= '<'.$captagtype.' class="plugin_latexcaption_caption"><span class="plugin_latexcaption_caption_number"'; 262 // Set title to label 263 // if ($label) $markup .= ' title="'.$label.'"'; 264 $markup .= '>'; 265 if (substr($type, 0, 3) == 'sub') { 266 $markup .= '('.$this->helper->number_to_alphabet($count).') '; 267 } 268 else { 269 $markup .= $this->getLang($type.$langset).' '. $count.$separator; 270 } 271 $markup .= '</span><span class="plugin_latexcaption_caption_text">'; 272 273 $renderer->doc .= $markup; 274 } 275 else { 276 $renderer->doc .= "</span></$captagtype>"; 277 278 } 279 return true; 280 } 281 282 if ($state == DOKU_LEXER_UNMATCHED) { 283 // return the dokuwiki markup within the figure tags 284 $renderer->doc .= $renderer->_xmlEntities($match); 285 } 286 287 if ($state == DOKU_LEXER_EXIT) { 288 $tagtype = $params['xhtml']['tagtype']; 289 $renderer->doc .= "</$tagtype>"; 290 return true; 291 } 292 } 293 294 elseif ($mode == 'latex') { 295 // All Doku $states get type param 296 // Only figure and table supported. 297 $type = $params['type']; 298 if (!in_array($type, ['figure', 'table', 'subfigure'])){ 299 return true; 300 } 301 302 if ($state == DOKU_LEXER_ENTER) { 303 // Render 304 $renderer->doc .= "\begin{$type}"; 305 return true; 306 } 307 if ($state == DOKU_LEXER_MATCHED) { 308 $incaption = $params['incaption']; 309 $label = $params['label']; 310 if ($incaption) { 311 $out = '\caption{'; 312 } else { 313 $out = '}'; 314 if ($label){ 315 $out .= "\n" . "\label{$label}"; 316 } 317 } 318 $renderer->doc .= $out; 319 return true; 320 } 321 if ($state == DOKU_LEXER_UNMATCHED) { 322 // Pass it through 323 $renderer->doc .= $match; 324 return true; 325 } 326 if ($state == DOKU_LEXER_EXIT) { 327 $renderer->doc .= "\end{$type}"; 328 return true; 329 } 330 } 331 332 /** 333 * WARNING: The odt mode seems to work in general, but strange things happen 334 * with the tables - therefore, within the table tags only a table 335 * is allowed, without any additional markup. 336 */ 337 elseif ($mode == 'odt') { 338 // All Doku $states get type param 339 // Only figure and table supported. 340 $type = $params['type']; 341 if (!in_array($type, ['figure', 'table'])) { 342 return true; 343 } 344 345 if ($state == DOKU_LEXER_ENTER) { 346 $renderer->p_open(); 347 return true; 348 } 349 if ($state == DOKU_LEXER_MATCHED) { 350 $incaption = $params['incaption']; 351 $count = $params['type_counter']; 352 $type = $params['type']; 353 $label = $params['label']; 354 355 if ($incaption) { 356 $renderer->p_close(); 357 $style_name = ($type == 'figure' ? 'Illustration' : 'Table'); 358 $labelname = ($label ? $label : 'ref'.$style_name.$count); 359 360 $out = '<text:p text:style-name="'.$style_name.'">'; 361 $out .= $this->getLang($type.$langset); 362 $out .= '<text:sequence text:ref-name="'.$labelname.'"'; 363 $out .= 'text:name="'.$style_name.'" text:formula="ooow:'.$style_name.'+1" style:num-format="1">'; 364 $out .= ' ' . $count . '</text:sequence>: '; 365 366 $renderer->doc .= $out; 367 } else { 368 $renderer->doc .= '</text:p>'; 369 $renderer->p_open(); 370 } 371 return true; 372 } 373 if ($state == DOKU_LEXER_UNMATCHED) { 374 // Pass it through 375 $renderer->cdata($match); 376 return true; 377 } 378 if ($state == DOKU_LEXER_EXIT) { 379 $renderer->p_close(); 380 return true; 381 } 382 } 383 } 384} 385 386// vim:ts=4:sw=4:et: 387