1<?php 2/** 3 * Renderer for WikiText output 4 * 5 * @author Adrian Lang <lang@cosmocode.de> 6 */ 7 8// must be run within Dokuwiki 9if(!defined('DOKU_INC')) die(); 10 11require_once DOKU_INC.'inc/parser/renderer.php'; 12 13class renderer_plugin_edittable_inverse extends Doku_Renderer { 14 /** @var string will contain the whole document */ 15 public $doc = ''; 16 17 // bunch of internal state variables 18 private $prepend_not_block = ''; 19 private $_key = 0; 20 private $_pos = 0; 21 private $_ownspan = 0; 22 private $previous_block = false; 23 private $_row = 0; 24 private $_rowspans = array(); 25 private $_table = array(); 26 private $_liststack = array(); 27 private $quotelvl = 0; 28 private $extlinkparser = null; 29 protected $extlinkPatterns = []; 30 31 function getFormat() { 32 return 'wiki'; 33 } 34 35 function document_start() { 36 } 37 38 function document_end() { 39 $this->block(); 40 $this->doc = rtrim($this->doc); 41 } 42 43 function header($text, $level, $pos) { 44 $this->block(); 45 if(!$text) return; //skip empty headlines 46 47 // write the header 48 $markup = str_repeat('=', 7 - $level); 49 $this->doc .= "$markup $text $markup".DOKU_LF; 50 } 51 52 function section_open($level) { 53 $this->block(); 54# $this->doc .= DOKU_LF; 55 } 56 57 function section_close() { 58 $this->block(); 59 $this->doc .= DOKU_LF; 60 } 61 62 // FIXME this did something compllicated with surrounding whitespaces. Why? 63 function cdata($text) { 64 if(strlen($text) === 0) { 65 $this->not_block(); 66 return; 67 } 68 69// if(!$this->previous_block && trim(substr($text, 0, 1)) === '' && trim($text) !== '') { 70// $this->doc .= ' '; 71// } 72 $this->not_block(); 73 74// if(trim(substr($text, -1, 1)) === '' && trim($text) !== '') { 75// $this->prepend_not_block = ' '; 76// } 77// $this->doc .= trim($text); 78 79 $this->doc .= $text; 80 } 81 82 function p_close() { 83 $this->block(); 84 if($this->quotelvl === 0) { 85 $this->doc = rtrim($this->doc, DOKU_LF).DOKU_LF.DOKU_LF; 86 } 87 } 88 89 function p_open() { 90 $this->block(); 91 if(strlen($this->doc) > 0 && substr($this->doc, 1, -1) !== DOKU_LF) { 92 $this->doc .= DOKU_LF.DOKU_LF; 93 } 94 $this->doc .= str_repeat('>', $this->quotelvl); 95 } 96 97 function linebreak() { 98 $this->not_block(); 99 $this->doc .= '\\\\ '; 100 } 101 102 function hr() { 103 $this->block(); 104 $this->doc .= '----'; 105 } 106 107 function block() { 108 if(isset($this->prepend_not_block)) { 109 unset($this->prepend_not_block); 110 } 111 $this->previous_block = true; 112 } 113 114 function not_block() { 115 if(isset($this->prepend_not_block)) { 116 $this->doc .= $this->prepend_not_block; 117 unset($this->prepend_not_block); 118 } 119 $this->previous_block = false; 120 } 121 122 function strong_open() { 123 $this->not_block(); 124 $this->doc .= '**'; 125 } 126 127 function strong_close() { 128 $this->not_block(); 129 $this->doc .= '**'; 130 } 131 132 function emphasis_open() { 133 $this->not_block(); 134 $this->doc .= '//'; 135 } 136 137 function emphasis_close() { 138 $this->not_block(); 139 $this->doc .= '//'; 140 } 141 142 function underline_open() { 143 $this->not_block(); 144 $this->doc .= '__'; 145 } 146 147 function underline_close() { 148 $this->not_block(); 149 $this->doc .= '__'; 150 } 151 152 function monospace_open() { 153 $this->not_block(); 154 $this->doc .= "''"; 155 } 156 157 function monospace_close() { 158 $this->not_block(); 159 $this->doc .= "''"; 160 } 161 162 function subscript_open() { 163 $this->not_block(); 164 $this->doc .= '<sub>'; 165 } 166 167 function subscript_close() { 168 $this->not_block(); 169 $this->doc .= '</sub>'; 170 } 171 172 function superscript_open() { 173 $this->not_block(); 174 $this->doc .= '<sup>'; 175 } 176 177 function superscript_close() { 178 $this->not_block(); 179 $this->doc .= '</sup>'; 180 } 181 182 function deleted_open() { 183 $this->not_block(); 184 $this->doc .= '<del>'; 185 } 186 187 function deleted_close() { 188 $this->not_block(); 189 $this->doc .= '</del>'; 190 } 191 192 function footnote_open() { 193 $this->not_block(); 194 $this->doc .= '(('; 195 } 196 197 function footnote_close() { 198 $this->not_block(); 199 $this->doc .= '))'; 200 } 201 202 function listu_open() { 203 $this->block(); 204 if(!isset($this->_liststack)) { 205 $this->_liststack = array(); 206 } 207 if(count($this->_liststack) === 0) { 208 $this->doc .= DOKU_LF; 209 } 210 $this->_liststack[] = '*'; 211 } 212 213 function listu_close() { 214 $this->block(); 215 array_pop($this->_liststack); 216 if(count($this->_liststack) === 0) { 217 $this->doc .= DOKU_LF; 218 } 219 } 220 221 function listo_open() { 222 $this->block(); 223 if(!isset($this->_liststack)) { 224 $this->_liststack = array(); 225 } 226 if(count($this->_liststack) === 0) { 227 $this->doc .= DOKU_LF; 228 } 229 $this->_liststack[] = '-'; 230 } 231 232 function listo_close() { 233 $this->block(); 234 array_pop($this->_liststack); 235 if(count($this->_liststack) === 0) { 236 $this->doc .= DOKU_LF; 237 } 238 } 239 240 function listitem_open($level, $node = false) { 241 $this->block(); 242 $this->doc .= str_repeat(' ', $level * 2).end($this->_liststack).' '; 243 } 244 245 function listcontent_close() { 246 $this->block(); 247 $this->doc .= DOKU_LF; 248 } 249 250 function unformatted($text) { 251 $this->not_block(); 252 if(strpos($text, '%%') !== false) { 253 $this->doc .= "<nowiki>$text</nowiki>"; 254 } elseif($text[0] == "\n") { 255 $this->doc .= "<nowiki>$text</nowiki>"; 256 } else { 257 $this->doc .= "%%$text%%"; 258 } 259 } 260 261 function php($text, $wrapper = 'code') { 262 $this->not_block(); 263 $this->doc .= "<php>$text</php>"; 264 } 265 266 function phpblock($text) { 267 $this->block(); 268 $this->doc .= "<PHP>$text</PHP>"; 269 } 270 271 function html($text, $wrapper = 'code') { 272 $this->not_block(); 273 $this->doc .= "<html>$text</html>"; 274 } 275 276 function htmlblock($text) { 277 $this->block(); 278 $this->doc .= "<HTML>$text</HTML>"; 279 } 280 281 function quote_open() { 282 $this->block(); 283 if(substr($this->doc, -(++$this->quotelvl)) === DOKU_LF.str_repeat('>', $this->quotelvl - 1)) { 284 $this->doc .= '>'; 285 } else { 286 $this->doc .= DOKU_LF.str_repeat('>', $this->quotelvl); 287 } 288 $this->prepend_not_block = ' '; 289 } 290 291 function quote_close() { 292 $this->block(); 293 $this->quotelvl--; 294 if(strrpos($this->doc, DOKU_LF) === strlen($this->doc) - 1) { 295 return; 296 } 297 $this->doc .= DOKU_LF.DOKU_LF; 298 } 299 300 function preformatted($text) { 301 $this->block(); 302 $this->doc .= preg_replace('/^/m', ' ', $text).DOKU_LF; 303 } 304 305 function file($text, $language = null, $filename = null) { 306 $this->_highlight('file', $text, $language, $filename); 307 } 308 309 function code($text, $language = null, $filename = null) { 310 $this->_highlight('code', $text, $language, $filename); 311 } 312 313 function _highlight($type, $text, $language = null, $filename = null) { 314 if( $this->previous_block ) $this->doc .= "\n"; 315 316 $this->block(); 317 $this->doc .= "<$type"; 318 if($language != null) { 319 $this->doc .= " $language"; 320 } 321 if($filename != null) { 322 $this->doc .= " $filename"; 323 } 324 $this->doc .= ">"; 325 $this->doc .= $text; 326 if($text[0] == "\n") $this->doc .= "\n"; 327 $this->doc .= "</$type>"; 328 } 329 330 function acronym($acronym) { 331 $this->not_block(); 332 $this->doc .= $acronym; 333 } 334 335 function smiley($smiley) { 336 $this->not_block(); 337 $this->doc .= $smiley; 338 } 339 340 function entity($entity) { 341 $this->not_block(); 342 $this->doc .= $entity; 343 } 344 345 function multiplyentity($x, $y) { 346 $this->not_block(); 347 $this->doc .= "{$x}x{$y}"; 348 } 349 350 function singlequoteopening() { 351 $this->not_block(); 352 $this->doc .= "'"; 353 } 354 355 function singlequoteclosing() { 356 $this->not_block(); 357 $this->doc .= "'"; 358 } 359 360 function apostrophe() { 361 $this->not_block(); 362 $this->doc .= "'"; 363 } 364 365 function doublequoteopening() { 366 $this->not_block(); 367 $this->doc .= '"'; 368 } 369 370 function doublequoteclosing() { 371 $this->not_block(); 372 $this->doc .= '"'; 373 } 374 375 /** 376 */ 377 function camelcaselink($link) { 378 $this->not_block(); 379 $this->doc .= $link; 380 } 381 382 function locallink($hash, $name = null) { 383 $this->not_block(); 384 $this->doc .= "[[#$hash"; 385 if($name !== null) { 386 $this->doc .= '|'; 387 $this->_echoLinkTitle($name); 388 } 389 $this->doc .= ']]'; 390 } 391 392 function internallink($id, $name = null, $search = null, $returnonly = false, $linktype = 'content') { 393 $this->not_block(); 394 $this->doc .= "[[$id"; 395 if($name !== null) { 396 $this->doc .= '|'; 397 $this->_echoLinkTitle($name); 398 } 399 $this->doc .= ']]'; 400 } 401 402 /** 403 * Handle external Links 404 * 405 * @author Andreas Gohr <andi@splitbrain.org> 406 * @param $url 407 * @param null $name 408 */ 409 function externallink($url, $name = null) { 410 $this->not_block(); 411 412 /* 413 * When $name is null it might have been a match of an URL that was in the text without 414 * any link syntax. These are recognized by a bunch of patterns in Doku_Parser_Mode_externallink. 415 * We simply reuse these patterns here. However, since we don't parse the pattern through the Lexer, 416 * no escaping is done on the patterns - this means we need a non-conflicting delimiter. I decided for 417 * a single tick >>'<< which seems to work. Since the patterns contain wordboundaries they are matched 418 * against the URL surrounded by spaces. 419 */ 420 if($name === null) { 421 // get the patterns from the parser if available, otherwise use a duplicate 422 if(is_null($this->extlinkparser)) { 423 if ( 424 class_exists('\dokuwiki\Parsing\ParserMode\Externallink') && 425 method_exists('\dokuwiki\Parsing\ParserMode\Externallink', 'getPatterns') 426 ) { 427 $this->extlinkparser = new \dokuwiki\Parsing\ParserMode\Externallink(); 428 $this->extlinkparser->preConnect(); 429 $this->extlinkPatterns = $this->extlinkparser->getPatterns(); 430 } else { 431 $ltrs = '\w'; 432 $gunk = '/\#~:.?+=&%@!\-\[\]'; 433 $punc = '.:?\-;,'; 434 $host = $ltrs . $punc; 435 $any = $ltrs . $gunk . $punc; 436 437 $schemes = getSchemes(); 438 foreach ($schemes as $scheme) { 439 $this->extlinkPatterns[] = '\b(?i)'.$scheme.'(?-i)://['.$any.']+?(?=['.$punc.']*[^'.$any.'])'; 440 } 441 442 $this->extlinkPatterns[] = '(?<=\s)(?i)www?(?-i)\.['.$host.']+?\.['.$host.']+?['.$any.']+?(?=['.$punc.']*[^'.$any.'])'; 443 $this->extlinkPatterns[] = '(?<=\s)(?i)ftp?(?-i)\.['.$host.']+?\.['.$host.']+?['.$any.']+?(?=['.$punc.']*[^'.$any.'])'; 444 } 445 } 446 447 // check if URL matches pattern 448 foreach($this->extlinkPatterns as $pattern) { 449 if(preg_match("'$pattern'", " $url ")) { 450 $this->doc .= $url; // gotcha! 451 return; 452 } 453 } 454 } 455 456 // still here? 457 if($url === "http://$name" || $url === "ftp://$name") { 458 // special case - www.* or ftp.* matching 459 $this->doc .= $name; 460 } else { 461 // link syntax! definitively link syntax 462 $this->doc .= "[[$url"; 463 if(!is_null($name)) { 464 // we do have a name! 465 $this->doc .= '|'; 466 $this->_echoLinkTitle($name); 467 } 468 $this->doc .= ']]'; 469 } 470 } 471 472 function interwikilink($match, $name = null, $wikiName, $wikiUri) { 473 $this->not_block(); 474 $this->doc .= "[[$wikiName>$wikiUri"; 475 if($name !== null) { 476 $this->doc .= '|'; 477 $this->_echoLinkTitle($name); 478 } 479 $this->doc .= ']]'; 480 } 481 482 function windowssharelink($url, $name = null) { 483 $this->not_block(); 484 $this->doc .= "[[$url"; 485 if($name !== null) { 486 $this->doc .= '|'; 487 $this->_echoLinkTitle($name); 488 } 489 $this->doc .= "]]"; 490 } 491 492 function emaillink($address, $name = null) { 493 $this->not_block(); 494 if($name === null) { 495 $this->doc .= "<$address>"; 496 } else { 497 $this->doc .= "[[$address|"; 498 $this->_echoLinkTitle($name); 499 $this->doc .= ']]'; 500 } 501 } 502 503 function internalmedia($src, $title = null, $align = null, $width = null, 504 $height = null, $cache = null, $linking = null) { 505 $this->not_block(); 506 $this->doc .= '{{'; 507 if($align === 'center' || $align === 'right') { 508 $this->doc .= ' '; 509 } 510 $this->doc .= $src; 511 512 $params = array(); 513 if($width !== null) { 514 $params[0] = $width; 515 if($height !== null) { 516 $params[0] .= "x$height"; 517 } 518 } 519 if($cache !== 'cache') { 520 $params[] = $cache; 521 } 522 if($linking !== 'details') { 523 $params[] = $linking; 524 } 525 if(count($params) > 0) { 526 $this->doc .= '?'; 527 } 528 $this->doc .= join('&', $params); 529 530 if($align === 'center' || $align === 'left') { 531 $this->doc .= ' '; 532 } 533 if($title != null) { 534 $this->doc .= "|$title"; 535 } 536 $this->doc .= '}}'; 537 } 538 539 function externalmedia($src, $title = null, $align = null, $width = null, 540 $height = null, $cache = null, $linking = null) { 541 $this->internalmedia($src, $title, $align, $width, $height, $cache, $linking); 542 } 543 544 /** 545 * Renders an RSS feed 546 * 547 * @author Andreas Gohr <andi@splitbrain.org> 548 */ 549 function rss($url, $params) { 550 $this->block(); 551 $this->doc .= '{{rss>'.$url; 552 $vals = array(); 553 if($params['max'] !== 8) { 554 $vals[] = $params['max']; 555 } 556 if($params['reverse']) { 557 $vals[] = 'reverse'; 558 } 559 if($params['author']) { 560 $vals[] = 'author'; 561 } 562 if($params['date']) { 563 $vals[] = 'date'; 564 } 565 if($params['details']) { 566 $vals[] = 'desc'; 567 } 568 if($params['refresh'] !== 14400) { 569 $val = '10m'; 570 foreach(array('d' => 86400, 'h' => 3600, 'm' => 60) as $p => $div) { 571 $res = $params['refresh'] / $div; 572 if($res === intval($res)) { 573 $val = "$res$p"; 574 break; 575 } 576 } 577 $vals[] = $val; 578 } 579 if(count($vals) > 0) { 580 $this->doc .= ' '.join(' ', $vals); 581 } 582 $this->doc .= '}}'; 583 } 584 585 function table_open($maxcols = null, $numrows = null, $pos = null) { 586 $this->block(); 587 $this->_table = array(); 588 $this->_row = 0; 589 $this->_rowspans = array(); 590 } 591 592 function table_close($pos = null) { 593 $this->doc .= $this->_table_to_wikitext($this->_table); 594 } 595 596 function tablerow_open() { 597 $this->block(); 598 $this->_table[++$this->_row] = array(); 599 $this->_key = 1; 600 while(isset($this->_rowspans[$this->_key])) { 601 --$this->_rowspans[$this->_key]; 602 if($this->_rowspans[$this->_key] === 1) { 603 unset($this->_rowspans[$this->_key]); 604 } 605 ++$this->_key; 606 } 607 } 608 609 function tablerow_close() { 610 $this->block(); 611 } 612 613 function tableheader_open($colspan = 1, $align = null, $rowspan = 1) { 614 $this->_cellopen('th', $colspan, $align, $rowspan); 615 } 616 617 function _cellopen($tag, $colspan, $align, $rowspan) { 618 $this->block(); 619 $this->_table[$this->_row][$this->_key] = compact('tag', 'colspan', 'align', 'rowspan'); 620 if($rowspan > 1) { 621 $this->_rowspans[$this->_key] = $rowspan; 622 $this->_ownspan = true; 623 } 624 $this->_pos = strlen($this->doc); 625 } 626 627 function tableheader_close() { 628 $this->_cellclose(); 629 } 630 631 function _cellclose() { 632 $this->block(); 633 $this->_table[$this->_row][$this->_key]['text'] = trim(substr($this->doc, $this->_pos)); 634 $this->doc = substr($this->doc, 0, $this->_pos); 635 $this->_key += $this->_table[$this->_row][$this->_key]['colspan']; 636 while(isset($this->_rowspans[$this->_key]) && !$this->_ownspan) { 637 --$this->_rowspans[$this->_key]; 638 if($this->_rowspans[$this->_key] === 1) { 639 unset($this->_rowspans[$this->_key]); 640 } 641 ++$this->_key; 642 } 643 $this->_ownspan = false; 644 } 645 646 function tablecell_open($colspan = 1, $align = null, $rowspan = 1) { 647 $this->_cellopen('td', $colspan, $align, $rowspan); 648 } 649 650 function tablecell_close() { 651 $this->_cellclose(); 652 } 653 654 function plugin($name, $args, $state = '', $match = '') { 655 $this->not_block(); 656 // This will break for plugins which provide a catch-all render method 657 // like the do or pagenavi plugins 658# $plugin =& plugin_load('syntax',$name); 659# if($plugin === null || !$plugin->render($this->getFormat(),$this,$args)) { 660 $this->doc .= $match; 661# } 662 } 663 664 function _echoLinkTitle($title) { 665 if(is_array($title)) { 666 $this->internalmedia( 667 $title['src'], 668 $title['title'], 669 $title['align'], 670 $title['width'], 671 $title['height'], 672 $title['cache'], 673 $title['linking'] 674 ); 675 } else { 676 $this->doc .= $title; 677 } 678 } 679 680 /** 681 * Helper for table to wikitext conversion 682 * 683 * @author Adrian Lang <lang@cosmocode.de> 684 * @param array $_table 685 * @return string 686 */ 687 private function _table_to_wikitext($_table) { 688 // Preprocess table for rowspan, make table 0-based. 689 $table = array(); 690 $keys = array_keys($_table); 691 $start = array_pop($keys); 692 foreach($_table as $i => $row) { 693 $inorm = $i - $start; 694 if(!isset($table[$inorm])) $table[$inorm] = array(); 695 $nextkey = 0; 696 foreach($row as $cell) { 697 while(isset($table[$inorm][$nextkey])) { 698 $nextkey++; 699 } 700 $nextkey += $cell['colspan'] - 1; 701 $table[$inorm][$nextkey] = $cell; 702 $rowspan = $cell['rowspan']; 703 $i2 = $inorm + 1; 704 while($rowspan-- > 1) { 705 if(!isset($table[$i2])) $table[$i2] = array(); 706 $nu_cell = $cell; 707 $nu_cell['text'] = ':::'; 708 $nu_cell['rowspan'] = 1; 709 $table[$i2++][$nextkey] = $nu_cell; 710 } 711 } 712 ksort($table[$inorm]); 713 } 714 715 // Get the max width for every column to do table prettyprinting. 716 $m_width = array(); 717 foreach($table as $row) { 718 foreach($row as $n => $cell) { 719 // Calculate cell width. 720 $diff = (utf8_strlen($cell['text']) + $cell['colspan'] + 721 ($cell['align'] === 'center' ? 3 : 2)); 722 723 // Calculate current max width. 724 $span = $cell['colspan']; 725 while(--$span >= 0) { 726 if(isset($m_width[$n - $span])) { 727 $diff -= $m_width[$n - $span]; 728 } 729 } 730 731 if($diff > 0) { 732 // Just add the difference to all cols. 733 while(++$span < $cell['colspan']) { 734 $m_width[$n - $span] = (isset($m_width[$n - $span]) ? $m_width[$n - $span] : 0) + ceil($diff / $cell['colspan']); 735 } 736 } 737 } 738 } 739 740 // Write the table. 741 $types = array('th' => '^', 'td' => '|'); 742 $str = ''; 743 foreach($table as $row) { 744 $pos = 0; 745 foreach($row as $n => $cell) { 746 $pos += utf8_strlen($cell['text']) + 1; 747 $span = $cell['colspan']; 748 $target = 0; 749 while(--$span >= 0) { 750 if(isset($m_width[$n - $span])) { 751 $target += $m_width[$n - $span]; 752 } 753 } 754 $pad = $target - utf8_strlen($cell['text']); 755 $pos += $pad + ($cell['colspan'] - 1); 756 switch($cell['align']) { 757 case 'right': 758 $lpad = $pad - 1; 759 break; 760 case 'left': 761 case '': 762 $lpad = 1; 763 break; 764 case 'center': 765 $lpad = floor($pad / 2); 766 break; 767 } 768 $str .= $types[$cell['tag']].str_repeat(' ', $lpad). 769 $cell['text'].str_repeat(' ', $pad - $lpad). 770 str_repeat($types[$cell['tag']], $cell['colspan'] - 1); 771 } 772 $str .= $types[$cell['tag']].DOKU_LF; 773 } 774 return $str; 775 } 776} 777 778//Setup VIM: ex: et ts=4 enc=utf-8 : 779