1<?php 2 3/** 4 * Plugin Columns: Syntax & rendering 5 * 6 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 7 * @author Mykola Ostrovskyy <dwpforge@gmail.com> 8 * Based on plugin by Michael Arlt <michael.arlt [at] sk-schwanstetten [dot] de> 9 */ 10 11class syntax_plugin_columns extends DokuWiki_Syntax_Plugin { 12 13 private $mode; 14 private $lexerSyntax; 15 private $syntax; 16 private $xhtmlRenderer; 17 private $odtRenderer; 18 19 /** 20 * Constructor 21 */ 22 public function __construct() { 23 $this->mode = substr(get_class($this), 7); 24 25 $columns = $this->getColumnsTagName(); 26 $newColumn = $this->getNewColumnTagName(); 27 if ($this->getConf('wrapnewcol') == 1) { 28 $newColumnLexer = '<' . $newColumn . '(?:>|\s.*?>)'; 29 $newColumnHandler = '<' . $newColumn . '(.*?)>'; 30 } 31 else { 32 $newColumnLexer = $newColumn; 33 $newColumnHandler = $newColumn; 34 } 35 $enterLexer = '<' . $columns . '(?:>|\s.*?>)'; 36 $enterHandler = '<' . $columns . '(.*?)>'; 37 $exit = '<\/' . $columns . '>'; 38 39 $this->lexerSyntax['enter'] = $enterLexer; 40 $this->lexerSyntax['newcol'] = $newColumnLexer; 41 $this->lexerSyntax['exit'] = $exit; 42 43 $this->syntax[DOKU_LEXER_ENTER] = '/' . $enterHandler . '/'; 44 $this->syntax[DOKU_LEXER_MATCHED] = '/' . $newColumnHandler . '/'; 45 $this->syntax[DOKU_LEXER_EXIT] = '/' . $exit . '/'; 46 } 47 48 /** 49 * What kind of syntax are we? 50 */ 51 public function getType() { 52 return 'substition'; 53 } 54 55 public function getPType() { 56 return 'block'; 57 } 58 59 /** 60 * Where to sort in? 61 */ 62 public function getSort() { 63 return 65; 64 } 65 66 public function connectTo($mode) { 67 $this->Lexer->addSpecialPattern($this->lexerSyntax['enter'], $mode, $this->mode); 68 $this->Lexer->addSpecialPattern($this->lexerSyntax['newcol'], $mode, $this->mode); 69 $this->Lexer->addSpecialPattern($this->lexerSyntax['exit'], $mode, $this->mode); 70 } 71 72 /** 73 * Handle the match 74 */ 75 public function handle($match, $state, $pos, Doku_Handler $handler) { 76 foreach ($this->syntax as $state => $pattern) { 77 if (preg_match($pattern, $match, $data) == 1) { 78 break; 79 } 80 } 81 switch ($state) { 82 case DOKU_LEXER_ENTER: 83 case DOKU_LEXER_MATCHED: 84 return array($state, preg_split('/\s+/', $data[1], -1, PREG_SPLIT_NO_EMPTY)); 85 86 case DOKU_LEXER_EXIT: 87 return array($state, array()); 88 } 89 return false; 90 } 91 92 /** 93 * Create output 94 */ 95 public function render($mode, Doku_Renderer $renderer, $data) { 96 $columnsRenderer = $this->getRenderer($mode, $renderer); 97 98 if ($columnsRenderer != NULL) { 99 $columnsRenderer->render($data[0], $renderer, $data[1]); 100 return true; 101 } 102 return false; 103 } 104 105 /** 106 * 107 */ 108 private function getRenderer($mode, Doku_Renderer $renderer) { 109 switch ($mode) { 110 case 'xhtml': 111 if ($this->xhtmlRenderer == NULL) { 112 $this->xhtmlRenderer = new columns_renderer_xhtml(); 113 } 114 return $this->xhtmlRenderer; 115 116 case 'odt': 117 if ($this->odtRenderer == NULL) { 118 if (method_exists($renderer, 'getODTPropertiesFromElement')) { 119 $this->odtRenderer = new columns_renderer_odt_v2(); 120 } 121 else { 122 $this->odtRenderer = new columns_renderer_odt_v1(); 123 } 124 } 125 return $this->odtRenderer; 126 } 127 128 return NULL; 129 } 130 131 /** 132 * Returns columns tag 133 */ 134 private function getColumnsTagName() { 135 $tag = $this->getConf('kwcolumns'); 136 if ($tag == '') { 137 $tag = $this->getLang('kwcolumns'); 138 } 139 return $tag; 140 } 141 142 /** 143 * Returns new column tag 144 */ 145 private function getNewColumnTagName() { 146 $tag = $this->getConf('kwnewcol'); 147 if ($tag == '') { 148 $tag = $this->getLang('kwnewcol'); 149 } 150 return $tag; 151 } 152} 153 154/** 155 * Base class for columns rendering. 156 */ 157abstract class columns_renderer { 158 /** 159 * 160 */ 161 public function render($state, Doku_Renderer $renderer, $attribute) { 162 switch ($state) { 163 case DOKU_LEXER_ENTER: 164 $this->render_enter($renderer, $attribute); 165 break; 166 167 case DOKU_LEXER_MATCHED: 168 $this->render_matched($renderer, $attribute); 169 break; 170 171 case DOKU_LEXER_EXIT: 172 $this->render_exit($renderer, $attribute); 173 break; 174 } 175 } 176 177 abstract protected function render_enter(Doku_Renderer $renderer, $attribute); 178 abstract protected function render_matched(Doku_Renderer $renderer, $attribute); 179 abstract protected function render_exit(Doku_Renderer $renderer, $attribute); 180 181 /** 182 * 183 */ 184 protected function getAttribute($attribute, $name) { 185 $result = ''; 186 if (array_key_exists($name, $attribute)) { 187 $result = $attribute[$name]; 188 } 189 return $result; 190 } 191 192 /** 193 * 194 */ 195 protected function getStyle($attribute, $attributeName, $styleName = '') { 196 $result = $this->getAttribute($attribute, $attributeName); 197 if ($result != '') { 198 if ($styleName == '') { 199 $styleName = $attributeName; 200 } 201 $result = $styleName . ':' . $result . ';'; 202 } 203 return $result; 204 } 205} 206 207/** 208 * Class columns_renderer_xhtml 209 * @author LarsDW223 210 */ 211class columns_renderer_xhtml extends columns_renderer { 212 /** 213 * 214 */ 215 public function render($state, Doku_Renderer $renderer, $attribute) { 216 parent::render($state, $renderer, $attribute); 217 218 if ($state == 987 && method_exists($renderer, 'finishSectionEdit')) { 219 $renderer->finishSectionEdit($attribute); 220 } 221 } 222 223 /** 224 * 225 */ 226 protected function render_enter(Doku_Renderer $renderer, $attribute) { 227 $renderer->doc .= $this->renderTable($attribute) . DOKU_LF; 228 $renderer->doc .= '<tr>' . $this->renderTd($attribute) . DOKU_LF; 229 } 230 231 /** 232 * 233 */ 234 protected function render_matched(Doku_Renderer $renderer, $attribute) { 235 $renderer->doc .= '</td>' . $this->renderTd($attribute) . DOKU_LF; 236 } 237 238 /** 239 * 240 */ 241 protected function render_exit(Doku_Renderer $renderer, $attribute) { 242 $renderer->doc .= '</td></tr></table>' . DOKU_LF; 243 } 244 245 /** 246 * 247 */ 248 private function renderTable($attribute) { 249 $width = $this->getAttribute($attribute, 'table-width'); 250 if ($width != '') { 251 return '<table class="columns-plugin" style="width:' . $width . '">'; 252 } 253 else { 254 return '<table class="columns-plugin">'; 255 } 256 } 257 258 /** 259 * 260 */ 261 private function renderTd($attribute) { 262 $class[] = 'columns-plugin'; 263 $class[] = $this->getAttribute($attribute, 'class'); 264 $class[] = $this->getAttribute($attribute, 'text-align'); 265 $html = '<td class="' . implode(' ', array_filter($class)) . '"'; 266 $style = $this->getStyle($attribute, 'column-width', 'width'); 267 $style .= $this->getStyle($attribute, 'vertical-align'); 268 if ($style != '') { 269 $html .= ' style="' . $style . '"'; 270 } 271 return $html . '>'; 272 } 273} 274 275/** 276 * Class columns_renderer_odt_v1 277 */ 278class columns_renderer_odt_v1 extends columns_renderer { 279 /** 280 * 281 */ 282 protected function render_enter(Doku_Renderer $renderer, $attribute) { 283 $this->addOdtTableStyle($renderer, $attribute); 284 $this->addOdtColumnStyles($renderer, $attribute); 285 $this->renderOdtTableEnter($renderer, $attribute); 286 $this->renderOdtColumnEnter($renderer, $attribute); 287 } 288 289 /** 290 * 291 */ 292 protected function render_matched(Doku_Renderer $renderer, $attribute) { 293 $this->addOdtColumnStyles($renderer, $attribute); 294 $this->renderOdtColumnExit($renderer); 295 $this->renderOdtColumnEnter($renderer, $attribute); 296 } 297 298 /** 299 * 300 */ 301 protected function render_exit(Doku_Renderer $renderer, $attribute) { 302 $this->renderOdtColumnExit($renderer); 303 $this->renderOdtTableExit($renderer); 304 } 305 306 /** 307 * 308 */ 309 private function addOdtTableStyle(Doku_Renderer $renderer, $attribute) { 310 $styleName = $this->getOdtTableStyleName($this->getAttribute($attribute, 'block-id')); 311 $style = '<style:style style:name="' . $styleName . '" style:family="table">'; 312 $style .= '<style:table-properties'; 313 $width = $this->getAttribute($attribute, 'table-width'); 314 315 if (($width != '') && ($width != '100%')) { 316 $metrics = $this->getOdtMetrics($renderer->autostyles); 317 $style .= ' style:width="' . $this->getOdtAbsoluteWidth($metrics, $width) . '"'; 318 } 319 $align = ($width == '100%') ? 'margins' : 'left'; 320 $style .= ' table:align="' . $align . '"/>'; 321 $style .= '</style:style>'; 322 323 $renderer->autostyles[$styleName] = $style; 324 } 325 326 /** 327 * 328 */ 329 private function addOdtColumnStyles(Doku_Renderer $renderer, $attribute) { 330 $blockId = $this->getAttribute($attribute, 'block-id'); 331 $columnId = $this->getAttribute($attribute, 'column-id'); 332 $styleName = $this->getOdtTableStyleName($blockId, $columnId); 333 334 $style = '<style:style style:name="' . $styleName . '" style:family="table-column">'; 335 $style .= '<style:table-column-properties'; 336 $width = $this->getAttribute($attribute, 'column-width'); 337 338 if ($width != '') { 339 $metrics = $this->getOdtMetrics($renderer->autostyles); 340 $style .= ' style:column-width="' . $this->getOdtAbsoluteWidth($metrics, $width) . '"'; 341 } 342 $style .= '/>'; 343 $style .= '</style:style>'; 344 345 $renderer->autostyles[$styleName] = $style; 346 347 $styleName = $this->getOdtTableStyleName($blockId, $columnId, 1); 348 349 $style = '<style:style style:name="' . $styleName . '" style:family="table-cell">'; 350 $style .= '<style:table-cell-properties'; 351 $style .= ' fo:border="none"'; 352 $style .= ' fo:padding-top="0cm"'; 353 $style .= ' fo:padding-bottom="0cm"'; 354 355 switch ($this->getAttribute($attribute, 'class')) { 356 case 'first': 357 $style .= ' fo:padding-left="0cm"'; 358 $style .= ' fo:padding-right="0.4cm"'; 359 break; 360 361 case 'last': 362 $style .= ' fo:padding-left="0.4cm"'; 363 $style .= ' fo:padding-right="0cm"'; 364 break; 365 } 366 367 /* There seems to be no easy way to control horizontal alignment of text within 368 the column as fo:text-align aplies to individual paragraphs. */ 369 //TODO: $this->getAttribute($attribute, 'text-align'); 370 371 $align = $this->getAttribute($attribute, 'vertical-align'); 372 if ($align != '') { 373 $style .= ' style:vertical-align="' . $align . '"'; 374 } 375 else { 376 $style .= ' style:vertical-align="top"'; 377 } 378 379 $style .= '/>'; 380 $style .= '</style:style>'; 381 382 $renderer->autostyles[$styleName] = $style; 383 } 384 385 /** 386 * 387 */ 388 private function renderOdtTableEnter(Doku_Renderer $renderer, $attribute) { 389 $columns = $this->getAttribute($attribute, 'columns'); 390 $blockId = $this->getAttribute($attribute, 'block-id'); 391 $styleName = $this->getOdtTableStyleName($blockId); 392 393 $renderer->doc .= '<table:table table:style-name="' . $styleName . '">'; 394 for ($c = 0; $c < $columns; $c++) { 395 $styleName = $this->getOdtTableStyleName($blockId, $c + 1); 396 $renderer->doc .= '<table:table-column table:style-name="' . $styleName . '" />'; 397 } 398 $renderer->doc .= '<table:table-row>'; 399 } 400 401 /** 402 * 403 */ 404 private function renderOdtColumnEnter(Doku_Renderer $renderer, $attribute) { 405 $blockId = $this->getAttribute($attribute, 'block-id'); 406 $columnId = $this->getAttribute($attribute, 'column-id'); 407 $styleName = $this->getOdtTableStyleName($blockId, $columnId, 1); 408 $renderer->doc .= '<table:table-cell table:style-name="' . $styleName . '" office:value-type="string">'; 409 } 410 411 /** 412 * 413 */ 414 private function renderOdtColumnExit(Doku_Renderer $renderer) { 415 $renderer->doc .= '</table:table-cell>'; 416 } 417 418 /** 419 * 420 */ 421 private function renderOdtTableExit(Doku_Renderer $renderer) { 422 $renderer->doc .= '</table:table-row>'; 423 $renderer->doc .= '</table:table>'; 424 } 425 426 /** 427 * Convert relative units to absolute 428 */ 429 private function getOdtAbsoluteWidth($metrics, $width) { 430 if (preg_match('/([\d\.]+)(.+)/', $width, $match) == 1) { 431 switch ($match[2]) { 432 case '%': 433 /* Won't work for nested column blocks */ 434 $width = ($match[1] / 100 * $metrics['page-width']) . $metrics['page-width-units']; 435 break; 436 case 'em': 437 /* Rough estimate */ 438 $width = ($match[1] * 0.8 * $metrics['font-size']) . $metrics['font-size-units']; 439 break; 440 } 441 } 442 return $width; 443 } 444 445 /** 446 * 447 */ 448 private function getOdtTableStyleName($blockId, $columnId = 0, $cell = 0) { 449 $result = 'ColumnsBlock' . $blockId; 450 if ($columnId != 0) { 451 if ($columnId <= 26) { 452 $result .= '.' . chr(ord('A') + $columnId - 1); 453 } 454 else { 455 /* To unlikey to handle it properly */ 456 $result .= '.a'; 457 } 458 if ($cell != 0) { 459 $result .= $cell; 460 } 461 } 462 return $result; 463 } 464 465 /** 466 * 467 */ 468 private function getOdtMetrics($autoStyle) { 469 $result = array(); 470 if (array_key_exists('pm1', $autoStyle)) { 471 $style = $autoStyle['pm1']; 472 if (preg_match('/fo:page-width="([\d\.]+)(.+?)"/', $style, $match) == 1) { 473 $result['page-width'] = floatval($match[1]); 474 $result['page-width-units'] = $match[2]; 475 $units = $match[2]; 476 477 if (preg_match('/fo:margin-left="([\d\.]+)(.+?)"/', $style, $match) == 1) { 478 // TODO: Unit conversion 479 if ($match[2] == $units) { 480 $result['page-width'] -= floatval($match[1]); 481 } 482 } 483 if (preg_match('/fo:margin-right="([\d\.]+)(.+?)"/', $style, $match) == 1) { 484 if ($match[2] == $units) { 485 $result['page-width'] -= floatval($match[1]); 486 } 487 } 488 } 489 } 490 if (!array_key_exists('page-width', $result)) { 491 $result['page-width'] = 17; 492 $result['page-width-units'] = 'cm'; 493 } 494 495 /* There seems to be no easy way to get default font size apart from loading styles.xml. */ 496 $styles = io_readFile(DOKU_PLUGIN . 'odt/styles.xml'); 497 if (preg_match('/<style:default-style style:family="paragraph">(.+?)<\/style:default-style>/s', $styles, $match) == 1) { 498 if (preg_match('/<style:text-properties(.+?)>/', $match[1], $match) == 1) { 499 if (preg_match('/fo:font-size="([\d\.]+)(.+?)"/', $match[1], $match) == 1) { 500 $result['font-size'] = floatval($match[1]); 501 $result['font-size-units'] = $match[2]; 502 } 503 } 504 } 505 if (!array_key_exists('font-size', $result)) { 506 $result['font-size'] = 12; 507 $result['font-size-units'] = 'pt'; 508 } 509 return $result; 510 } 511} 512 513/** 514 * Class columns_renderer_odt_v2 515 * @author LarsDW223 516 */ 517class columns_renderer_odt_v2 extends columns_renderer { 518 /** 519 * 520 */ 521 protected function render_enter(Doku_Renderer $renderer, $attribute) { 522 $this->renderOdtTableEnter($renderer, $attribute); 523 $this->renderOdtColumnEnter($renderer, $attribute); 524 } 525 526 /** 527 * 528 */ 529 protected function render_matched(Doku_Renderer $renderer, $attribute) { 530 $this->renderOdtColumnExit($renderer); 531 $this->renderOdtColumnEnter($renderer, $attribute); 532 } 533 534 /** 535 * 536 */ 537 protected function render_exit(Doku_Renderer $renderer, $attribute) { 538 $this->renderOdtColumnExit($renderer); 539 $this->renderOdtTableExit($renderer); 540 } 541 542 /** 543 * 544 */ 545 private function renderOdtTableEnter(Doku_Renderer $renderer, $attribute) { 546 $properties = array(); 547 $properties ['width'] = $this->getAttribute($attribute, 'table-width'); 548 $properties ['align'] = 'left'; 549 $renderer->_odtTableOpenUseProperties ($properties); 550 $renderer->tablerow_open(); 551 } 552 553 /** 554 * 555 */ 556 private function renderOdtColumnEnter(Doku_Renderer $renderer, $attribute) { 557 $properties = array(); 558 $properties ['width'] = $this->getAttribute($attribute, 'column-width'); 559 $properties ['border'] = 'none'; 560 $properties ['padding-top'] = '0cm'; 561 $properties ['padding-bottom'] = '0cm'; 562 switch ($this->getAttribute($attribute, 'class')) { 563 case 'first': 564 $properties ['padding-left'] = '0cm'; 565 $properties ['padding-right'] = '0.4cm'; 566 break; 567 568 case 'last': 569 $properties ['padding-left'] = '0.4cm'; 570 $properties ['padding-right'] = '0cm'; 571 break; 572 } 573 $align = $this->getAttribute($attribute, 'vertical-align'); 574 if ($align != '') { 575 $properties ['vertical-align'] = $align; 576 } 577 else { 578 $properties ['vertical-align'] = 'top'; 579 } 580 $align = $this->getAttribute($attribute, 'text-align'); 581 if ($align != '') { 582 $properties ['text-align'] = $align; 583 } 584 else { 585 $properties ['text-align'] = 'left'; 586 } 587 588 $renderer->_odtTableCellOpenUseProperties($properties); 589 } 590 591 /** 592 * 593 */ 594 private function renderOdtColumnExit(Doku_Renderer $renderer) { 595 $renderer->tablecell_close(); 596 } 597 598 /** 599 * 600 */ 601 private function renderOdtTableExit(Doku_Renderer $renderer) { 602 $renderer->tablerow_close(); 603 $renderer->table_close(); 604 } 605} 606