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