xref: /dokuwiki/inc/parser/renderer.php (revision 7d34963b3e75ea04c63ec066a6b7a692e123cb53)
1<?php
2/**
3 * Renderer output base class
4 *
5 * @author Harry Fuecks <hfuecks@gmail.com>
6 * @author Andreas Gohr <andi@splitbrain.org>
7 */
8
9use dokuwiki\Extension\Plugin;
10use dokuwiki\Extension\SyntaxPlugin;
11
12/**
13 * Allowed chars in $language for code highlighting
14 * @see GeSHi::set_language()
15 */
16define('PREG_PATTERN_VALID_LANGUAGE', '#[^a-zA-Z0-9\-_]#');
17
18/**
19 * An empty renderer, produces no output
20 *
21 * Inherits from dokuwiki\Extension\Plugin for giving additional functions to render plugins
22 *
23 * The renderer transforms the syntax instructions created by the parser and handler into the
24 * desired output format. For each instruction a corresponding method defined in this class will
25 * be called. That method needs to produce the desired output for the instruction and add it to the
26 * $doc field. When all instructions are processed, the $doc field contents will be cached by
27 * DokuWiki and sent to the user.
28 */
29abstract class Doku_Renderer extends Plugin
30{
31    /** @var array Settings, control the behavior of the renderer */
32    public $info = [
33        'cache' => true,
34        // may the rendered result cached?
35        'toc' => true,
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($src, $title = null, $align = null, $width = null,
675                                  $height = null, $cache = null, $linking = null)
676    {
677    }
678
679    /**
680     * Render an external media file
681     *
682     * @param string $src full media URL
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 externalmedia($src, $title = null, $align = null, $width = null,
691                                  $height = null, $cache = null, $linking = null)
692    {
693    }
694
695    /**
696     * Render a link to an internal media file
697     *
698     * @param string $src media ID
699     * @param string $title descriptive text
700     * @param string $align left|center|right
701     * @param int $width width of media in pixel
702     * @param int $height height of media in pixel
703     * @param string $cache cache|recache|nocache
704     */
705    public function internalmedialink($src, $title = null, $align = null,
706                                      $width = null, $height = null, $cache = null)
707    {
708    }
709
710    /**
711     * Render a link to an external media file
712     *
713     * @param string $src media ID
714     * @param string $title descriptive text
715     * @param string $align left|center|right
716     * @param int $width width of media in pixel
717     * @param int $height height of media in pixel
718     * @param string $cache cache|recache|nocache
719     */
720    public function externalmedialink($src, $title = null, $align = null,
721                                      $width = null, $height = null, $cache = null)
722    {
723    }
724
725    /**
726     * Start a table
727     *
728     * @param int $maxcols maximum number of columns
729     * @param int $numrows NOT IMPLEMENTED
730     * @param int $pos byte position in the original source
731     */
732    public function table_open($maxcols = null, $numrows = null, $pos = null)
733    {
734    }
735
736    /**
737     * Close a table
738     *
739     * @param int $pos byte position in the original source
740     */
741    public function table_close($pos = null)
742    {
743    }
744
745    /**
746     * Open a table header
747     */
748    public function tablethead_open()
749    {
750    }
751
752    /**
753     * Close a table header
754     */
755    public function tablethead_close()
756    {
757    }
758
759    /**
760     * Open a table body
761     */
762    public function tabletbody_open()
763    {
764    }
765
766    /**
767     * Close a table body
768     */
769    public function tabletbody_close()
770    {
771    }
772
773    /**
774     * Open a table footer
775     */
776    public function tabletfoot_open()
777    {
778    }
779
780    /**
781     * Close a table footer
782     */
783    public function tabletfoot_close()
784    {
785    }
786
787    /**
788     * Open a table row
789     */
790    public function tablerow_open()
791    {
792    }
793
794    /**
795     * Close a table row
796     */
797    public function tablerow_close()
798    {
799    }
800
801    /**
802     * Open a table header cell
803     *
804     * @param int $colspan
805     * @param string $align left|center|right
806     * @param int $rowspan
807     */
808    public function tableheader_open($colspan = 1, $align = null, $rowspan = 1)
809    {
810    }
811
812    /**
813     * Close a table header cell
814     */
815    public function tableheader_close()
816    {
817    }
818
819    /**
820     * Open a table cell
821     *
822     * @param int $colspan
823     * @param string $align left|center|right
824     * @param int $rowspan
825     */
826    public function tablecell_open($colspan = 1, $align = null, $rowspan = 1)
827    {
828    }
829
830    /**
831     * Close a table cell
832     */
833    public function tablecell_close()
834    {
835    }
836
837    #endregion
838
839    #region util functions, you probably won't need to reimplement them
840
841    /**
842     * Creates a linkid from a headline
843     *
844     * @param string $title The headline title
845     * @param boolean $create Create a new unique ID?
846     * @return string
847     * @author Andreas Gohr <andi@splitbrain.org>
848     */
849    public function _headerToLink($title, $create = false)
850    {
851        if ($create) {
852            return sectionID($title, $this->headers);
853        } else {
854            $check = false;
855            return sectionID($title, $check);
856        }
857    }
858
859    /**
860     * Removes any Namespace from the given name but keeps
861     * casing and special chars
862     *
863     * @param string $name
864     * @return string
865     * @author Andreas Gohr <andi@splitbrain.org>
866     *
867     */
868    public function _simpleTitle($name)
869    {
870        global $conf;
871
872        //if there is a hash we use the ancor name only
873        [$name, $hash] = sexplode('#', $name, 2);
874        if ($hash) return $hash;
875
876        if ($conf['useslash']) {
877            $name = strtr($name, ';/', ';:');
878        } else {
879            $name = strtr($name, ';', ':');
880        }
881
882        return noNSorNS($name);
883    }
884
885    /**
886     * Resolve an interwikilink
887     *
888     * @param string $shortcut identifier for the interwiki link
889     * @param string $reference fragment that refers the content
890     * @param null|bool $exists reference which returns if an internal page exists
891     * @return string interwikilink
892     */
893    public function _resolveInterWiki(&$shortcut, $reference, &$exists = null)
894    {
895        //get interwiki URL
896        if (isset($this->interwiki[$shortcut])) {
897            $url = $this->interwiki[$shortcut];
898        } elseif (isset($this->interwiki['default'])) {
899            $shortcut = 'default';
900            $url = $this->interwiki[$shortcut];
901        } else {
902            // not parsable interwiki outputs '' to make sure string manipluation works
903            $shortcut = '';
904            $url = '';
905        }
906
907        //split into hash and url part
908        $hash = strrchr($reference, '#');
909        if ($hash) {
910            $reference = substr($reference, 0, -strlen($hash));
911            $hash = substr($hash, 1);
912        }
913
914        //replace placeholder
915        if (preg_match('#\{(URL|NAME|SCHEME|HOST|PORT|PATH|QUERY)\}#', $url)) {
916            //use placeholders
917            $url = str_replace('{URL}', rawurlencode($reference), $url);
918            //wiki names will be cleaned next, otherwise urlencode unsafe chars
919            $url = str_replace(
920                '{NAME}',
921                ($url[0] === ':') ? $reference : preg_replace_callback(
922                    '/[[\\\\\]^`{|}#%]/', static fn($match) => rawurlencode($match[0]), $reference
923                ),
924                $url
925            );
926            $parsed = parse_url($reference);
927            if (empty($parsed['scheme'])) $parsed['scheme'] = '';
928            if (empty($parsed['host'])) $parsed['host'] = '';
929            if (empty($parsed['port'])) $parsed['port'] = 80;
930            if (empty($parsed['path'])) $parsed['path'] = '';
931            if (empty($parsed['query'])) $parsed['query'] = '';
932            $url = strtr($url, [
933                '{SCHEME}' => $parsed['scheme'],
934                '{HOST}' => $parsed['host'],
935                '{PORT}' => $parsed['port'],
936                '{PATH}' => $parsed['path'],
937                '{QUERY}' => $parsed['query'],
938            ]);
939        } elseif ($url != '') {
940            // make sure when no url is defined, we keep it null
941            // default
942            $url .= rawurlencode($reference);
943        }
944        //handle as wiki links
945        if ($url && $url[0] === ':') {
946            $urlparam = '';
947            $id = $url;
948            if (strpos($url, '?') !== false) {
949                [$id, $urlparam] = sexplode('?', $url, 2, '');
950            }
951            $url = wl(cleanID($id), $urlparam);
952            $exists = page_exists($id);
953        }
954        if ($hash) $url .= '#' . rawurlencode($hash);
955
956        return $url;
957    }
958
959    #endregion
960}
961
962
963//Setup VIM: ex: et ts=4 :
964