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