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