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