1<?php 2 3/** 4 * Renderer output base class 5 * 6 * @author Harry Fuecks <hfuecks@gmail.com> 7 * @author Andreas Gohr <andi@splitbrain.org> 8 */ 9 10use dokuwiki\Extension\Plugin; 11use dokuwiki\Extension\SyntaxPlugin; 12 13/** 14 * Allowed chars in $language for code highlighting 15 * @see GeSHi::set_language() 16 */ 17define('PREG_PATTERN_VALID_LANGUAGE', '#[^a-zA-Z0-9\-_]#'); 18 19/** 20 * An empty renderer, produces no output 21 * 22 * Inherits from dokuwiki\Extension\Plugin for giving additional functions to render plugins 23 * 24 * The renderer transforms the syntax instructions created by the parser and handler into the 25 * desired output format. For each instruction a corresponding method defined in this class will 26 * be called. That method needs to produce the desired output for the instruction and add it to the 27 * $doc field. When all instructions are processed, the $doc field contents will be cached by 28 * DokuWiki and sent to the user. 29 */ 30abstract class Doku_Renderer extends Plugin 31{ 32 /** @var array Settings, control the behavior of the renderer */ 33 public $info = [ 34 'cache' => true, // may the rendered result cached? 35 'toc' => true, // render the TOC? 36 ]; 37 38 /** @var array contains the smiley configuration, set in p_render() */ 39 public $smileys = []; 40 /** @var array contains the entity configuration, set in p_render() */ 41 public $entities = []; 42 /** @var array contains the acronym configuration, set in p_render() */ 43 public $acronyms = []; 44 /** @var array contains the interwiki configuration, set in p_render() */ 45 public $interwiki = []; 46 /** @var string|int link pages and media against this revision */ 47 public $date_at = ''; 48 49 /** @var array the list of headers used to create unique link ids */ 50 protected $headers = []; 51 52 /** 53 * @var string the rendered document, this will be cached after the renderer ran through 54 */ 55 public $doc = ''; 56 57 /** 58 * clean out any per-use values 59 * 60 * This is called before each use of the renderer object and should be used to 61 * completely reset the state of the renderer to be reused for a new document 62 */ 63 public function reset() 64 { 65 $this->headers = []; 66 $this->doc = ''; 67 $this->info['cache'] = true; 68 $this->info['toc'] = true; 69 } 70 71 /** 72 * Allow the plugin to prevent DokuWiki from reusing an instance 73 * 74 * Since most renderer plugins fail to implement Doku_Renderer::reset() we default 75 * to reinstantiating the renderer here 76 * 77 * @return bool false if the plugin has to be instantiated 78 */ 79 public function isSingleton() 80 { 81 return false; 82 } 83 84 /** 85 * Returns the format produced by this renderer. 86 * 87 * Has to be overidden by sub classes 88 * 89 * @return string 90 */ 91 abstract public function getFormat(); 92 93 /** 94 * Disable caching of this renderer's output 95 */ 96 public function nocache() 97 { 98 $this->info['cache'] = false; 99 } 100 101 /** 102 * Disable TOC generation for this renderer's output 103 * 104 * This might not be used for certain sub renderer 105 */ 106 public function notoc() 107 { 108 $this->info['toc'] = false; 109 } 110 111 /** 112 * Handle plugin rendering 113 * 114 * Most likely this needs NOT to be overwritten by sub classes 115 * 116 * @param string $name Plugin name 117 * @param mixed $data custom data set by handler 118 * @param string $state matched state if any 119 * @param string $match raw matched syntax 120 */ 121 public function plugin($name, $data, $state = '', $match = '') 122 { 123 /** @var SyntaxPlugin $plugin */ 124 $plugin = plugin_load('syntax', $name); 125 if ($plugin != null) { 126 $plugin->render($this->getFormat(), $this, $data); 127 } 128 } 129 130 /** 131 * handle nested render instructions 132 * this method (and nest_close method) should not be overloaded in actual renderer output classes 133 * 134 * @param array $instructions 135 */ 136 public function nest($instructions) 137 { 138 foreach ($instructions as $instruction) { 139 // execute the callback against ourself 140 if (method_exists($this, $instruction[0])) { 141 call_user_func_array([$this, $instruction[0]], $instruction[1] ?: []); 142 } 143 } 144 } 145 146 /** 147 * dummy closing instruction issued by Doku_Handler_Nest 148 * 149 * normally the syntax mode should override this instruction when instantiating Doku_Handler_Nest - 150 * however plugins will not be able to - as their instructions require data. 151 */ 152 public function nest_close() 153 { 154 } 155 156 #region Syntax modes - sub classes will need to implement them to fill $doc 157 158 /** 159 * Initialize the document 160 */ 161 public function document_start() 162 { 163 } 164 165 /** 166 * Finalize the document 167 */ 168 public function document_end() 169 { 170 } 171 172 /** 173 * Render the Table of Contents 174 * 175 * @return string 176 */ 177 public function render_TOC() 178 { 179 return ''; 180 } 181 182 /** 183 * Add an item to the TOC 184 * 185 * @param string $id the hash link 186 * @param string $text the text to display 187 * @param int $level the nesting level 188 */ 189 public function toc_additem($id, $text, $level) 190 { 191 } 192 193 /** 194 * Render a heading 195 * 196 * @param string $text the text to display 197 * @param int $level header level 198 * @param int $pos byte position in the original source 199 */ 200 public function header($text, $level, $pos) 201 { 202 } 203 204 /** 205 * Open a new section 206 * 207 * @param int $level section level (as determined by the previous header) 208 */ 209 public function section_open($level) 210 { 211 } 212 213 /** 214 * Close the current section 215 */ 216 public function section_close() 217 { 218 } 219 220 /** 221 * Render plain text data 222 * 223 * @param string $text 224 */ 225 public function cdata($text) 226 { 227 } 228 229 /** 230 * Open a paragraph 231 */ 232 public function p_open() 233 { 234 } 235 236 /** 237 * Close a paragraph 238 */ 239 public function p_close() 240 { 241 } 242 243 /** 244 * Create a line break 245 */ 246 public function linebreak() 247 { 248 } 249 250 /** 251 * Create a horizontal line 252 */ 253 public function hr() 254 { 255 } 256 257 /** 258 * Start strong (bold) formatting 259 */ 260 public function strong_open() 261 { 262 } 263 264 /** 265 * Stop strong (bold) formatting 266 */ 267 public function strong_close() 268 { 269 } 270 271 /** 272 * Start emphasis (italics) formatting 273 */ 274 public function emphasis_open() 275 { 276 } 277 278 /** 279 * Stop emphasis (italics) formatting 280 */ 281 public function emphasis_close() 282 { 283 } 284 285 /** 286 * Start underline formatting 287 */ 288 public function underline_open() 289 { 290 } 291 292 /** 293 * Stop underline formatting 294 */ 295 public function underline_close() 296 { 297 } 298 299 /** 300 * Start monospace formatting 301 */ 302 public function monospace_open() 303 { 304 } 305 306 /** 307 * Stop monospace formatting 308 */ 309 public function monospace_close() 310 { 311 } 312 313 /** 314 * Start a subscript 315 */ 316 public function subscript_open() 317 { 318 } 319 320 /** 321 * Stop a subscript 322 */ 323 public function subscript_close() 324 { 325 } 326 327 /** 328 * Start a superscript 329 */ 330 public function superscript_open() 331 { 332 } 333 334 /** 335 * Stop a superscript 336 */ 337 public function superscript_close() 338 { 339 } 340 341 /** 342 * Start deleted (strike-through) formatting 343 */ 344 public function deleted_open() 345 { 346 } 347 348 /** 349 * Stop deleted (strike-through) formatting 350 */ 351 public function deleted_close() 352 { 353 } 354 355 /** 356 * Start a footnote 357 */ 358 public function footnote_open() 359 { 360 } 361 362 /** 363 * Stop a footnote 364 */ 365 public function footnote_close() 366 { 367 } 368 369 /** 370 * Open an unordered list 371 */ 372 public function listu_open() 373 { 374 } 375 376 /** 377 * Close an unordered list 378 */ 379 public function listu_close() 380 { 381 } 382 383 /** 384 * Open an ordered list 385 */ 386 public function listo_open() 387 { 388 } 389 390 /** 391 * Close an ordered list 392 */ 393 public function listo_close() 394 { 395 } 396 397 /** 398 * Open a list item 399 * 400 * @param int $level the nesting level 401 * @param bool $node true when a node; false when a leaf 402 */ 403 public function listitem_open($level, $node = false) 404 { 405 } 406 407 /** 408 * Close a list item 409 */ 410 public function listitem_close() 411 { 412 } 413 414 /** 415 * Start the content of a list item 416 */ 417 public function listcontent_open() 418 { 419 } 420 421 /** 422 * Stop the content of a list item 423 */ 424 public function listcontent_close() 425 { 426 } 427 428 /** 429 * Output unformatted $text 430 * 431 * Defaults to $this->cdata() 432 * 433 * @param string $text 434 */ 435 public function unformatted($text) 436 { 437 $this->cdata($text); 438 } 439 440 /** 441 * Output preformatted text 442 * 443 * @param string $text 444 */ 445 public function preformatted($text) 446 { 447 } 448 449 /** 450 * Start a block quote 451 */ 452 public function quote_open() 453 { 454 } 455 456 /** 457 * Stop a block quote 458 */ 459 public function quote_close() 460 { 461 } 462 463 /** 464 * Display text as file content, optionally syntax highlighted 465 * 466 * @param string $text text to show 467 * @param string $lang programming language to use for syntax highlighting 468 * @param string $file file path label 469 */ 470 public function file($text, $lang = null, $file = null) 471 { 472 } 473 474 /** 475 * Display text as code content, optionally syntax highlighted 476 * 477 * @param string $text text to show 478 * @param string $lang programming language to use for syntax highlighting 479 * @param string $file file path label 480 */ 481 public function code($text, $lang = null, $file = null) 482 { 483 } 484 485 /** 486 * Format an acronym 487 * 488 * Uses $this->acronyms 489 * 490 * @param string $acronym 491 */ 492 public function acronym($acronym) 493 { 494 } 495 496 /** 497 * Format a smiley 498 * 499 * Uses $this->smiley 500 * 501 * @param string $smiley 502 */ 503 public function smiley($smiley) 504 { 505 } 506 507 /** 508 * Format an entity 509 * 510 * Entities are basically small text replacements 511 * 512 * Uses $this->entities 513 * 514 * @param string $entity 515 */ 516 public function entity($entity) 517 { 518 } 519 520 /** 521 * Typographically format a multiply sign 522 * 523 * Example: ($x=640, $y=480) should result in "640×480" 524 * 525 * @param string|int $x first value 526 * @param string|int $y second value 527 */ 528 public function multiplyentity($x, $y) 529 { 530 } 531 532 /** 533 * Render an opening single quote char (language specific) 534 */ 535 public function singlequoteopening() 536 { 537 } 538 539 /** 540 * Render a closing single quote char (language specific) 541 */ 542 public function singlequoteclosing() 543 { 544 } 545 546 /** 547 * Render an apostrophe char (language specific) 548 */ 549 public function apostrophe() 550 { 551 } 552 553 /** 554 * Render an opening double quote char (language specific) 555 */ 556 public function doublequoteopening() 557 { 558 } 559 560 /** 561 * Render an closinging double quote char (language specific) 562 */ 563 public function doublequoteclosing() 564 { 565 } 566 567 /** 568 * Render a CamelCase link 569 * 570 * @param string $link The link name 571 * @see http://en.wikipedia.org/wiki/CamelCase 572 */ 573 public function camelcaselink($link) 574 { 575 } 576 577 /** 578 * Render a page local link 579 * 580 * @param string $hash hash link identifier 581 * @param string $name name for the link 582 */ 583 public function locallink($hash, $name = null) 584 { 585 } 586 587 /** 588 * Render a wiki internal link 589 * 590 * @param string $link page ID to link to. eg. 'wiki:syntax' 591 * @param string|array $title name for the link, array for media file 592 */ 593 public function internallink($link, $title = null) 594 { 595 } 596 597 /** 598 * Render an external link 599 * 600 * @param string $link full URL with scheme 601 * @param string|array $title name for the link, array for media file 602 */ 603 public function externallink($link, $title = null) 604 { 605 } 606 607 /** 608 * Render the output of an RSS feed 609 * 610 * @param string $url URL of the feed 611 * @param array $params Finetuning of the output 612 */ 613 public function rss($url, $params) 614 { 615 } 616 617 /** 618 * Render an interwiki link 619 * 620 * You may want to use $this->_resolveInterWiki() here 621 * 622 * @param string $link original link - probably not much use 623 * @param string|array $title name for the link, array for media file 624 * @param string $wikiName indentifier (shortcut) for the remote wiki 625 * @param string $wikiUri the fragment parsed from the original link 626 */ 627 public function interwikilink($link, $title, $wikiName, $wikiUri) 628 { 629 } 630 631 /** 632 * Link to file on users OS 633 * 634 * @param string $link the link 635 * @param string|array $title name for the link, array for media file 636 */ 637 public function filelink($link, $title = null) 638 { 639 } 640 641 /** 642 * Link to windows share 643 * 644 * @param string $link the link 645 * @param string|array $title name for the link, array for media file 646 */ 647 public function windowssharelink($link, $title = null) 648 { 649 } 650 651 /** 652 * Render a linked E-Mail Address 653 * 654 * Should honor $conf['mailguard'] setting 655 * 656 * @param string $address Email-Address 657 * @param string|array $name name for the link, array for media file 658 */ 659 public function emaillink($address, $name = null) 660 { 661 } 662 663 /** 664 * Render an internal media file 665 * 666 * @param string $src media ID 667 * @param string $title descriptive text 668 * @param string $align left|center|right 669 * @param int $width width of media in pixel 670 * @param int $height height of media in pixel 671 * @param string $cache cache|recache|nocache 672 * @param string $linking linkonly|detail|nolink 673 */ 674 public function internalmedia( 675 $src, 676 $title = null, 677 $align = null, 678 $width = null, 679 $height = null, 680 $cache = null, 681 $linking = null 682 ) { 683 } 684 685 /** 686 * Render an external media file 687 * 688 * @param string $src full media URL 689 * @param string $title descriptive text 690 * @param string $align left|center|right 691 * @param int $width width of media in pixel 692 * @param int $height height of media in pixel 693 * @param string $cache cache|recache|nocache 694 * @param string $linking linkonly|detail|nolink 695 */ 696 public function externalmedia( 697 $src, 698 $title = null, 699 $align = null, 700 $width = null, 701 $height = null, 702 $cache = null, 703 $linking = null 704 ) { 705 } 706 707 /** 708 * Render a link to an internal media file 709 * 710 * @param string $src media ID 711 * @param string $title descriptive text 712 * @param string $align left|center|right 713 * @param int $width width of media in pixel 714 * @param int $height height of media in pixel 715 * @param string $cache cache|recache|nocache 716 */ 717 public function internalmedialink( 718 $src, 719 $title = null, 720 $align = null, 721 $width = null, 722 $height = null, 723 $cache = null 724 ) { 725 } 726 727 /** 728 * Render a link to an external media file 729 * 730 * @param string $src media ID 731 * @param string $title descriptive text 732 * @param string $align left|center|right 733 * @param int $width width of media in pixel 734 * @param int $height height of media in pixel 735 * @param string $cache cache|recache|nocache 736 */ 737 public function externalmedialink( 738 $src, 739 $title = null, 740 $align = null, 741 $width = null, 742 $height = null, 743 $cache = null 744 ) { 745 } 746 747 /** 748 * Start a table 749 * 750 * @param int $maxcols maximum number of columns 751 * @param int $numrows NOT IMPLEMENTED 752 * @param int $pos byte position in the original source 753 */ 754 public function table_open($maxcols = null, $numrows = null, $pos = null) 755 { 756 } 757 758 /** 759 * Close a table 760 * 761 * @param int $pos byte position in the original source 762 */ 763 public function table_close($pos = null) 764 { 765 } 766 767 /** 768 * Open a table header 769 */ 770 public function tablethead_open() 771 { 772 } 773 774 /** 775 * Close a table header 776 */ 777 public function tablethead_close() 778 { 779 } 780 781 /** 782 * Open a table body 783 */ 784 public function tabletbody_open() 785 { 786 } 787 788 /** 789 * Close a table body 790 */ 791 public function tabletbody_close() 792 { 793 } 794 795 /** 796 * Open a table footer 797 */ 798 public function tabletfoot_open() 799 { 800 } 801 802 /** 803 * Close a table footer 804 */ 805 public function tabletfoot_close() 806 { 807 } 808 809 /** 810 * Open a table row 811 */ 812 public function tablerow_open() 813 { 814 } 815 816 /** 817 * Close a table row 818 */ 819 public function tablerow_close() 820 { 821 } 822 823 /** 824 * Open a table header cell 825 * 826 * @param int $colspan 827 * @param string $align left|center|right 828 * @param int $rowspan 829 */ 830 public function tableheader_open($colspan = 1, $align = null, $rowspan = 1) 831 { 832 } 833 834 /** 835 * Close a table header cell 836 */ 837 public function tableheader_close() 838 { 839 } 840 841 /** 842 * Open a table cell 843 * 844 * @param int $colspan 845 * @param string $align left|center|right 846 * @param int $rowspan 847 */ 848 public function tablecell_open($colspan = 1, $align = null, $rowspan = 1) 849 { 850 } 851 852 /** 853 * Close a table cell 854 */ 855 public function tablecell_close() 856 { 857 } 858 859 #endregion 860 861 #region util functions, you probably won't need to reimplement them 862 863 /** 864 * Creates a linkid from a headline 865 * 866 * @param string $title The headline title 867 * @param boolean $create Create a new unique ID? 868 * @return string 869 * @author Andreas Gohr <andi@splitbrain.org> 870 */ 871 public function _headerToLink($title, $create = false) 872 { 873 if ($create) { 874 return sectionID($title, $this->headers); 875 } else { 876 $check = false; 877 return sectionID($title, $check); 878 } 879 } 880 881 /** 882 * Removes any Namespace from the given name but keeps 883 * casing and special chars 884 * 885 * @param string $name 886 * @return string 887 * @author Andreas Gohr <andi@splitbrain.org> 888 * 889 */ 890 public function _simpleTitle($name) 891 { 892 global $conf; 893 894 // remove relative namespace 895 $name = ltrim($name, '~'); 896 897 //if there is a hash we use the ancor name only 898 [$name, $hash] = sexplode('#', $name, 2); 899 if ($hash) return $hash; 900 901 if ($conf['useslash']) { 902 $name = strtr($name, ';/', ';:'); 903 } else { 904 $name = strtr($name, ';', ':'); 905 } 906 907 return noNSorNS($name); 908 } 909 910 /** 911 * Resolve an interwikilink 912 * 913 * @param string $shortcut identifier for the interwiki link 914 * @param string $reference fragment that refers the content 915 * @param null|bool $exists reference which returns if an internal page exists 916 * @return string interwikilink 917 */ 918 public function _resolveInterWiki(&$shortcut, $reference, &$exists = null) 919 { 920 //get interwiki URL 921 if (isset($this->interwiki[$shortcut])) { 922 $url = $this->interwiki[$shortcut]; 923 } elseif (isset($this->interwiki['default'])) { 924 $shortcut = 'default'; 925 $url = $this->interwiki[$shortcut]; 926 } else { 927 // not parsable interwiki outputs '' to make sure string manipluation works 928 $shortcut = ''; 929 $url = ''; 930 } 931 932 //split into hash and url part 933 $hash = strrchr($reference, '#'); 934 if ($hash) { 935 $reference = substr($reference, 0, -strlen($hash)); 936 $hash = substr($hash, 1); 937 } 938 939 //replace placeholder 940 if (preg_match('#\{(URL|NAME|SCHEME|HOST|PORT|PATH|QUERY)\}#', $url)) { 941 //use placeholders 942 $url = str_replace('{URL}', rawurlencode($reference), $url); 943 //wiki names will be cleaned next, otherwise urlencode unsafe chars 944 $url = str_replace( 945 '{NAME}', 946 ($url[0] === ':') ? $reference : preg_replace_callback( 947 '/[[\\\\\]^`{|}#%]/', 948 static fn($match) => rawurlencode($match[0]), 949 $reference 950 ), 951 $url 952 ); 953 $parsed = parse_url($reference); 954 if (empty($parsed['scheme'])) $parsed['scheme'] = ''; 955 if (empty($parsed['host'])) $parsed['host'] = ''; 956 if (empty($parsed['port'])) $parsed['port'] = 80; 957 if (empty($parsed['path'])) $parsed['path'] = ''; 958 if (empty($parsed['query'])) $parsed['query'] = ''; 959 $url = strtr($url, [ 960 '{SCHEME}' => $parsed['scheme'], 961 '{HOST}' => $parsed['host'], 962 '{PORT}' => $parsed['port'], 963 '{PATH}' => $parsed['path'], 964 '{QUERY}' => $parsed['query'], 965 ]); 966 } elseif ($url != '') { 967 // make sure when no url is defined, we keep it null 968 // default 969 $url .= rawurlencode($reference); 970 } 971 //handle as wiki links 972 if ($url && $url[0] === ':') { 973 $urlparam = ''; 974 $id = $url; 975 if (strpos($url, '?') !== false) { 976 [$id, $urlparam] = sexplode('?', $url, 2, ''); 977 } 978 $url = wl(cleanID($id), $urlparam); 979 $exists = page_exists($id); 980 } 981 if ($hash) $url .= '#' . rawurlencode($hash); 982 983 return $url; 984 } 985 986 #endregion 987} 988 989 990//Setup VIM: ex: et ts=4 : 991