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