xref: /dokuwiki/inc/parser/handler.php (revision 27a2b0851e6b7273beab68070c1e639454918da7)
1<?php
2if(!defined('DOKU_INC')) define('DOKU_INC',realpath(dirname(__FILE__).'/../../').'/');
3
4class Doku_Handler {
5
6    var $Renderer = NULL;
7
8    var $CallWriter = NULL;
9
10    var $calls = array();
11
12    var $meta = array(
13        'section' => FALSE,
14        'toc' => TRUE,
15    );
16
17    var $rewriteBlocks = TRUE;
18
19    function Doku_Handler() {
20        $this->CallWriter = & new Doku_Handler_CallWriter($this);
21    }
22
23    function _addCall($handler, $args, $pos) {
24        $call = array($handler,$args, $pos);
25        $this->CallWriter->writeCall($call);
26    }
27
28    function _finalize(){
29        if ( $this->meta['section'] ) {
30            $S = & new Doku_Handler_Section();
31            $this->calls = $S->process($this->calls);
32        }
33
34        if ( $this->rewriteBlocks ) {
35            $B = & new Doku_Handler_Block();
36            $this->calls = $B->process($this->calls);
37        }
38
39        if ( $this->meta['toc'] ) {
40            $T = & new Doku_Handler_Toc();
41            $this->calls = $T->process($this->calls);
42        }
43
44        array_unshift($this->calls,array('document_start',array(),0));
45        $last_call = end($this->calls);
46        array_push($this->calls,array('document_end',array(),$last_call[2]));
47    }
48
49    function fetch() {
50        $call = each($this->calls);
51        if ( $call ) {
52            return $call['value'];
53        }
54        return FALSE;
55    }
56
57    function base($match, $state, $pos) {
58        switch ( $state ) {
59            case DOKU_LEXER_UNMATCHED:
60                $this->_addCall('cdata',array($match), $pos);
61                return TRUE;
62            break;
63
64        }
65    }
66
67    function header($match, $state, $pos) {
68        $match = trim($match);
69        $levels = array(
70            '======'=>1,
71            '====='=>2,
72            '===='=>3,
73            '==='=>4,
74            '=='=>5,
75        );
76        $hsplit = preg_split( '/(={2,})/u', $match,-1,
77            PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY );
78
79        // Locate the level - default to level 1 if no match (title contains == signs)
80        if ( isset($hsplit[0]) && array_key_exists($hsplit[0], $levels) ) {
81            $level = $levels[$hsplit[0]];
82        } else {
83            $level = 1;
84        }
85
86        // Strip of the marker for the header, based on the level - the rest is the title
87        $iLevels = array_flip($levels);
88        $markerLen = strlen($iLevels[$level]);
89        $title = substr($match, $markerLen, strlen($match)-($markerLen*2));
90
91        $this->_addCall('header',array($title,$level,$pos), $pos);
92        $this->meta['section'] = TRUE;
93        return TRUE;
94    }
95
96    function notoc($match, $state, $pos) {
97        $this->meta['toc'] = FALSE;
98        return TRUE;
99    }
100
101    function linebreak($match, $state, $pos) {
102        $this->_addCall('linebreak',array(),$pos);
103        return TRUE;
104    }
105
106    function eol($match, $state, $pos) {
107        $this->_addCall('eol',array(),$pos);
108        return TRUE;
109    }
110
111    function hr($match, $state, $pos) {
112        $this->_addCall('hr',array(),$pos);
113        return TRUE;
114    }
115
116    function _nestingTag($match, $state, $pos, $name) {
117        switch ( $state ) {
118            case DOKU_LEXER_ENTER:
119                $this->_addCall($name.'_open', array(), $pos);
120            break;
121            case DOKU_LEXER_EXIT:
122                $this->_addCall($name.'_close', array(), $pos);
123            break;
124            case DOKU_LEXER_UNMATCHED:
125                $this->_addCall('cdata',array($match), $pos);
126            break;
127        }
128    }
129
130    function strong($match, $state, $pos) {
131        $this->_nestingTag($match, $state, $pos, 'strong');
132        return TRUE;
133    }
134
135    function emphasis($match, $state, $pos) {
136        $this->_nestingTag($match, $state, $pos, 'emphasis');
137        return TRUE;
138    }
139
140    function underline($match, $state, $pos) {
141        $this->_nestingTag($match, $state, $pos, 'underline');
142        return TRUE;
143    }
144
145    function monospace($match, $state, $pos) {
146        $this->_nestingTag($match, $state, $pos, 'monospace');
147        return TRUE;
148    }
149
150    function subscript($match, $state, $pos) {
151        $this->_nestingTag($match, $state, $pos, 'subscript');
152        return TRUE;
153    }
154
155    function superscript($match, $state, $pos) {
156        $this->_nestingTag($match, $state, $pos, 'superscript');
157        return TRUE;
158    }
159
160    function deleted($match, $state, $pos) {
161        $this->_nestingTag($match, $state, $pos, 'deleted');
162        return TRUE;
163    }
164
165
166    function footnote($match, $state, $pos) {
167        $this->_nestingTag($match, $state, $pos, 'footnote');
168        return TRUE;
169    }
170
171    function listblock($match, $state, $pos) {
172        switch ( $state ) {
173            case DOKU_LEXER_ENTER:
174                $ReWriter = & new Doku_Handler_List($this->CallWriter);
175                $this->CallWriter = & $ReWriter;
176                $this->_addCall('list_open', array($match), $pos);
177            break;
178            case DOKU_LEXER_EXIT:
179                $this->_addCall('list_close', array(), $pos);
180                $this->CallWriter->process();
181                $ReWriter = & $this->CallWriter;
182                $this->CallWriter = & $ReWriter->CallWriter;
183            break;
184            case DOKU_LEXER_MATCHED:
185                $this->_addCall('list_item', array($match), $pos);
186            break;
187            case DOKU_LEXER_UNMATCHED:
188                $this->_addCall('cdata', array($match), $pos);
189            break;
190        }
191        return TRUE;
192    }
193
194    function unformatted($match, $state, $pos) {
195        if ( $state == DOKU_LEXER_UNMATCHED ) {
196            $this->_addCall('unformatted',array($match), $pos);
197        }
198        return TRUE;
199    }
200
201    function php($match, $state, $pos) {
202        if ( $state == DOKU_LEXER_UNMATCHED ) {
203            $this->_addCall('php',array($match), $pos);
204        }
205        return TRUE;
206    }
207
208    function html($match, $state, $pos) {
209        if ( $state == DOKU_LEXER_UNMATCHED ) {
210            $this->_addCall('html',array($match), $pos);
211        }
212        return TRUE;
213    }
214
215    function preformatted($match, $state, $pos) {
216        switch ( $state ) {
217            case DOKU_LEXER_ENTER:
218                $ReWriter = & new Doku_Handler_Preformatted($this->CallWriter);
219                $this->CallWriter = & $ReWriter;
220                $this->_addCall('preformatted_start',array(), $pos);
221            break;
222            case DOKU_LEXER_EXIT:
223                $this->_addCall('preformatted_end',array(), $pos);
224                $this->CallWriter->process();
225                $ReWriter = & $this->CallWriter;
226                $this->CallWriter = & $ReWriter->CallWriter;
227            break;
228            case DOKU_LEXER_MATCHED:
229                $this->_addCall('preformatted_newline',array(), $pos);
230            break;
231            case DOKU_LEXER_UNMATCHED:
232                $this->_addCall('preformatted_content',array($match), $pos);
233            break;
234        }
235
236        return TRUE;
237    }
238
239    function file($match, $state, $pos) {
240        if ( $state == DOKU_LEXER_UNMATCHED ) {
241            $this->_addCall('file',array($match), $pos);
242        }
243        return TRUE;
244    }
245
246    function quote($match, $state, $pos) {
247
248        switch ( $state ) {
249
250            case DOKU_LEXER_ENTER:
251                $ReWriter = & new Doku_Handler_Quote($this->CallWriter);
252                $this->CallWriter = & $ReWriter;
253                $this->_addCall('quote_start',array($match), $pos);
254            break;
255
256            case DOKU_LEXER_EXIT:
257                $this->_addCall('quote_end',array(), $pos);
258                $this->CallWriter->process();
259                $ReWriter = & $this->CallWriter;
260                $this->CallWriter = & $ReWriter->CallWriter;
261            break;
262
263            case DOKU_LEXER_MATCHED:
264                $this->_addCall('quote_newline',array($match), $pos);
265            break;
266
267            case DOKU_LEXER_UNMATCHED:
268                $this->_addCall('cdata',array($match), $pos);
269            break;
270
271        }
272
273        return TRUE;
274    }
275
276    function code($match, $state, $pos) {
277        switch ( $state ) {
278            case DOKU_LEXER_UNMATCHED:
279                $matches = preg_split('/>/u',$match,2);
280                $matches[0] = trim($matches[0]);
281                if ( trim($matches[0]) == '' ) {
282                    $matches[0] = NULL;
283                }
284                # $matches[0] contains name of programming language
285                # if available
286                $this->_addCall(
287                        'code',
288                        array($matches[1],$matches[0]),
289                        $pos
290                    );
291            break;
292        }
293        return TRUE;
294    }
295
296    function acronym($match, $state, $pos) {
297        $this->_addCall('acronym',array($match), $pos);
298        return TRUE;
299    }
300
301    function smiley($match, $state, $pos) {
302        $this->_addCall('smiley',array($match), $pos);
303        return TRUE;
304    }
305
306    function wordblock($match, $state, $pos) {
307        $this->_addCall('wordblock',array($match), $pos);
308        return TRUE;
309    }
310
311    function entity($match, $state, $pos) {
312        $this->_addCall('entity',array($match), $pos);
313        return TRUE;
314    }
315
316    function multiplyentity($match, $state, $pos) {
317        preg_match_all('/\d+/',$match,$matches);
318        $this->_addCall('multiplyentity',array($matches[0][0],$matches[0][1]), $pos);
319        return TRUE;
320    }
321
322    function singlequoteopening($match, $state, $pos) {
323        $this->_addCall('singlequoteopening',array(), $pos);
324        return TRUE;
325    }
326
327    function singlequoteclosing($match, $state, $pos) {
328        $this->_addCall('singlequoteclosing',array(), $pos);
329        return TRUE;
330    }
331
332    function doublequoteopening($match, $state, $pos) {
333        $this->_addCall('doublequoteopening',array(), $pos);
334        return TRUE;
335    }
336
337    function doublequoteclosing($match, $state, $pos) {
338        $this->_addCall('doublequoteclosing',array(), $pos);
339        return TRUE;
340    }
341
342    function camelcaselink($match, $state, $pos) {
343        $this->_addCall('camelcaselink',array($match), $pos);
344        return TRUE;
345    }
346
347    /*
348    */
349    function internallink($match, $state, $pos) {
350        // Strip the opening and closing markup
351        $link = preg_replace(array('/^\[\[/','/\]\]$/u'),'',$match);
352
353        // Split title from URL
354        $link = preg_split('/\|/u',$link,2);
355        if ( !isset($link[1]) ) {
356            $link[1] = NULL;
357        } else if ( preg_match('/^\{\{[^\}]+\}\}$/',$link[1]) ) {
358            // If the title is an image, convert it to an array containing the image details
359            $link[1] = Doku_Handler_Parse_Media($link[1]);
360        }
361
362        //decide which kind of link it is
363
364        if ( preg_match('/^[a-zA-Z]+>{1}.+$/u',$link[0]) ) {
365        // Interwiki
366            $interwiki = preg_split('/>/u',$link[0]);
367            $this->_addCall(
368                'interwikilink',
369                array($link[0],$link[1],strtolower($interwiki[0]),$interwiki[1]),
370                $pos
371                );
372        }elseif ( preg_match('/\\\\\\\\[\w.:?\-;,]+?\\\\/u',$link[0]) ) {
373        // Windows Share
374            $this->_addCall(
375                'windowssharelink',
376                array($link[0],$link[1]),
377                $pos
378                );
379        }elseif ( preg_match('#([a-z0-9\-_.]+?)@([\w\-]+\.([\w\-\.]+\.)*[\w]+)#i',$link[0]) ) {
380        // E-Mail
381            $this->_addCall(
382                'emaillink',
383                array($link[0],$link[1]),
384                $pos
385                );
386        }elseif ( preg_match('#^([a-z0-9]+?)://#i',$link[0]) ) {
387        // external link (accepts all protocols)
388            $this->_addCall(
389                    'externallink',
390                    array($link[0],$link[1]),
391                    $pos
392                    );
393        }else{
394        // internal link
395            $this->_addCall(
396                'internallink',
397                array($link[0],$link[1]),
398                $pos
399                );
400        }
401
402        return TRUE;
403    }
404
405    function filelink($match, $state, $pos) {
406        $this->_addCall('filelink',array($match, NULL), $pos);
407        return TRUE;
408    }
409
410    function windowssharelink($match, $state, $pos) {
411        $this->_addCall('windowssharelink',array($match, NULL), $pos);
412        return TRUE;
413    }
414
415    function media($match, $state, $pos) {
416        $p = Doku_Handler_Parse_Media($match);
417
418        $this->_addCall(
419              $p['type'],
420              array($p['src'], $p['title'], $p['align'], $p['width'], $p['height'], $p['cache']),
421              $pos
422             );
423        return TRUE;
424    }
425
426    function rss($match, $state, $pos) {
427        $link = preg_replace(array('/^\{\{rss>/','/\}\}$/'),'',$match);
428        $this->_addCall('rss',array($link),$pos);
429    }
430
431    function externallink($match, $state, $pos) {
432        // Prevent use of multibyte strings in URLs
433        // See: http://www.boingboing.net/2005/02/06/shmoo_group_exploit_.html
434        // Not worried about other charsets so long as page is output as UTF-8
435        /*if ( strlen($match) != utf8_strlen($match) ) {
436            $this->_addCall('cdata',array($match), $pos);
437        } else {*/
438
439            $this->_addCall('externallink',array($match, NULL), $pos);
440        //}
441        return TRUE;
442    }
443
444    function emaillink($match, $state, $pos) {
445        $email = preg_replace(array('/^</','/>$/'),'',$match);
446        $this->_addCall('emaillink',array($email, NULL), $pos);
447        return TRUE;
448    }
449
450    function table($match, $state, $pos) {
451        switch ( $state ) {
452
453            case DOKU_LEXER_ENTER:
454
455                $ReWriter = & new Doku_Handler_Table($this->CallWriter);
456                $this->CallWriter = & $ReWriter;
457
458                $this->_addCall('table_start', array(), $pos);
459                //$this->_addCall('table_row', array(), $pos);
460                if ( trim($match) == '^' ) {
461                    $this->_addCall('tableheader', array(), $pos);
462                } else {
463                    $this->_addCall('tablecell', array(), $pos);
464                }
465            break;
466
467            case DOKU_LEXER_EXIT:
468                $this->_addCall('table_end', array(), $pos);
469                $this->CallWriter->process();
470                $ReWriter = & $this->CallWriter;
471                $this->CallWriter = & $ReWriter->CallWriter;
472            break;
473
474            case DOKU_LEXER_UNMATCHED:
475                if ( trim($match) != '' ) {
476                    $this->_addCall('cdata',array($match), $pos);
477                }
478            break;
479
480            case DOKU_LEXER_MATCHED:
481                if ( preg_match('/.{2}/',$match) ) {
482                    $this->_addCall('table_align', array($match), $pos);
483                } else if ( $match == "\n|" ) {
484                    $this->_addCall('table_row', array(), $pos);
485                    $this->_addCall('tablecell', array(), $pos);
486                } else if ( $match == "\n^" ) {
487                    $this->_addCall('table_row', array(), $pos);
488                    $this->_addCall('tableheader', array(), $pos);
489                } else if ( $match == '|' ) {
490                    $this->_addCall('tablecell', array(), $pos);
491                } else if ( $match == '^' ) {
492                    $this->_addCall('tableheader', array(), $pos);
493                }
494            break;
495        }
496        return TRUE;
497    }
498}
499
500//------------------------------------------------------------------------
501function Doku_Handler_Parse_Media($match) {
502
503    // Strip the opening and closing markup
504    $link = preg_replace(array('/^\{\{/','/\}\}$/u'),'',$match);
505
506    // Split title from URL
507    $link = preg_split('/\|/u',$link,2);
508
509
510    // Check alignment
511    $ralign = (bool)preg_match('/^ /',$link[0]);
512    $lalign = (bool)preg_match('/ $/',$link[0]);
513
514    // Logic = what's that ;)...
515    if ( $lalign & $ralign ) {
516        $align = 'center';
517    } else if ( $ralign ) {
518        $align = 'right';
519    } else if ( $lalign ) {
520        $align = 'left';
521    } else {
522        $align = NULL;
523    }
524
525    // The title...
526    if ( !isset($link[1]) ) {
527        $link[1] = NULL;
528    }
529
530    //remove aligning spaces
531    $link[0] = trim($link[0]);
532
533    //split into src and parameters (using the very last questionmark)
534    $pos = strrpos($link[0], '?');
535    if($pos !== false){
536        $src   = substr($link[0],0,$pos);
537        $param = substr($link[0],$pos+1);
538    }else{
539        $src   = $link[0];
540        $param = '';
541    }
542
543    //parse width and height
544    if(preg_match('#(\d+)(x(\d+))?#i',$param,$size)){
545        ($size[1]) ? $w = $size[1] : $w = NULL;
546        ($size[3]) ? $h = $size[3] : $h = NULL;
547    }
548
549    //get caching command
550    if (preg_match('/(nocache|recache)/i',$param,$cachemode)){
551        $cache = $cachemode[1];
552    }else{
553        $cache = 'cache';
554    }
555
556    // Check whether this is a local or remote image
557    if ( preg_match('#^(https?|ftp)#i',$src) ) {
558        $call = 'externalmedia';
559    } else {
560        $call = 'internalmedia';
561    }
562
563    $params = array(
564        'type'=>$call,
565        'src'=>$src,
566        'title'=>$link[1],
567        'align'=>$align,
568        'width'=>$w,
569        'height'=>$h,
570        'cache'=>$cache,
571    );
572
573    return $params;
574}
575
576//------------------------------------------------------------------------
577class Doku_Handler_CallWriter {
578
579    var $Handler;
580
581    function Doku_Handler_CallWriter(& $Handler) {
582        $this->Handler = & $Handler;
583    }
584
585    function writeCall($call) {
586        $this->Handler->calls[] = $call;
587    }
588
589    function writeCalls($calls) {
590        $this->Handler->calls = array_merge($this->Handler->calls, $calls);
591    }
592}
593
594//------------------------------------------------------------------------
595class Doku_Handler_List {
596
597    var $CallWriter;
598
599    var $calls = array();
600    var $listCalls = array();
601    var $listStack = array();
602
603    function Doku_Handler_List(& $CallWriter) {
604        $this->CallWriter = & $CallWriter;
605    }
606
607    function writeCall($call) {
608        $this->calls[] = $call;
609    }
610
611    // Probably not needed but just in case...
612    function writeCalls($calls) {
613        $this->calls = array_merge($this->calls, $calls);
614        $this->CallWriter->writeCalls($this->calls);
615    }
616
617    //------------------------------------------------------------------------
618    function process() {
619        foreach ( $this->calls as $call ) {
620            switch ($call[0]) {
621                case 'list_item':
622                    $this->listOpen($call);
623                break;
624                case 'list_open':
625                    $this->listStart($call);
626                break;
627                case 'list_close':
628                    $this->listEnd($call);
629                break;
630                default:
631                    $this->listContent($call);
632                break;
633            }
634        }
635
636        $this->CallWriter->writeCalls($this->listCalls);
637    }
638
639    //------------------------------------------------------------------------
640    function listStart($call) {
641        $depth = $this->interpretSyntax($call[1][0], $listType);
642
643        $this->initialDepth = $depth;
644        $this->listStack[] = array($listType, $depth);
645
646        $this->listCalls[] = array('list'.$listType.'_open',array(),$call[2]);
647        $this->listCalls[] = array('listitem_open',array(1),$call[2]);
648        $this->listCalls[] = array('listcontent_open',array(),$call[2]);
649    }
650
651    //------------------------------------------------------------------------
652    function listEnd($call) {
653        $closeContent = TRUE;
654
655        while ( $list = array_pop($this->listStack) ) {
656            if ( $closeContent ) {
657                $this->listCalls[] = array('listcontent_close',array(),$call[2]);
658                $closeContent = FALSE;
659            }
660            $this->listCalls[] = array('listitem_close',array(),$call[2]);
661            $this->listCalls[] = array('list'.$list[0].'_close', array(), $call[2]);
662        }
663    }
664
665    //------------------------------------------------------------------------
666    function listOpen($call) {
667        $depth = $this->interpretSyntax($call[1][0], $listType);
668        $end = end($this->listStack);
669
670        // Not allowed to be shallower than initialDepth
671        if ( $depth < $this->initialDepth ) {
672            $depth = $this->initialDepth;
673        }
674
675        //------------------------------------------------------------------------
676        if ( $depth == $end[1] ) {
677
678            // Just another item in the list...
679            if ( $listType == $end[0] ) {
680                $this->listCalls[] = array('listcontent_close',array(),$call[2]);
681                $this->listCalls[] = array('listitem_close',array(),$call[2]);
682                $this->listCalls[] = array('listitem_open',array($depth-1),$call[2]);
683                $this->listCalls[] = array('listcontent_open',array(),$call[2]);
684
685            // Switched list type...
686            } else {
687
688                $this->listCalls[] = array('listcontent_close',array(),$call[2]);
689                $this->listCalls[] = array('listitem_close',array(),$call[2]);
690                $this->listCalls[] = array('list'.$end[0].'_close', array(), $call[2]);
691                $this->listCalls[] = array('list'.$listType.'_open', array(), $call[2]);
692                $this->listCalls[] = array('listitem_open', array($depth-1), $call[2]);
693                $this->listCalls[] = array('listcontent_open',array(),$call[2]);
694
695                array_pop($this->listStack);
696                $this->listStack[] = array($listType, $depth);
697            }
698
699        //------------------------------------------------------------------------
700        // Getting deeper...
701        } else if ( $depth > $end[1] ) {
702
703            $this->listCalls[] = array('listcontent_close',array(),$call[2]);
704            $this->listCalls[] = array('list'.$listType.'_open', array(), $call[2]);
705            $this->listCalls[] = array('listitem_open', array($depth-1), $call[2]);
706            $this->listCalls[] = array('listcontent_open',array(),$call[2]);
707
708            $this->listStack[] = array($listType, $depth);
709
710        //------------------------------------------------------------------------
711        // Getting shallower ( $depth < $end[1] )
712        } else {
713            $this->listCalls[] = array('listcontent_close',array(),$call[2]);
714            $this->listCalls[] = array('listitem_close',array(),$call[2]);
715            $this->listCalls[] = array('list'.$end[0].'_close',array(),$call[2]);
716
717            // Throw away the end - done
718            array_pop($this->listStack);
719
720            while (1) {
721                $end = end($this->listStack);
722
723                if ( $end[1] <= $depth ) {
724
725                    // Normalize depths
726                    $depth = $end[1];
727
728                    $this->listCalls[] = array('listitem_close',array(),$call[2]);
729
730                    if ( $end[0] == $listType ) {
731                        $this->listCalls[] = array('listitem_open',array($depth-1),$call[2]);
732                        $this->listCalls[] = array('listcontent_open',array(),$call[2]);
733
734                    } else {
735                        // Switching list type...
736                        $this->listCalls[] = array('list'.$end[0].'_close', array(), $call[2]);
737                        $this->listCalls[] = array('list'.$listType.'_open', array(), $call[2]);
738                        $this->listCalls[] = array('listitem_open', array($depth-1), $call[2]);
739                        $this->listCalls[] = array('listcontent_open',array(),$call[2]);
740
741                        array_pop($this->listStack);
742                        $this->listStack[] = array($listType, $depth);
743                    }
744
745                    break;
746
747                // Haven't dropped down far enough yet.... ( $end[1] > $depth )
748                } else {
749
750                    $this->listCalls[] = array('listitem_close',array(),$call[2]);
751                    $this->listCalls[] = array('list'.$end[0].'_close',array(),$call[2]);
752
753                    array_pop($this->listStack);
754
755                }
756
757            }
758
759        }
760    }
761
762    //------------------------------------------------------------------------
763    function listContent($call) {
764        $this->listCalls[] = $call;
765    }
766
767    //------------------------------------------------------------------------
768    function interpretSyntax($match, & $type) {
769        if ( substr($match,-1) == '*' ) {
770            $type = 'u';
771        } else {
772            $type = 'o';
773        }
774        return count(explode('  ',str_replace("\t",'  ',$match)));
775    }
776}
777
778//------------------------------------------------------------------------
779class Doku_Handler_Preformatted {
780
781    var $CallWriter;
782
783    var $calls = array();
784    var $pos;
785    var $text ='';
786
787
788
789    function Doku_Handler_Preformatted(& $CallWriter) {
790        $this->CallWriter = & $CallWriter;
791    }
792
793    function writeCall($call) {
794        $this->calls[] = $call;
795    }
796
797    // Probably not needed but just in case...
798    function writeCalls($calls) {
799        $this->calls = array_merge($this->calls, $calls);
800        $this->CallWriter->writeCalls($this->calls);
801    }
802
803    function process() {
804        foreach ( $this->calls as $call ) {
805            switch ($call[0]) {
806                case 'preformatted_start':
807                    $this->pos = $call[2];
808                break;
809                case 'preformatted_newline':
810                    $this->text .= "\n";
811                break;
812                case 'preformatted_content':
813                    $this->text .= $call[1][0];
814                break;
815                case 'preformatted_end':
816                    $this->CallWriter->writeCall(array('preformatted',array($this->text),$this->pos));
817                break;
818            }
819        }
820    }
821}
822
823//------------------------------------------------------------------------
824class Doku_Handler_Quote {
825
826    var $CallWriter;
827
828    var $calls = array();
829
830    var $quoteCalls = array();
831
832    function Doku_Handler_Quote(& $CallWriter) {
833        $this->CallWriter = & $CallWriter;
834    }
835
836    function writeCall($call) {
837        $this->calls[] = $call;
838    }
839
840    // Probably not needed but just in case...
841    function writeCalls($calls) {
842        $this->calls = array_merge($this->calls, $calls);
843        $this->CallWriter->writeCalls($this->calls);
844    }
845
846    function process() {
847
848        $quoteDepth = 1;
849
850        foreach ( $this->calls as $call ) {
851            switch ($call[0]) {
852
853                case 'quote_start':
854
855                    $this->quoteCalls[] = array('quote_open',array(),$call[2]);
856
857                case 'quote_newline':
858
859                    $quoteLength = $this->getDepth($call[1][0]);
860
861                    if ( $quoteLength > $quoteDepth ) {
862                        $quoteDiff = $quoteLength - $quoteDepth;
863                        for ( $i = 1; $i <= $quoteDiff; $i++ ) {
864                            $this->quoteCalls[] = array('quote_open',array(),$call[2]);
865                        }
866                    } else if ( $quoteLength < $quoteDepth ) {
867                        $quoteDiff = $quoteDepth - $quoteLength;
868                        for ( $i = 1; $i <= $quoteDiff; $i++ ) {
869                            $this->quoteCalls[] = array('quote_close',array(),$call[2]);
870                        }
871                    }
872
873                    $quoteDepth = $quoteLength;
874
875                break;
876
877                case 'quote_end':
878
879                    if ( $quoteDepth > 1 ) {
880                        $quoteDiff = $quoteDepth - 1;
881                        for ( $i = 1; $i <= $quoteDiff; $i++ ) {
882                            $this->quoteCalls[] = array('quote_close',array(),$call[2]);
883                        }
884                    }
885
886                    $this->quoteCalls[] = array('quote_close',array(),$call[2]);
887
888                    $this->CallWriter->writeCalls($this->quoteCalls);
889                break;
890
891                default:
892                    $this->quoteCalls[] = $call;
893                break;
894            }
895        }
896    }
897
898    function getDepth($marker) {
899        preg_match('/>{1,}/', $marker, $matches);
900        $quoteLength = strlen($matches[0]);
901        return $quoteLength;
902    }
903}
904
905//------------------------------------------------------------------------
906class Doku_Handler_Table {
907
908    var $CallWriter;
909
910    var $calls = array();
911    var $tableCalls = array();
912    var $maxCols = 0;
913    var $maxRows = 1;
914    var $currentCols = 0;
915    var $firstCell = FALSE;
916    var $lastCellType = 'tablecell';
917
918    function Doku_Handler_Table(& $CallWriter) {
919        $this->CallWriter = & $CallWriter;
920    }
921
922    function writeCall($call) {
923        $this->calls[] = $call;
924    }
925
926    // Probably not needed but just in case...
927    function writeCalls($calls) {
928        $this->calls = array_merge($this->calls, $calls);
929        $this->CallWriter->writeCalls($this->calls);
930    }
931
932    //------------------------------------------------------------------------
933    function process() {
934        foreach ( $this->calls as $call ) {
935            switch ( $call[0] ) {
936                case 'table_start':
937                    $this->tableStart($call);
938                break;
939                case 'table_row':
940                    $this->tableRowClose(array('tablerow_close',$call[1],$call[2]));
941                    $this->tableRowOpen(array('tablerow_open',$call[1],$call[2]));
942                break;
943                case 'tableheader':
944                case 'tablecell':
945                    $this->tableCell($call);
946                break;
947                case 'table_end':
948                    $this->tableRowClose(array('tablerow_close',$call[1],$call[2]));
949                    $this->tableEnd($call);
950                break;
951                default:
952                    $this->tableDefault($call);
953                break;
954            }
955        }
956        $this->CallWriter->writeCalls($this->tableCalls);
957    }
958
959    function tableStart($call) {
960        $this->tableCalls[] = array('table_open',array(),$call[2]);
961        $this->tableCalls[] = array('tablerow_open',array(),$call[2]);
962        $this->firstCell = TRUE;
963    }
964
965    function tableEnd($call) {
966        $this->tableCalls[] = array('table_close',array(),$call[2]);
967        $this->finalizeTable();
968    }
969
970    function tableRowOpen($call) {
971        $this->tableCalls[] = $call;
972        $this->currentCols = 0;
973        $this->firstCell = TRUE;
974        $this->lastCellType = 'tablecell';
975        $this->maxRows++;
976    }
977
978    function tableRowClose($call) {
979        // Strip off final cell opening and anything after it
980        while ( $discard = array_pop($this->tableCalls ) ) {
981
982            if ( $discard[0] == 'tablecell_open' || $discard[0] == 'tableheader_open') {
983
984                // Its a spanning element - put it back and close it
985                if ( $discard[1][0] > 1 ) {
986
987                    $this->tableCalls[] = $discard;
988                    if ( strstr($discard[0],'cell') ) {
989                        $name = 'tablecell';
990                    } else {
991                        $name = 'tableheader';
992                    }
993                    $this->tableCalls[] = array($name.'_close',array(),$call[2]);
994                }
995
996                break;
997            }
998        }
999        $this->tableCalls[] = $call;
1000
1001        if ( $this->currentCols > $this->maxCols ) {
1002            $this->maxCols = $this->currentCols;
1003        }
1004    }
1005
1006    function tableCell($call) {
1007        if ( !$this->firstCell ) {
1008
1009            // Increase the span
1010            $lastCall = end($this->tableCalls);
1011
1012            // A cell call which follows an open cell means an empty cell so span
1013            if ( $lastCall[0] == 'tablecell_open' || $lastCall[0] == 'tableheader_open' ) {
1014                 $this->tableCalls[] = array('colspan',array(),$call[2]);
1015
1016            }
1017
1018            $this->tableCalls[] = array($this->lastCellType.'_close',array(),$call[2]);
1019            $this->tableCalls[] = array($call[0].'_open',array(1,NULL),$call[2]);
1020            $this->lastCellType = $call[0];
1021
1022        } else {
1023
1024            $this->tableCalls[] = array($call[0].'_open',array(1,NULL),$call[2]);
1025            $this->lastCellType = $call[0];
1026            $this->firstCell = FALSE;
1027
1028        }
1029
1030        $this->currentCols++;
1031    }
1032
1033    function tableDefault($call) {
1034        $this->tableCalls[] = $call;
1035    }
1036
1037    function finalizeTable() {
1038
1039        // Add the max cols and rows to the table opening
1040        if ( $this->tableCalls[0][0] == 'table_open' ) {
1041            // Adjust to num cols not num col delimeters
1042            $this->tableCalls[0][1][] = $this->maxCols - 1;
1043            $this->tableCalls[0][1][] = $this->maxRows;
1044        } else {
1045            trigger_error('First element in table call list is not table_open');
1046        }
1047
1048        $lastRow = 0;
1049        $lastCell = 0;
1050        $toDelete = array();
1051
1052        // Look for the colspan elements and increment the colspan on the
1053        // previous non-empty opening cell. Once done, delete all the cells
1054        // that contain colspans
1055        foreach ( $this->tableCalls as $key => $call ) {
1056
1057            if ( $call[0] == 'tablerow_open' ) {
1058
1059                $lastRow = $key;
1060
1061            } else if ( $call[0] == 'tablecell_open' || $call[0] == 'tableheader_open' ) {
1062
1063                $lastCell = $key;
1064
1065            } else if ( $call[0] == 'table_align' ) {
1066
1067                // If the previous element was a cell open, align right
1068                if ( $this->tableCalls[$key-1][0] == 'tablecell_open' || $this->tableCalls[$key-1][0] == 'tableheader_open' ) {
1069                    $this->tableCalls[$key-1][1][1] = 'right';
1070
1071                // If the next element if the close of an element, align either center or left
1072                } else if ( $this->tableCalls[$key+1][0] == 'tablecell_close' || $this->tableCalls[$key+1][0] == 'tableheader_close' ) {
1073                    if ( $this->tableCalls[$lastCell][1][1] == 'right' ) {
1074                        $this->tableCalls[$lastCell][1][1] = 'center';
1075                    } else {
1076                        $this->tableCalls[$lastCell][1][1] = 'left';
1077                    }
1078
1079                }
1080
1081                // Now convert the whitespace back to cdata
1082                $this->tableCalls[$key][0] = 'cdata';
1083
1084            } else if ( $call[0] == 'colspan' ) {
1085
1086                $this->tableCalls[$key-1][1][0] = FALSE;
1087
1088                for($i = $key-2; $i > $lastRow; $i--) {
1089
1090                    if ( $this->tableCalls[$i][0] == 'tablecell_open' || $this->tableCalls[$i][0] == 'tableheader_open' ) {
1091
1092                        if ( FALSE !== $this->tableCalls[$i][1][0] ) {
1093                            $this->tableCalls[$i][1][0]++;
1094                            break;
1095                        }
1096
1097
1098                    }
1099                }
1100
1101                $toDelete[] = $key-1;
1102                $toDelete[] = $key;
1103                $toDelete[] = $key+1;
1104            }
1105        }
1106
1107        foreach ( $toDelete as $delete ) {
1108            unset($this->tableCalls[$delete]);
1109        }
1110
1111        $this->tableCalls = array_values($this->tableCalls);
1112    }
1113}
1114
1115//------------------------------------------------------------------------
1116class Doku_Handler_Section {
1117
1118    function process($calls) {
1119
1120        $sectionCalls = array();
1121        $inSection = FALSE;
1122
1123        foreach ( $calls as $call ) {
1124
1125            if ( $call[0] == 'header' ) {
1126
1127                if ( $inSection ) {
1128                    $sectionCalls[] = array('section_close',array(), $call[2]);
1129                }
1130
1131                $sectionCalls[] = $call;
1132                $sectionCalls[] = array('section_open',array($call[1][1]), $call[2]);
1133                $inSection = TRUE;
1134
1135            } else {
1136                $sectionCalls[] = $call;
1137            }
1138        }
1139
1140        if ( $inSection ) {
1141            $sectionCalls[] = array('section_close',array(), $call[2]);
1142        }
1143
1144        return $sectionCalls;
1145    }
1146
1147}
1148
1149//------------------------------------------------------------------------
1150class Doku_Handler_Block {
1151
1152    var $calls = array();
1153
1154    var $blockStack = array();
1155
1156    var $inParagraph = FALSE;
1157    var $atStart = TRUE;
1158    var $skipEolKey = -1;
1159
1160    // Blocks don't contain linefeeds
1161    var $blockOpen = array(
1162            'header',
1163            'listu_open','listo_open','listitem_open',
1164            'table_open','tablerow_open','tablecell_open','tableheader_open',
1165            'quote_open',
1166            'section_open', // Needed to prevent p_open between header and section_open
1167            'code','file','php','html','hr','preformatted',
1168        );
1169
1170    var $blockClose = array(
1171            'header',
1172            'listu_close','listo_close','listitem_close',
1173            'table_close','tablerow_close','tablecell_close','tableheader_close',
1174            'quote_close',
1175            'section_close', // Needed to prevent p_close after section_close
1176            'code','file','php','html','hr','preformatted',
1177        );
1178
1179    // Stacks can contain linefeeds
1180    var $stackOpen = array(
1181        'footnote_open','section_open',
1182        );
1183
1184    var $stackClose = array(
1185        'footnote_close','section_close',
1186        );
1187
1188    function closeParagraph($pos){
1189        // look back if there was any content - we don't want empty paragraphs
1190        $content = '';
1191        for($i=count($this->calls)-1; $i>=0; $i--){
1192            if($this->calls[$i][0] == 'p_open'){
1193                break;
1194            }elseif($this->calls[$i][0] == 'cdata'){
1195                $content .= $this->calls[$i][1][0];
1196            }else{
1197                $content = 'found markup';
1198                break;
1199            }
1200        }
1201
1202        if(trim($content)==''){
1203            //remove the whole paragraph
1204            array_splice($this->calls,$i);
1205        }else{
1206            $this->calls[] = array('p_close',array(), $pos);
1207        }
1208    }
1209
1210    function process($calls) {
1211        foreach ( $calls as $key => $call ) {
1212
1213            // Process blocks which are stack like... (contain linefeeds)
1214            if ( in_array($call[0],$this->stackOpen ) ) {
1215                /*
1216                if ( $this->atStart ) {
1217                    $this->calls[] = array('p_open',array(), $call[2]);
1218                    $this->atStart = FALSE;
1219                    $this->inParagraph = TRUE;
1220                }
1221                */
1222                $this->calls[] = $call;
1223
1224                // Hack - footnotes shouldn't immediately contain a p_open
1225                if ( $call[0] != 'footnote_open' ) {
1226                    $this->addToStack();
1227                } else {
1228                    $this->addToStack(FALSE);
1229                }
1230                continue;
1231            }
1232
1233            if ( in_array($call[0],$this->stackClose ) ) {
1234
1235                if ( $this->inParagraph ) {
1236                    //$this->calls[] = array('p_close',array(), $call[2]);
1237                    $this->closeParagraph($call[2]);
1238                }
1239                $this->calls[] = $call;
1240                $this->removeFromStack();
1241                continue;
1242            }
1243
1244            if ( !$this->atStart ) {
1245
1246                if ( $call[0] == 'eol' ) {
1247
1248
1249                    /* XXX
1250                    if ( $this->inParagraph ) {
1251                        $this->calls[] = array('p_close',array(), $call[2]);
1252                    }
1253                    $this->calls[] = array('p_open',array(), $call[2]);
1254                    $this->inParagraph = TRUE;
1255                    */
1256
1257                    # Check this isn't an eol instruction to skip...
1258                    if ( $this->skipEolKey != $key ) {
1259                         # Look to see if the next instruction is an EOL
1260                        if ( isset($calls[$key+1]) && $calls[$key+1][0] == 'eol' ) {
1261
1262                            if ( $this->inParagraph ) {
1263                                //$this->calls[] = array('p_close',array(), $call[2]);
1264                                $this->closeParagraph($call[2]);
1265                            }
1266
1267                            $this->calls[] = array('p_open',array(), $call[2]);
1268                            $this->inParagraph = TRUE;
1269
1270
1271                            # Mark the next instruction for skipping
1272                            $this->skipEolKey = $key+1;
1273
1274                        }else{
1275                            //if this is just a single eol make a space from it
1276                            $this->calls[] = array('cdata',array(" "), $call[2]);
1277                        }
1278                    }
1279
1280
1281                } else {
1282
1283                    $storeCall = TRUE;
1284
1285                    if ( $this->inParagraph && in_array($call[0], $this->blockOpen) ) {
1286                        //$this->calls[] = array('p_close',array(), $call[2]);
1287                        $this->closeParagraph($call[2]);
1288                        $this->inParagraph = FALSE;
1289                        $this->calls[] = $call;
1290                        $storeCall = FALSE;
1291                    }
1292
1293                    if ( in_array($call[0], $this->blockClose) ) {
1294                        if ( $this->inParagraph ) {
1295                            //$this->calls[] = array('p_close',array(), $call[2]);
1296                            $this->closeParagraph($call[2]);
1297                            $this->inParagraph = FALSE;
1298                        }
1299                        if ( $storeCall ) {
1300                            $this->calls[] = $call;
1301                            $storeCall = FALSE;
1302                        }
1303
1304                        // This really sucks and suggests this whole class sucks but...
1305                        if ( isset($calls[$key+1])
1306                            &&
1307                            !in_array($calls[$key+1][0], $this->blockOpen)
1308                            &&
1309                            !in_array($calls[$key+1][0], $this->blockClose)
1310                            ) {
1311
1312                            $this->calls[] = array('p_open',array(), $call[2]);
1313                            $this->inParagraph = TRUE;
1314                        }
1315                    }
1316
1317                    if ( $storeCall ) {
1318                        $this->calls[] = $call;
1319                    }
1320
1321                }
1322
1323
1324            } else {
1325
1326                // Unless there's already a block at the start, start a paragraph
1327                if ( !in_array($call[0],$this->blockOpen) ) {
1328                    $this->calls[] = array('p_open',array(), $call[2]);
1329                    if ( $call[0] != 'eol' ) {
1330                        $this->calls[] = $call;
1331                    }
1332                    $this->atStart = FALSE;
1333                    $this->inParagraph = TRUE;
1334                } else {
1335                    $this->calls[] = $call;
1336                    $this->atStart = FALSE;
1337                }
1338
1339            }
1340
1341        }
1342
1343        if ( $this->inParagraph ) {
1344            if ( $call[0] == 'p_open' ) {
1345                // Ditch the last call
1346                array_pop($this->calls);
1347            } else if ( !in_array($call[0], $this->blockClose) ) {
1348                //$this->calls[] = array('p_close',array(), $call[2]);
1349                $this->closeParagraph($call[2]);
1350            } else {
1351                $last_call = array_pop($this->calls);
1352                //$this->calls[] = array('p_close',array(), $call[2]);
1353                $this->closeParagraph($call[2]);
1354                $this->calls[] = $last_call;
1355            }
1356        }
1357
1358        return $this->calls;
1359    }
1360
1361    function addToStack($newStart = TRUE) {
1362        $this->blockStack[] = array($this->atStart, $this->inParagraph);
1363        $this->atStart = $newStart;
1364        $this->inParagraph = FALSE;
1365    }
1366
1367    function removeFromStack() {
1368        $state = array_pop($this->blockStack);
1369        $this->atStart = $state[0];
1370        $this->inParagraph = $state[1];
1371    }
1372}
1373//------------------------------------------------------------------------
1374define('DOKU_TOC_OPEN',1);
1375define('DOKU_TOCBRANCH_OPEN',2);
1376define('DOKU_TOCITEM_OPEN',3);
1377define('DOKU_TOC_ELEMENT',4);
1378define('DOKU_TOCITEM_CLOSE',5);
1379define('DOKU_TOCBRANCH_CLOSE',6);
1380define('DOKU_TOC_CLOSE',7);
1381
1382class Doku_Handler_Toc {
1383
1384    var $calls = array();
1385    var $tocStack = array();
1386    var $toc = array();
1387    var $numHeaders = 0;
1388
1389    function process($calls) {
1390      #FIXME can this be done better?
1391      global $conf;
1392
1393        foreach ( $calls as $call ) {
1394            if ( $call[0] == 'header' && $call[1][1] <= $conf['maxtoclevel'] ) {
1395                $this->numHeaders++;
1396                $this->addToToc($call);
1397            }
1398            $this->calls[] = $call;
1399        }
1400
1401        // Complete the table of contents then prepend to the calls
1402        $this->finalizeToc($call);
1403        return $this->calls;
1404    }
1405
1406    function addToToc($call) {
1407
1408        $depth = $call[1][1];
1409
1410        // If it's the opening item...
1411        if ( count ( $this->toc) == 0 ) {
1412
1413            $this->addTocCall($call, DOKU_TOC_OPEN);
1414
1415            for ( $i = 1; $i <= $depth; $i++ ) {
1416
1417                $this->addTocCall(array($call[0],array($call[1][0],$i),$call[2]), DOKU_TOCBRANCH_OPEN);
1418
1419                if ( $i != $depth ) {
1420                    $this->addTocCall(array($call[0],array($call[1][0], $i, '', TRUE),$call[2]), DOKU_TOCITEM_OPEN);
1421                } else {
1422                    $this->addTocCall(array($call[0],array($call[1][0], $i),$call[2]), DOKU_TOCITEM_OPEN);
1423                    $this->addTocCall(array($call[0],array($call[1][0], $i),$call[2]), DOKU_TOC_ELEMENT);
1424                }
1425
1426                $this->tocStack[] = $i;
1427
1428            }
1429            return;
1430        }
1431
1432        $currentDepth = end($this->tocStack);
1433        $initialDepth = $currentDepth;
1434
1435        // Create new branches as needed
1436        if ( $depth > $currentDepth ) {
1437
1438            for ($i = $currentDepth+1; $i <= $depth; $i++ ) {
1439                $this->addTocCall(array($call[0],array($call[1][0],$i),$call[2]), DOKU_TOCBRANCH_OPEN);
1440                // It's just a filler
1441                if ( $i != $depth ) {
1442                    $this->addTocCall(array($call[0],array($call[1][0], $i, '', TRUE),$call[2]), DOKU_TOCITEM_OPEN);
1443                } else {
1444                    $this->addTocCall(array($call[0],array($call[1][0], $i),$call[2]), DOKU_TOCITEM_OPEN);
1445                }
1446                $this->tocStack[] = $i;
1447            }
1448
1449            $currentDepth = $i-1;
1450
1451        }
1452
1453        // Going down
1454        if ( $depth < $currentDepth ) {
1455            for ( $i = $currentDepth; $i >= $depth; $i-- ) {
1456                if ( $i != $depth ) {
1457                    array_pop($this->tocStack);
1458                    $this->addTocCall(array($call[0],array($call[1][0],$i),$call[2]), DOKU_TOCITEM_CLOSE);
1459                    $this->addTocCall(array($call[0],array($call[1][0],$i),$call[2]), DOKU_TOCBRANCH_CLOSE);
1460                } else {
1461                    $this->addTocCall(array($call[0],array($call[1][0],$i),$call[2]), DOKU_TOCITEM_CLOSE);
1462                    $this->addTocCall(array($call[0],array($call[1][0],$i),$call[2]), DOKU_TOCITEM_OPEN);
1463                    $this->addTocCall($call, DOKU_TOC_ELEMENT);
1464                    return;
1465                }
1466            }
1467        }
1468
1469        if ( $depth == $initialDepth ) {
1470            $this->addTocCall($call, DOKU_TOCITEM_CLOSE);
1471            $this->addTocCall($call, DOKU_TOCITEM_OPEN);
1472        }
1473
1474        $this->addTocCall($call, DOKU_TOC_ELEMENT);
1475
1476
1477   }
1478
1479    function addTocCall($call, $type) {
1480        switch ( $type ) {
1481            case DOKU_TOC_OPEN:
1482                $this->toc[] = array('toc_open',array(),$call[2]);
1483            break;
1484
1485            case DOKU_TOCBRANCH_OPEN:
1486                $this->toc[] = array('tocbranch_open',array($call[1][1]),$call[2]);
1487            break;
1488
1489            case DOKU_TOCITEM_OPEN:
1490                if ( isset( $call[1][3] ) ) {
1491                    $this->toc[] = array('tocitem_open',array($call[1][1], TRUE),$call[2]);
1492                } else {
1493                    $this->toc[] = array('tocitem_open',array($call[1][1]),$call[2]);
1494                }
1495            break;
1496
1497            case DOKU_TOC_ELEMENT:
1498                $this->toc[] = array('tocelement',array($call[1][1],$call[1][0]),$call[2]);
1499            break;
1500
1501            case DOKU_TOCITEM_CLOSE:
1502                $this->toc[] = array('tocitem_close',array($call[1][1]),$call[2]);
1503            break;
1504
1505            case DOKU_TOCBRANCH_CLOSE:
1506                $this->toc[] = array('tocbranch_close',array($call[1][1]),$call[2]);
1507            break;
1508
1509            case DOKU_TOC_CLOSE:
1510                if ( count($this->toc) > 0 ) {
1511                    $this->toc[] = array('toc_close',array(),$call[2]);
1512                }
1513            break;
1514        }
1515    }
1516
1517    function finalizeToc($call) {
1518        global $conf;
1519        if ( $this->numHeaders < $conf['maxtoclevel'] ) {
1520            return;
1521        }
1522        if ( count ($this->tocStack) > 0 ) {
1523            while ( NULL !== ($toc = array_pop($this->tocStack)) ) {
1524                $this->addTocCall(array($call[0],array('',$toc),$call[2]), DOKU_TOCITEM_CLOSE);
1525                $this->addTocCall(array($call[0],array('',$toc),$call[2]), DOKU_TOCBRANCH_CLOSE);
1526            }
1527        }
1528        $this->addTocCall($call, DOKU_TOC_CLOSE);
1529        $this->calls = array_merge($this->toc, $this->calls);
1530    }
1531
1532}
1533
1534
1535//Setup VIM: ex: et ts=4 enc=utf-8 :
1536