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