xref: /dokuwiki/inc/parser/renderer.php (revision 75364f13219a5af44f52c564ea0a62df64c3a17f)
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