1<?php
2/**
3 * Renderer for WikiText output
4 *
5 * @author Adrian Lang <lang@cosmocode.de>
6 */
7
8// must be run within Dokuwiki
9if(!defined('DOKU_INC')) die();
10
11require_once DOKU_INC.'inc/parser/renderer.php';
12
13class renderer_plugin_edittable_inverse extends Doku_Renderer {
14    /** @var string will contain the whole document */
15    public $doc = '';
16
17    // bunch of internal state variables
18    private $prepend_not_block = '';
19    private $_key = 0;
20    private $_pos = 0;
21    private $_ownspan = 0;
22    private $previous_block = false;
23    private $_row = 0;
24    private $_rowspans = array();
25    private $_table = array();
26    private $_liststack = array();
27    private $quotelvl = 0;
28    private $extlinkparser = null;
29    protected $extlinkPatterns = [];
30
31    function getFormat() {
32        return 'wiki';
33    }
34
35    function document_start() {
36    }
37
38    function document_end() {
39        $this->block();
40        $this->doc = rtrim($this->doc);
41    }
42
43    function header($text, $level, $pos) {
44        $this->block();
45        if(!$text) return; //skip empty headlines
46
47        // write the header
48        $markup = str_repeat('=', 7 - $level);
49        $this->doc .= "$markup $text $markup".DOKU_LF;
50    }
51
52    function section_open($level) {
53        $this->block();
54#        $this->doc .= DOKU_LF;
55    }
56
57    function section_close() {
58        $this->block();
59        $this->doc .= DOKU_LF;
60    }
61
62    // FIXME this did something compllicated with surrounding whitespaces. Why?
63    function cdata($text) {
64        if(strlen($text) === 0) {
65            $this->not_block();
66            return;
67        }
68
69//        if(!$this->previous_block && trim(substr($text, 0, 1)) === '' && trim($text) !== '') {
70//            $this->doc .= ' ';
71//        }
72        $this->not_block();
73
74//        if(trim(substr($text, -1, 1)) === '' && trim($text) !== '') {
75//            $this->prepend_not_block = ' ';
76//        }
77//        $this->doc .= trim($text);
78
79        $this->doc .= $text;
80    }
81
82    function p_close() {
83        $this->block();
84        if($this->quotelvl === 0) {
85            $this->doc = rtrim($this->doc, DOKU_LF).DOKU_LF.DOKU_LF;
86        }
87    }
88
89    function p_open() {
90        $this->block();
91        if(strlen($this->doc) > 0 && substr($this->doc, 1, -1) !== DOKU_LF) {
92            $this->doc .= DOKU_LF.DOKU_LF;
93        }
94        $this->doc .= str_repeat('>', $this->quotelvl);
95    }
96
97    function linebreak() {
98        $this->not_block();
99        $this->doc .= '\\\\ ';
100    }
101
102    function hr() {
103        $this->block();
104        $this->doc .= '----';
105    }
106
107    function block() {
108        if(isset($this->prepend_not_block)) {
109            unset($this->prepend_not_block);
110        }
111        $this->previous_block = true;
112    }
113
114    function not_block() {
115        if(isset($this->prepend_not_block)) {
116            $this->doc .= $this->prepend_not_block;
117            unset($this->prepend_not_block);
118        }
119        $this->previous_block = false;
120    }
121
122    function strong_open() {
123        $this->not_block();
124        $this->doc .= '**';
125    }
126
127    function strong_close() {
128        $this->not_block();
129        $this->doc .= '**';
130    }
131
132    function emphasis_open() {
133        $this->not_block();
134        $this->doc .= '//';
135    }
136
137    function emphasis_close() {
138        $this->not_block();
139        $this->doc .= '//';
140    }
141
142    function underline_open() {
143        $this->not_block();
144        $this->doc .= '__';
145    }
146
147    function underline_close() {
148        $this->not_block();
149        $this->doc .= '__';
150    }
151
152    function monospace_open() {
153        $this->not_block();
154        $this->doc .= "''";
155    }
156
157    function monospace_close() {
158        $this->not_block();
159        $this->doc .= "''";
160    }
161
162    function subscript_open() {
163        $this->not_block();
164        $this->doc .= '<sub>';
165    }
166
167    function subscript_close() {
168        $this->not_block();
169        $this->doc .= '</sub>';
170    }
171
172    function superscript_open() {
173        $this->not_block();
174        $this->doc .= '<sup>';
175    }
176
177    function superscript_close() {
178        $this->not_block();
179        $this->doc .= '</sup>';
180    }
181
182    function deleted_open() {
183        $this->not_block();
184        $this->doc .= '<del>';
185    }
186
187    function deleted_close() {
188        $this->not_block();
189        $this->doc .= '</del>';
190    }
191
192    function footnote_open() {
193        $this->not_block();
194        $this->doc .= '((';
195    }
196
197    function footnote_close() {
198        $this->not_block();
199        $this->doc .= '))';
200    }
201
202    function listu_open() {
203        $this->block();
204        if(!isset($this->_liststack)) {
205            $this->_liststack = array();
206        }
207        if(count($this->_liststack) === 0) {
208            $this->doc .= DOKU_LF;
209        }
210        $this->_liststack[] = '*';
211    }
212
213    function listu_close() {
214        $this->block();
215        array_pop($this->_liststack);
216        if(count($this->_liststack) === 0) {
217            $this->doc .= DOKU_LF;
218        }
219    }
220
221    function listo_open() {
222        $this->block();
223        if(!isset($this->_liststack)) {
224            $this->_liststack = array();
225        }
226        if(count($this->_liststack) === 0) {
227            $this->doc .= DOKU_LF;
228        }
229        $this->_liststack[] = '-';
230    }
231
232    function listo_close() {
233        $this->block();
234        array_pop($this->_liststack);
235        if(count($this->_liststack) === 0) {
236            $this->doc .= DOKU_LF;
237        }
238    }
239
240    function listitem_open($level, $node = false) {
241        $this->block();
242        $this->doc .= str_repeat(' ', $level * 2).end($this->_liststack).' ';
243    }
244
245    function listcontent_close() {
246        $this->block();
247        $this->doc .= DOKU_LF;
248    }
249
250    function unformatted($text) {
251        $this->not_block();
252        if(strpos($text, '%%') !== false) {
253            $this->doc .= "<nowiki>$text</nowiki>";
254        } elseif($text[0] == "\n") {
255            $this->doc .= "<nowiki>$text</nowiki>";
256        } else {
257            $this->doc .= "%%$text%%";
258        }
259    }
260
261    function php($text, $wrapper = 'code') {
262        $this->not_block();
263        $this->doc .= "<php>$text</php>";
264    }
265
266    function phpblock($text) {
267        $this->block();
268        $this->doc .= "<PHP>$text</PHP>";
269    }
270
271    function html($text, $wrapper = 'code') {
272        $this->not_block();
273        $this->doc .= "<html>$text</html>";
274    }
275
276    function htmlblock($text) {
277        $this->block();
278        $this->doc .= "<HTML>$text</HTML>";
279    }
280
281    function quote_open() {
282        $this->block();
283        if(substr($this->doc, -(++$this->quotelvl)) === DOKU_LF.str_repeat('>', $this->quotelvl - 1)) {
284            $this->doc .= '>';
285        } else {
286            $this->doc .= DOKU_LF.str_repeat('>', $this->quotelvl);
287        }
288        $this->prepend_not_block = ' ';
289    }
290
291    function quote_close() {
292        $this->block();
293        $this->quotelvl--;
294        if(strrpos($this->doc, DOKU_LF) === strlen($this->doc) - 1) {
295            return;
296        }
297        $this->doc .= DOKU_LF.DOKU_LF;
298    }
299
300    function preformatted($text) {
301        $this->block();
302        $this->doc .= preg_replace('/^/m', '  ', $text).DOKU_LF;
303    }
304
305    function file($text, $language = null, $filename = null) {
306        $this->_highlight('file', $text, $language, $filename);
307    }
308
309    function code($text, $language = null, $filename = null) {
310        $this->_highlight('code', $text, $language, $filename);
311    }
312
313    function _highlight($type, $text, $language = null, $filename = null) {
314        if( $this->previous_block ) $this->doc .= "\n";
315
316        $this->block();
317        $this->doc .= "<$type";
318        if($language != null) {
319            $this->doc .= " $language";
320        }
321        if($filename != null) {
322            $this->doc .= " $filename";
323        }
324        $this->doc .= ">";
325        $this->doc .= $text;
326        if($text[0] == "\n") $this->doc .= "\n";
327        $this->doc .= "</$type>";
328    }
329
330    function acronym($acronym) {
331        $this->not_block();
332        $this->doc .= $acronym;
333    }
334
335    function smiley($smiley) {
336        $this->not_block();
337        $this->doc .= $smiley;
338    }
339
340    function entity($entity) {
341        $this->not_block();
342        $this->doc .= $entity;
343    }
344
345    function multiplyentity($x, $y) {
346        $this->not_block();
347        $this->doc .= "{$x}x{$y}";
348    }
349
350    function singlequoteopening() {
351        $this->not_block();
352        $this->doc .= "'";
353    }
354
355    function singlequoteclosing() {
356        $this->not_block();
357        $this->doc .= "'";
358    }
359
360    function apostrophe() {
361        $this->not_block();
362        $this->doc .= "'";
363    }
364
365    function doublequoteopening() {
366        $this->not_block();
367        $this->doc .= '"';
368    }
369
370    function doublequoteclosing() {
371        $this->not_block();
372        $this->doc .= '"';
373    }
374
375    /**
376     */
377    function camelcaselink($link) {
378        $this->not_block();
379        $this->doc .= $link;
380    }
381
382    function locallink($hash, $name = null) {
383        $this->not_block();
384        $this->doc .= "[[#$hash";
385        if($name !== null) {
386            $this->doc .= '|';
387            $this->_echoLinkTitle($name);
388        }
389        $this->doc .= ']]';
390    }
391
392    function internallink($id, $name = null, $search = null, $returnonly = false, $linktype = 'content') {
393        $this->not_block();
394        $this->doc .= "[[$id";
395        if($name !== null) {
396            $this->doc .= '|';
397            $this->_echoLinkTitle($name);
398        }
399        $this->doc .= ']]';
400    }
401
402    /**
403     * Handle external Links
404     *
405     * @author Andreas Gohr <andi@splitbrain.org>
406     * @param      $url
407     * @param null $name
408     */
409    function externallink($url, $name = null) {
410        $this->not_block();
411
412        /*
413         * When $name is null it might have been a match of an URL that was in the text without
414         * any link syntax. These are recognized by a bunch of patterns in Doku_Parser_Mode_externallink.
415         * We simply reuse these patterns here. However, since we don't parse the pattern through the Lexer,
416         * no escaping is done on the patterns - this means we need a non-conflicting delimiter. I decided for
417         * a single tick >>'<< which seems to work. Since the patterns contain wordboundaries they are matched
418         * against the URL surrounded by spaces.
419         */
420        if($name === null) {
421            // get the patterns from the parser if available, otherwise use a duplicate
422            if(is_null($this->extlinkparser)) {
423                if (
424                    class_exists('\dokuwiki\Parsing\ParserMode\Externallink') &&
425                    method_exists('\dokuwiki\Parsing\ParserMode\Externallink', 'getPatterns')
426                ) {
427                    $this->extlinkparser = new \dokuwiki\Parsing\ParserMode\Externallink();
428                    $this->extlinkparser->preConnect();
429                    $this->extlinkPatterns = $this->extlinkparser->getPatterns();
430                } else {
431                    $ltrs = '\w';
432                    $gunk = '/\#~:.?+=&%@!\-\[\]';
433                    $punc = '.:?\-;,';
434                    $host = $ltrs . $punc;
435                    $any  = $ltrs . $gunk . $punc;
436
437                    $schemes = getSchemes();
438                    foreach ($schemes as $scheme) {
439                        $this->extlinkPatterns[] = '\b(?i)'.$scheme.'(?-i)://['.$any.']+?(?=['.$punc.']*[^'.$any.'])';
440                    }
441
442                    $this->extlinkPatterns[] = '(?<=\s)(?i)www?(?-i)\.['.$host.']+?\.['.$host.']+?['.$any.']+?(?=['.$punc.']*[^'.$any.'])';
443                    $this->extlinkPatterns[] = '(?<=\s)(?i)ftp?(?-i)\.['.$host.']+?\.['.$host.']+?['.$any.']+?(?=['.$punc.']*[^'.$any.'])';
444                }
445            }
446
447            // check if URL matches pattern
448            foreach($this->extlinkPatterns as $pattern) {
449                if(preg_match("'$pattern'", " $url ")) {
450                    $this->doc .= $url; // gotcha!
451                    return;
452                }
453            }
454        }
455
456        // still here?
457        if($url === "http://$name" || $url === "ftp://$name") {
458            // special case - www.* or ftp.* matching
459            $this->doc .= $name;
460        } else {
461            // link syntax! definitively link syntax
462            $this->doc .= "[[$url";
463            if(!is_null($name)) {
464                // we do have a name!
465                $this->doc .= '|';
466                $this->_echoLinkTitle($name);
467            }
468            $this->doc .= ']]';
469        }
470    }
471
472    function interwikilink($match, $name = null, $wikiName, $wikiUri) {
473        $this->not_block();
474        $this->doc .= "[[$wikiName>$wikiUri";
475        if($name !== null) {
476            $this->doc .= '|';
477            $this->_echoLinkTitle($name);
478        }
479        $this->doc .= ']]';
480    }
481
482    function windowssharelink($url, $name = null) {
483        $this->not_block();
484        $this->doc .= "[[$url";
485        if($name !== null) {
486            $this->doc .= '|';
487            $this->_echoLinkTitle($name);
488        }
489        $this->doc .= "]]";
490    }
491
492    function emaillink($address, $name = null) {
493        $this->not_block();
494        if($name === null) {
495            $this->doc .= "<$address>";
496        } else {
497            $this->doc .= "[[$address|";
498            $this->_echoLinkTitle($name);
499            $this->doc .= ']]';
500        }
501    }
502
503    function internalmedia($src, $title = null, $align = null, $width = null,
504                           $height = null, $cache = null, $linking = null) {
505        $this->not_block();
506        $this->doc .= '{{';
507        if($align === 'center' || $align === 'right') {
508            $this->doc .= ' ';
509        }
510        $this->doc .= $src;
511
512        $params = array();
513        if($width !== null) {
514            $params[0] = $width;
515            if($height !== null) {
516                $params[0] .= "x$height";
517            }
518        }
519        if($cache !== 'cache') {
520            $params[] = $cache;
521        }
522        if($linking !== 'details') {
523            $params[] = $linking;
524        }
525        if(count($params) > 0) {
526            $this->doc .= '?';
527        }
528        $this->doc .= join('&', $params);
529
530        if($align === 'center' || $align === 'left') {
531            $this->doc .= ' ';
532        }
533        if($title != null) {
534            $this->doc .= "|$title";
535        }
536        $this->doc .= '}}';
537    }
538
539    function externalmedia($src, $title = null, $align = null, $width = null,
540                           $height = null, $cache = null, $linking = null) {
541        $this->internalmedia($src, $title, $align, $width, $height, $cache, $linking);
542    }
543
544    /**
545     * Renders an RSS feed
546     *
547     * @author Andreas Gohr <andi@splitbrain.org>
548     */
549    function rss($url, $params) {
550        $this->block();
551        $this->doc .= '{{rss>'.$url;
552        $vals = array();
553        if($params['max'] !== 8) {
554            $vals[] = $params['max'];
555        }
556        if($params['reverse']) {
557            $vals[] = 'reverse';
558        }
559        if($params['author']) {
560            $vals[] = 'author';
561        }
562        if($params['date']) {
563            $vals[] = 'date';
564        }
565        if($params['details']) {
566            $vals[] = 'desc';
567        }
568        if($params['refresh'] !== 14400) {
569            $val = '10m';
570            foreach(array('d' => 86400, 'h' => 3600, 'm' => 60) as $p => $div) {
571                $res = $params['refresh'] / $div;
572                if($res === intval($res)) {
573                    $val = "$res$p";
574                    break;
575                }
576            }
577            $vals[] = $val;
578        }
579        if(count($vals) > 0) {
580            $this->doc .= ' '.join(' ', $vals);
581        }
582        $this->doc .= '}}';
583    }
584
585    function table_open($maxcols = null, $numrows = null, $pos = null) {
586        $this->block();
587        $this->_table    = array();
588        $this->_row      = 0;
589        $this->_rowspans = array();
590    }
591
592    function table_close($pos = null) {
593        $this->doc .= $this->_table_to_wikitext($this->_table);
594    }
595
596    function tablerow_open() {
597        $this->block();
598        $this->_table[++$this->_row] = array();
599        $this->_key                  = 1;
600        while(isset($this->_rowspans[$this->_key])) {
601            --$this->_rowspans[$this->_key];
602            if($this->_rowspans[$this->_key] === 1) {
603                unset($this->_rowspans[$this->_key]);
604            }
605            ++$this->_key;
606        }
607    }
608
609    function tablerow_close() {
610        $this->block();
611    }
612
613    function tableheader_open($colspan = 1, $align = null, $rowspan = 1) {
614        $this->_cellopen('th', $colspan, $align, $rowspan);
615    }
616
617    function _cellopen($tag, $colspan, $align, $rowspan) {
618        $this->block();
619        $this->_table[$this->_row][$this->_key] = compact('tag', 'colspan', 'align', 'rowspan');
620        if($rowspan > 1) {
621            $this->_rowspans[$this->_key] = $rowspan;
622            $this->_ownspan               = true;
623        }
624        $this->_pos = strlen($this->doc);
625    }
626
627    function tableheader_close() {
628        $this->_cellclose();
629    }
630
631    function _cellclose() {
632        $this->block();
633        $this->_table[$this->_row][$this->_key]['text'] = trim(substr($this->doc, $this->_pos));
634        $this->doc                                      = substr($this->doc, 0, $this->_pos);
635        $this->_key += $this->_table[$this->_row][$this->_key]['colspan'];
636        while(isset($this->_rowspans[$this->_key]) && !$this->_ownspan) {
637            --$this->_rowspans[$this->_key];
638            if($this->_rowspans[$this->_key] === 1) {
639                unset($this->_rowspans[$this->_key]);
640            }
641            ++$this->_key;
642        }
643        $this->_ownspan = false;
644    }
645
646    function tablecell_open($colspan = 1, $align = null, $rowspan = 1) {
647        $this->_cellopen('td', $colspan, $align, $rowspan);
648    }
649
650    function tablecell_close() {
651        $this->_cellclose();
652    }
653
654    function plugin($name, $args, $state = '', $match = '') {
655        $this->not_block();
656        // This will break for plugins which provide a catch-all render method
657        // like the do or pagenavi plugins
658#        $plugin =& plugin_load('syntax',$name);
659#        if($plugin === null || !$plugin->render($this->getFormat(),$this,$args)) {
660        $this->doc .= $match;
661#        }
662    }
663
664    function _echoLinkTitle($title) {
665        if(is_array($title)) {
666            $this->internalmedia(
667                $title['src'],
668                $title['title'],
669                $title['align'],
670                $title['width'],
671                $title['height'],
672                $title['cache'],
673                $title['linking']
674            );
675        } else {
676            $this->doc .= $title;
677        }
678    }
679
680    /**
681     * Helper for table to wikitext conversion
682     *
683     * @author Adrian Lang <lang@cosmocode.de>
684     * @param array $_table
685     * @return string
686     */
687    private function _table_to_wikitext($_table) {
688        // Preprocess table for rowspan, make table 0-based.
689        $table = array();
690        $keys  = array_keys($_table);
691        $start = array_pop($keys);
692        foreach($_table as $i => $row) {
693            $inorm = $i - $start;
694            if(!isset($table[$inorm])) $table[$inorm] = array();
695            $nextkey = 0;
696            foreach($row as $cell) {
697                while(isset($table[$inorm][$nextkey])) {
698                    $nextkey++;
699                }
700                $nextkey += $cell['colspan'] - 1;
701                $table[$inorm][$nextkey] = $cell;
702                $rowspan                 = $cell['rowspan'];
703                $i2                      = $inorm + 1;
704                while($rowspan-- > 1) {
705                    if(!isset($table[$i2])) $table[$i2] = array();
706                    $nu_cell                = $cell;
707                    $nu_cell['text']        = ':::';
708                    $nu_cell['rowspan']     = 1;
709                    $table[$i2++][$nextkey] = $nu_cell;
710                }
711            }
712            ksort($table[$inorm]);
713        }
714
715        // Get the max width for every column to do table prettyprinting.
716        $m_width = array();
717        foreach($table as $row) {
718            foreach($row as $n => $cell) {
719                // Calculate cell width.
720                $diff = (utf8_strlen($cell['text']) + $cell['colspan'] +
721                    ($cell['align'] === 'center' ? 3 : 2));
722
723                // Calculate current max width.
724                $span = $cell['colspan'];
725                while(--$span >= 0) {
726                    if(isset($m_width[$n - $span])) {
727                        $diff -= $m_width[$n - $span];
728                    }
729                }
730
731                if($diff > 0) {
732                    // Just add the difference to all cols.
733                    while(++$span < $cell['colspan']) {
734                        $m_width[$n - $span] = (isset($m_width[$n - $span]) ? $m_width[$n - $span] : 0) + ceil($diff / $cell['colspan']);
735                    }
736                }
737            }
738        }
739
740        // Write the table.
741        $types = array('th' => '^', 'td' => '|');
742        $str   = '';
743        foreach($table as $row) {
744            $pos = 0;
745            foreach($row as $n => $cell) {
746                $pos += utf8_strlen($cell['text']) + 1;
747                $span   = $cell['colspan'];
748                $target = 0;
749                while(--$span >= 0) {
750                    if(isset($m_width[$n - $span])) {
751                        $target += $m_width[$n - $span];
752                    }
753                }
754                $pad = $target - utf8_strlen($cell['text']);
755                $pos += $pad + ($cell['colspan'] - 1);
756                switch($cell['align']) {
757                    case 'right':
758                        $lpad = $pad - 1;
759                        break;
760                    case 'left':
761                    case '':
762                        $lpad = 1;
763                        break;
764                    case 'center':
765                        $lpad = floor($pad / 2);
766                        break;
767                }
768                $str .= $types[$cell['tag']].str_repeat(' ', $lpad).
769                    $cell['text'].str_repeat(' ', $pad - $lpad).
770                    str_repeat($types[$cell['tag']], $cell['colspan'] - 1);
771            }
772            $str .= $types[$cell['tag']].DOKU_LF;
773        }
774        return $str;
775    }
776}
777
778//Setup VIM: ex: et ts=4 enc=utf-8 :
779