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