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