~ubuntu-branches/ubuntu/karmic/dokuwiki/karmic

« back to all changes in this revision

Viewing changes to inc/parser/handler.php

  • Committer: Bazaar Package Importer
  • Author(s): Mohammed Adnène Trojette
  • Date: 2007-03-29 19:44:52 UTC
  • mfrom: (2.1.6 feisty)
  • Revision ID: james.westby@ubuntu.com-20070329194452-8r2w798oo21ago6l
Tags: 0.0.20061106-6
* High-urgency upload for fixing RC bug.
* Make fr.po's translation of "global" consistent. (Closes: #416509)
* Remove /etc/apache*/conf.d/ on purge. (Closes: #387974)

Show diffs side-by-side

added added

removed removed

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