xref: /dokuwiki/inc/parser/handler.php (revision bc3b6aec0f5bdef988488010807a94bee0808426)
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 don't contain linefeeds
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 linefeeds
1218    var $stackOpen = array(
1219        'footnote_open','section_open',
1220        );
1221
1222    var $stackClose = array(
1223        'footnote_close','section_close',
1224        );
1225
1226    /**
1227     * Close a paragraph if needed
1228     *
1229     * This function makes sure there are no empty paragraphs on the stack
1230     *
1231     * @author Andreas Gohr <andi@splitbrain.org>
1232     */
1233    function closeParagraph($pos){
1234        // look back if there was any content - we don't want empty paragraphs
1235        $content = '';
1236        for($i=count($this->calls)-1; $i>=0; $i--){
1237            if($this->calls[$i][0] == 'p_open'){
1238                break;
1239            }elseif($this->calls[$i][0] == 'cdata'){
1240                $content .= $this->calls[$i][1][0];
1241            }else{
1242                $content = 'found markup';
1243                break;
1244            }
1245        }
1246
1247        if(trim($content)==''){
1248            //remove the whole paragraph
1249            array_splice($this->calls,$i);
1250        }else{
1251            $this->calls[] = array('p_close',array(), $pos);
1252        }
1253    }
1254
1255    /**
1256     * Processes the whole instruction stack to open and close paragraphs
1257     *
1258     * @author Harry Fuecks <hfuecks@gmail.com>
1259     * @author Andreas Gohr <andi@splitbrain.org>
1260     * @todo   This thing is really messy and should be rewritten
1261     */
1262    function process($calls) {
1263        foreach ( $calls as $key => $call ) {
1264
1265            // Process blocks which are stack like... (contain linefeeds)
1266            if ( in_array($call[0],$this->stackOpen ) ) {
1267                /*
1268                if ( $this->atStart ) {
1269                    $this->calls[] = array('p_open',array(), $call[2]);
1270                    $this->atStart = FALSE;
1271                    $this->inParagraph = TRUE;
1272                }
1273                */
1274                $this->calls[] = $call;
1275
1276                // Hack - footnotes shouldn't immediately contain a p_open
1277                if ( $call[0] != 'footnote_open' ) {
1278                    $this->addToStack();
1279                } else {
1280                    $this->addToStack(FALSE);
1281                }
1282                continue;
1283            }
1284
1285            if ( in_array($call[0],$this->stackClose ) ) {
1286
1287                if ( $this->inParagraph ) {
1288                    //$this->calls[] = array('p_close',array(), $call[2]);
1289                    $this->closeParagraph($call[2]);
1290                }
1291                $this->calls[] = $call;
1292                $this->removeFromStack();
1293                continue;
1294            }
1295
1296            if ( !$this->atStart ) {
1297
1298                if ( $call[0] == 'eol' ) {
1299
1300
1301                    /* XXX
1302                    if ( $this->inParagraph ) {
1303                        $this->calls[] = array('p_close',array(), $call[2]);
1304                    }
1305                    $this->calls[] = array('p_open',array(), $call[2]);
1306                    $this->inParagraph = TRUE;
1307                    */
1308
1309                    # Check this isn't an eol instruction to skip...
1310                    if ( $this->skipEolKey != $key ) {
1311                         # Look to see if the next instruction is an EOL
1312                        if ( isset($calls[$key+1]) && $calls[$key+1][0] == 'eol' ) {
1313
1314                            if ( $this->inParagraph ) {
1315                                //$this->calls[] = array('p_close',array(), $call[2]);
1316                                $this->closeParagraph($call[2]);
1317                            }
1318
1319                            $this->calls[] = array('p_open',array(), $call[2]);
1320                            $this->inParagraph = TRUE;
1321
1322
1323                            # Mark the next instruction for skipping
1324                            $this->skipEolKey = $key+1;
1325
1326                        }else{
1327                            //if this is just a single eol make a space from it
1328                            $this->calls[] = array('cdata',array(" "), $call[2]);
1329                        }
1330                    }
1331
1332
1333                } else {
1334
1335                    $storeCall = TRUE;
1336
1337                    if ( $this->inParagraph && in_array($call[0], $this->blockOpen) ) {
1338                        //$this->calls[] = array('p_close',array(), $call[2]);
1339                        $this->closeParagraph($call[2]);
1340                        $this->inParagraph = FALSE;
1341                        $this->calls[] = $call;
1342                        $storeCall = FALSE;
1343                    }
1344
1345                    if ( in_array($call[0], $this->blockClose) ) {
1346                        if ( $this->inParagraph ) {
1347                            //$this->calls[] = array('p_close',array(), $call[2]);
1348                            $this->closeParagraph($call[2]);
1349                            $this->inParagraph = FALSE;
1350                        }
1351                        if ( $storeCall ) {
1352                            $this->calls[] = $call;
1353                            $storeCall = FALSE;
1354                        }
1355
1356                        // This really sucks and suggests this whole class sucks but...
1357                        if ( isset($calls[$key+1])
1358                            &&
1359                            !in_array($calls[$key+1][0], $this->blockOpen)
1360                            &&
1361                            !in_array($calls[$key+1][0], $this->blockClose)
1362                            ) {
1363
1364                            $this->calls[] = array('p_open',array(), $call[2]);
1365                            $this->inParagraph = TRUE;
1366                        }
1367                    }
1368
1369                    if ( $storeCall ) {
1370                        $this->calls[] = $call;
1371                    }
1372
1373                }
1374
1375
1376            } else {
1377
1378                // Unless there's already a block at the start, start a paragraph
1379                if ( !in_array($call[0],$this->blockOpen) ) {
1380                    $this->calls[] = array('p_open',array(), $call[2]);
1381                    if ( $call[0] != 'eol' ) {
1382                        $this->calls[] = $call;
1383                    }
1384                    $this->atStart = FALSE;
1385                    $this->inParagraph = TRUE;
1386                } else {
1387                    $this->calls[] = $call;
1388                    $this->atStart = FALSE;
1389                }
1390
1391            }
1392
1393        }
1394
1395        if ( $this->inParagraph ) {
1396            if ( $call[0] == 'p_open' ) {
1397                // Ditch the last call
1398                array_pop($this->calls);
1399            } else if ( !in_array($call[0], $this->blockClose) ) {
1400                //$this->calls[] = array('p_close',array(), $call[2]);
1401                $this->closeParagraph($call[2]);
1402            } else {
1403                $last_call = array_pop($this->calls);
1404                //$this->calls[] = array('p_close',array(), $call[2]);
1405                $this->closeParagraph($call[2]);
1406                $this->calls[] = $last_call;
1407            }
1408        }
1409
1410        return $this->calls;
1411    }
1412
1413    function addToStack($newStart = TRUE) {
1414        $this->blockStack[] = array($this->atStart, $this->inParagraph);
1415        $this->atStart = $newStart;
1416        $this->inParagraph = FALSE;
1417    }
1418
1419    function removeFromStack() {
1420        $state = array_pop($this->blockStack);
1421        $this->atStart = $state[0];
1422        $this->inParagraph = $state[1];
1423    }
1424}
1425
1426//------------------------------------------------------------------------
1427define('DOKU_TOC_OPEN',1);
1428define('DOKU_TOCBRANCH_OPEN',2);
1429define('DOKU_TOCITEM_OPEN',3);
1430define('DOKU_TOC_ELEMENT',4);
1431define('DOKU_TOCITEM_CLOSE',5);
1432define('DOKU_TOCBRANCH_CLOSE',6);
1433define('DOKU_TOC_CLOSE',7);
1434
1435class Doku_Handler_Toc {
1436
1437    var $calls = array();
1438    var $tocStack = array();
1439    var $toc = array();
1440    var $numHeaders = 0;
1441
1442    function process($calls) {
1443      #FIXME can this be done better?
1444      global $conf;
1445
1446        foreach ( $calls as $call ) {
1447            if ( $call[0] == 'header' && $call[1][1] <= $conf['maxtoclevel'] ) {
1448                $this->numHeaders++;
1449                $this->addToToc($call);
1450            }
1451            $this->calls[] = $call;
1452        }
1453
1454        // Complete the table of contents then prepend to the calls
1455        $this->finalizeToc($call);
1456        return $this->calls;
1457    }
1458
1459    function addToToc($call) {
1460
1461        $depth = $call[1][1];
1462
1463        // If it's the opening item...
1464        if ( count ( $this->toc) == 0 ) {
1465
1466            $this->addTocCall($call, DOKU_TOC_OPEN);
1467
1468            for ( $i = 1; $i <= $depth; $i++ ) {
1469
1470                $this->addTocCall(array($call[0],array($call[1][0],$i),$call[2]), DOKU_TOCBRANCH_OPEN);
1471
1472                if ( $i != $depth ) {
1473                    $this->addTocCall(array($call[0],array($call[1][0], $i, '', TRUE),$call[2]), DOKU_TOCITEM_OPEN);
1474                } else {
1475                    $this->addTocCall(array($call[0],array($call[1][0], $i),$call[2]), DOKU_TOCITEM_OPEN);
1476                    $this->addTocCall(array($call[0],array($call[1][0], $i),$call[2]), DOKU_TOC_ELEMENT);
1477                }
1478
1479                $this->tocStack[] = $i;
1480
1481            }
1482            return;
1483        }
1484
1485        $currentDepth = end($this->tocStack);
1486        $initialDepth = $currentDepth;
1487
1488        // Create new branches as needed
1489        if ( $depth > $currentDepth ) {
1490
1491            for ($i = $currentDepth+1; $i <= $depth; $i++ ) {
1492                $this->addTocCall(array($call[0],array($call[1][0],$i),$call[2]), DOKU_TOCBRANCH_OPEN);
1493                // It's just a filler
1494                if ( $i != $depth ) {
1495                    $this->addTocCall(array($call[0],array($call[1][0], $i, '', TRUE),$call[2]), DOKU_TOCITEM_OPEN);
1496                } else {
1497                    $this->addTocCall(array($call[0],array($call[1][0], $i),$call[2]), DOKU_TOCITEM_OPEN);
1498                }
1499                $this->tocStack[] = $i;
1500            }
1501
1502            $currentDepth = $i-1;
1503
1504        }
1505
1506        // Going down
1507        if ( $depth < $currentDepth ) {
1508            for ( $i = $currentDepth; $i >= $depth; $i-- ) {
1509                if ( $i != $depth ) {
1510                    array_pop($this->tocStack);
1511                    $this->addTocCall(array($call[0],array($call[1][0],$i),$call[2]), DOKU_TOCITEM_CLOSE);
1512                    $this->addTocCall(array($call[0],array($call[1][0],$i),$call[2]), DOKU_TOCBRANCH_CLOSE);
1513                } else {
1514                    $this->addTocCall(array($call[0],array($call[1][0],$i),$call[2]), DOKU_TOCITEM_CLOSE);
1515                    $this->addTocCall(array($call[0],array($call[1][0],$i),$call[2]), DOKU_TOCITEM_OPEN);
1516                    $this->addTocCall($call, DOKU_TOC_ELEMENT);
1517                    return;
1518                }
1519            }
1520        }
1521
1522        if ( $depth == $initialDepth ) {
1523            $this->addTocCall($call, DOKU_TOCITEM_CLOSE);
1524            $this->addTocCall($call, DOKU_TOCITEM_OPEN);
1525        }
1526
1527        $this->addTocCall($call, DOKU_TOC_ELEMENT);
1528
1529
1530   }
1531
1532    function addTocCall($call, $type) {
1533        switch ( $type ) {
1534            case DOKU_TOC_OPEN:
1535                $this->toc[] = array('toc_open',array(),$call[2]);
1536            break;
1537
1538            case DOKU_TOCBRANCH_OPEN:
1539                $this->toc[] = array('tocbranch_open',array($call[1][1]),$call[2]);
1540            break;
1541
1542            case DOKU_TOCITEM_OPEN:
1543                if ( isset( $call[1][3] ) ) {
1544                    $this->toc[] = array('tocitem_open',array($call[1][1], TRUE),$call[2]);
1545                } else {
1546                    $this->toc[] = array('tocitem_open',array($call[1][1]),$call[2]);
1547                }
1548            break;
1549
1550            case DOKU_TOC_ELEMENT:
1551                $this->toc[] = array('tocelement',array($call[1][1],$call[1][0]),$call[2]);
1552            break;
1553
1554            case DOKU_TOCITEM_CLOSE:
1555                $this->toc[] = array('tocitem_close',array($call[1][1]),$call[2]);
1556            break;
1557
1558            case DOKU_TOCBRANCH_CLOSE:
1559                $this->toc[] = array('tocbranch_close',array($call[1][1]),$call[2]);
1560            break;
1561
1562            case DOKU_TOC_CLOSE:
1563                if ( count($this->toc) > 0 ) {
1564                    $this->toc[] = array('toc_close',array(),$call[2]);
1565                }
1566            break;
1567        }
1568    }
1569
1570    function finalizeToc($call) {
1571        global $conf;
1572        if ( $this->numHeaders < $conf['maxtoclevel'] ) {
1573            return;
1574        }
1575        if ( count ($this->tocStack) > 0 ) {
1576            while ( NULL !== ($toc = array_pop($this->tocStack)) ) {
1577                $this->addTocCall(array($call[0],array('',$toc),$call[2]), DOKU_TOCITEM_CLOSE);
1578                $this->addTocCall(array($call[0],array('',$toc),$call[2]), DOKU_TOCBRANCH_CLOSE);
1579            }
1580        }
1581        $this->addTocCall($call, DOKU_TOC_CLOSE);
1582        $this->calls = array_merge($this->toc, $this->calls);
1583    }
1584
1585}
1586
1587
1588//Setup VIM: ex: et ts=4 enc=utf-8 :
1589