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