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 * Open an ordered list with a non-default starting number 392 * 393 * Sibling of listo_open for ordered lists whose first item is numbered 394 * other than 1 (e.g. GFM "5. foo" -> start at 5). The default delegates 395 * to listo_open so renderers that don't care about the starting number 396 * still produce a valid list. Renderers that DO care (xhtml emits 397 * start="N") override this method. 398 * 399 * @param int $start Starting number for the list 400 */ 401 public function listo_open_start($start = 1) 402 { 403 $this->listo_open(); 404 } 405 406 /** 407 * Close an ordered list 408 */ 409 public function listo_close() 410 { 411 } 412 413 /** 414 * Open a list item 415 * 416 * @param int $level the nesting level 417 * @param bool $node true when a node; false when a leaf 418 */ 419 public function listitem_open($level, $node = false) 420 { 421 } 422 423 /** 424 * Close a list item 425 */ 426 public function listitem_close() 427 { 428 } 429 430 /** 431 * Start the content of a list item 432 */ 433 public function listcontent_open() 434 { 435 } 436 437 /** 438 * Stop the content of a list item 439 */ 440 public function listcontent_close() 441 { 442 } 443 444 /** 445 * Output unformatted $text 446 * 447 * Defaults to $this->cdata() 448 * 449 * @param string $text 450 */ 451 public function unformatted($text) 452 { 453 $this->cdata($text); 454 } 455 456 /** 457 * Output preformatted text 458 * 459 * @param string $text 460 */ 461 public function preformatted($text) 462 { 463 } 464 465 /** 466 * Start a block quote 467 */ 468 public function quote_open() 469 { 470 } 471 472 /** 473 * Stop a block quote 474 */ 475 public function quote_close() 476 { 477 } 478 479 /** 480 * Display text as file content, optionally syntax highlighted 481 * 482 * @param string $text text to show 483 * @param string $lang programming language to use for syntax highlighting 484 * @param string $file file path label 485 */ 486 public function file($text, $lang = null, $file = null) 487 { 488 } 489 490 /** 491 * Display text as code content, optionally syntax highlighted 492 * 493 * @param string $text text to show 494 * @param string $lang programming language to use for syntax highlighting 495 * @param string $file file path label 496 */ 497 public function code($text, $lang = null, $file = null) 498 { 499 } 500 501 /** 502 * Format an acronym 503 * 504 * Uses $this->acronyms 505 * 506 * @param string $acronym 507 */ 508 public function acronym($acronym) 509 { 510 } 511 512 /** 513 * Format a smiley 514 * 515 * Uses $this->smiley 516 * 517 * @param string $smiley 518 */ 519 public function smiley($smiley) 520 { 521 } 522 523 /** 524 * Format an entity 525 * 526 * Entities are basically small text replacements 527 * 528 * Uses $this->entities 529 * 530 * @param string $entity 531 */ 532 public function entity($entity) 533 { 534 } 535 536 /** 537 * Typographically format a multiply sign 538 * 539 * Example: ($x=640, $y=480) should result in "640×480" 540 * 541 * @param string|int $x first value 542 * @param string|int $y second value 543 */ 544 public function multiplyentity($x, $y) 545 { 546 } 547 548 /** 549 * Render an opening single quote char (language specific) 550 */ 551 public function singlequoteopening() 552 { 553 } 554 555 /** 556 * Render a closing single quote char (language specific) 557 */ 558 public function singlequoteclosing() 559 { 560 } 561 562 /** 563 * Render an apostrophe char (language specific) 564 */ 565 public function apostrophe() 566 { 567 } 568 569 /** 570 * Render an opening double quote char (language specific) 571 */ 572 public function doublequoteopening() 573 { 574 } 575 576 /** 577 * Render an closinging double quote char (language specific) 578 */ 579 public function doublequoteclosing() 580 { 581 } 582 583 /** 584 * Render a CamelCase link 585 * 586 * @param string $link The link name 587 * @see http://en.wikipedia.org/wiki/CamelCase 588 */ 589 public function camelcaselink($link) 590 { 591 } 592 593 /** 594 * Render a page local link 595 * 596 * @param string $hash hash link identifier 597 * @param string $name name for the link 598 */ 599 public function locallink($hash, $name = null) 600 { 601 } 602 603 /** 604 * Render a wiki internal link 605 * 606 * @param string $link page ID to link to. eg. 'wiki:syntax' 607 * @param string|array $title name for the link, array for media file 608 */ 609 public function internallink($link, $title = null) 610 { 611 } 612 613 /** 614 * Render an external link 615 * 616 * @param string $link full URL with scheme 617 * @param string|array $title name for the link, array for media file 618 */ 619 public function externallink($link, $title = null) 620 { 621 } 622 623 /** 624 * Render the output of an RSS feed 625 * 626 * @param string $url URL of the feed 627 * @param array $params Finetuning of the output 628 */ 629 public function rss($url, $params) 630 { 631 } 632 633 /** 634 * Render an interwiki link 635 * 636 * You may want to use $this->_resolveInterWiki() here 637 * 638 * @param string $link original link - probably not much use 639 * @param string|array $title name for the link, array for media file 640 * @param string $wikiName indentifier (shortcut) for the remote wiki 641 * @param string $wikiUri the fragment parsed from the original link 642 */ 643 public function interwikilink($link, $title, $wikiName, $wikiUri) 644 { 645 } 646 647 /** 648 * Link to file on users OS 649 * 650 * @param string $link the link 651 * @param string|array $title name for the link, array for media file 652 */ 653 public function filelink($link, $title = null) 654 { 655 } 656 657 /** 658 * Link to windows share 659 * 660 * @param string $link the link 661 * @param string|array $title name for the link, array for media file 662 */ 663 public function windowssharelink($link, $title = null) 664 { 665 } 666 667 /** 668 * Render a linked E-Mail Address 669 * 670 * Should honor $conf['mailguard'] setting 671 * 672 * @param string $address Email-Address 673 * @param string|array $name name for the link, array for media file 674 */ 675 public function emaillink($address, $name = null) 676 { 677 } 678 679 /** 680 * Render an internal media file 681 * 682 * @param string $src media ID 683 * @param string $title descriptive text 684 * @param string $align left|center|right 685 * @param int $width width of media in pixel 686 * @param int $height height of media in pixel 687 * @param string $cache cache|recache|nocache 688 * @param string $linking linkonly|detail|nolink 689 */ 690 public function internalmedia( 691 $src, 692 $title = null, 693 $align = null, 694 $width = null, 695 $height = null, 696 $cache = null, 697 $linking = null 698 ) { 699 } 700 701 /** 702 * Render an external media file 703 * 704 * @param string $src full media URL 705 * @param string $title descriptive text 706 * @param string $align left|center|right 707 * @param int $width width of media in pixel 708 * @param int $height height of media in pixel 709 * @param string $cache cache|recache|nocache 710 * @param string $linking linkonly|detail|nolink 711 */ 712 public function externalmedia( 713 $src, 714 $title = null, 715 $align = null, 716 $width = null, 717 $height = null, 718 $cache = null, 719 $linking = null 720 ) { 721 } 722 723 /** 724 * Render a link to an internal media file 725 * 726 * @param string $src media ID 727 * @param string $title descriptive text 728 * @param string $align left|center|right 729 * @param int $width width of media in pixel 730 * @param int $height height of media in pixel 731 * @param string $cache cache|recache|nocache 732 */ 733 public function internalmedialink( 734 $src, 735 $title = null, 736 $align = null, 737 $width = null, 738 $height = null, 739 $cache = null 740 ) { 741 } 742 743 /** 744 * Render a link to an external media file 745 * 746 * @param string $src media ID 747 * @param string $title descriptive text 748 * @param string $align left|center|right 749 * @param int $width width of media in pixel 750 * @param int $height height of media in pixel 751 * @param string $cache cache|recache|nocache 752 */ 753 public function externalmedialink( 754 $src, 755 $title = null, 756 $align = null, 757 $width = null, 758 $height = null, 759 $cache = null 760 ) { 761 } 762 763 /** 764 * Start a table 765 * 766 * @param int $maxcols maximum number of columns 767 * @param int $numrows NOT IMPLEMENTED 768 * @param int $pos byte position in the original source 769 */ 770 public function table_open($maxcols = null, $numrows = null, $pos = null) 771 { 772 } 773 774 /** 775 * Close a table 776 * 777 * @param int $pos byte position in the original source 778 */ 779 public function table_close($pos = null) 780 { 781 } 782 783 /** 784 * Open a table header 785 */ 786 public function tablethead_open() 787 { 788 } 789 790 /** 791 * Close a table header 792 */ 793 public function tablethead_close() 794 { 795 } 796 797 /** 798 * Open a table body 799 */ 800 public function tabletbody_open() 801 { 802 } 803 804 /** 805 * Close a table body 806 */ 807 public function tabletbody_close() 808 { 809 } 810 811 /** 812 * Open a table footer 813 */ 814 public function tabletfoot_open() 815 { 816 } 817 818 /** 819 * Close a table footer 820 */ 821 public function tabletfoot_close() 822 { 823 } 824 825 /** 826 * Open a table row 827 */ 828 public function tablerow_open() 829 { 830 } 831 832 /** 833 * Close a table row 834 */ 835 public function tablerow_close() 836 { 837 } 838 839 /** 840 * Open a table header cell 841 * 842 * @param int $colspan 843 * @param string $align left|center|right 844 * @param int $rowspan 845 */ 846 public function tableheader_open($colspan = 1, $align = null, $rowspan = 1) 847 { 848 } 849 850 /** 851 * Close a table header cell 852 */ 853 public function tableheader_close() 854 { 855 } 856 857 /** 858 * Open a table cell 859 * 860 * @param int $colspan 861 * @param string $align left|center|right 862 * @param int $rowspan 863 */ 864 public function tablecell_open($colspan = 1, $align = null, $rowspan = 1) 865 { 866 } 867 868 /** 869 * Close a table cell 870 */ 871 public function tablecell_close() 872 { 873 } 874 875 #endregion 876 877 #region util functions, you probably won't need to reimplement them 878 879 /** 880 * Creates a linkid from a headline 881 * 882 * @param string $title The headline title 883 * @param boolean $create Create a new unique ID? 884 * @return string 885 * @author Andreas Gohr <andi@splitbrain.org> 886 */ 887 public function _headerToLink($title, $create = false) 888 { 889 if ($create) { 890 return sectionID($title, $this->headers); 891 } else { 892 $check = false; 893 return sectionID($title, $check); 894 } 895 } 896 897 /** 898 * Removes any Namespace from the given name but keeps 899 * casing and special chars 900 * 901 * @param string $name 902 * @return string 903 * @author Andreas Gohr <andi@splitbrain.org> 904 * 905 */ 906 public function _simpleTitle($name) 907 { 908 global $conf; 909 910 // remove relative namespace 911 $name = ltrim($name, '~'); 912 913 //if there is a hash we use the ancor name only 914 [$name, $hash] = sexplode('#', $name, 2); 915 if ($hash) return $hash; 916 917 if ($conf['useslash']) { 918 $name = strtr($name, ';/', ';:'); 919 } else { 920 $name = strtr($name, ';', ':'); 921 } 922 923 return noNSorNS($name); 924 } 925 926 /** 927 * Resolve an interwikilink 928 * 929 * @param string $shortcut identifier for the interwiki link 930 * @param string $reference fragment that refers the content 931 * @param null|bool $exists reference which returns if an internal page exists 932 * @return string interwikilink 933 */ 934 public function _resolveInterWiki(&$shortcut, $reference, &$exists = null) 935 { 936 //get interwiki URL 937 if (isset($this->interwiki[$shortcut])) { 938 $url = $this->interwiki[$shortcut]; 939 } elseif (isset($this->interwiki['default'])) { 940 $shortcut = 'default'; 941 $url = $this->interwiki[$shortcut]; 942 } else { 943 // not parsable interwiki outputs '' to make sure string manipluation works 944 $shortcut = ''; 945 $url = ''; 946 } 947 948 //split into hash and url part 949 $hash = strrchr($reference, '#'); 950 if ($hash) { 951 $reference = substr($reference, 0, -strlen($hash)); 952 $hash = substr($hash, 1); 953 } 954 955 //replace placeholder 956 if (preg_match('#\{(URL|NAME|SCHEME|HOST|PORT|PATH|QUERY)\}#', $url)) { 957 //use placeholders 958 $url = str_replace('{URL}', rawurlencode($reference), $url); 959 //wiki names will be cleaned next, otherwise urlencode unsafe chars 960 $url = str_replace( 961 '{NAME}', 962 ($url[0] === ':') ? $reference : preg_replace_callback( 963 '/[[\\\\\]^`{|}#%]/', 964 static fn($match) => rawurlencode($match[0]), 965 $reference 966 ), 967 $url 968 ); 969 $parsed = parse_url($reference); 970 if (empty($parsed['scheme'])) $parsed['scheme'] = ''; 971 if (empty($parsed['host'])) $parsed['host'] = ''; 972 if (empty($parsed['port'])) $parsed['port'] = 80; 973 if (empty($parsed['path'])) $parsed['path'] = ''; 974 if (empty($parsed['query'])) $parsed['query'] = ''; 975 $url = strtr($url, [ 976 '{SCHEME}' => $parsed['scheme'], 977 '{HOST}' => $parsed['host'], 978 '{PORT}' => $parsed['port'], 979 '{PATH}' => $parsed['path'], 980 '{QUERY}' => $parsed['query'], 981 ]); 982 } elseif ($url != '') { 983 // make sure when no url is defined, we keep it null 984 // default 985 $url .= rawurlencode($reference); 986 } 987 //handle as wiki links 988 if ($url && $url[0] === ':') { 989 $urlparam = ''; 990 $id = $url; 991 if (str_contains($url, '?')) { 992 [$id, $urlparam] = sexplode('?', $url, 2, ''); 993 } 994 $url = wl(cleanID($id), $urlparam); 995 $exists = page_exists($id); 996 } 997 if ($hash) $url .= '#' . rawurlencode($hash); 998 999 return $url; 1000 } 1001 1002 #endregion 1003} 1004 1005 1006//Setup VIM: ex: et ts=4 : 1007