2
if(!defined('DOKU_INC')) define('DOKU_INC',realpath(dirname(__FILE__).'/../../').'/');
8
var $CallWriter = NULL;
14
'section_edit_start' => -1,
15
'section_edit_level' => 1,
16
'section_edit_title' => ''
19
var $rewriteBlocks = TRUE;
21
function Doku_Handler() {
22
$this->CallWriter = & new Doku_Handler_CallWriter($this);
25
function _addCall($handler, $args, $pos) {
26
$call = array($handler,$args, $pos);
27
$this->CallWriter->writeCall($call);
32
$this->CallWriter->finalise();
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]));
43
if ( $this->rewriteBlocks ) {
44
$B = & new Doku_Handler_Block();
45
$this->calls = $B->process($this->calls);
48
trigger_event('PARSER_HANDLER_DONE',$this);
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]));
56
$call = each($this->calls);
58
return $call['value'];
65
* Special plugin handler
67
* This handler is called for all modes starting with 'plugin_'.
68
* An additional parameter with the plugin name is passed
70
* @author Andreas Gohr <andi@splitbrain.org>
72
function plugin($match, $state, $pos, $pluginname){
73
$data = array($match);
74
$plugin =& plugin_load('syntax',$pluginname);
76
$data = $plugin->handle($match, $state, $pos, $this);
78
$this->_addCall('plugin',array($pluginname,$data,$state),$pos);
82
function base($match, $state, $pos) {
84
case DOKU_LEXER_UNMATCHED:
85
$this->_addCall('cdata',array($match), $pos);
92
function header($match, $state, $pos) {
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);
102
if ($this->status['section']) $this->_addCall('section_close',array(),$pos);
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;
111
$this->_addCall('header',array($title,$level,$pos), $pos);
113
$this->_addCall('section_open',array($level),$pos);
114
$this->status['section'] = TRUE;
118
function notoc($match, $state, $pos) {
119
$this->_addCall('notoc',array(),$pos);
123
function nocache($match, $state, $pos) {
124
$this->_addCall('nocache',array(),$pos);
128
function linebreak($match, $state, $pos) {
129
$this->_addCall('linebreak',array(),$pos);
133
function eol($match, $state, $pos) {
134
$this->_addCall('eol',array(),$pos);
138
function hr($match, $state, $pos) {
139
$this->_addCall('hr',array(),$pos);
143
function _nestingTag($match, $state, $pos, $name) {
145
case DOKU_LEXER_ENTER:
146
$this->_addCall($name.'_open', array(), $pos);
148
case DOKU_LEXER_EXIT:
149
$this->_addCall($name.'_close', array(), $pos);
151
case DOKU_LEXER_UNMATCHED:
152
$this->_addCall('cdata',array($match), $pos);
157
function strong($match, $state, $pos) {
158
$this->_nestingTag($match, $state, $pos, 'strong');
162
function emphasis($match, $state, $pos) {
163
$this->_nestingTag($match, $state, $pos, 'emphasis');
167
function underline($match, $state, $pos) {
168
$this->_nestingTag($match, $state, $pos, 'underline');
172
function monospace($match, $state, $pos) {
173
$this->_nestingTag($match, $state, $pos, 'monospace');
177
function subscript($match, $state, $pos) {
178
$this->_nestingTag($match, $state, $pos, 'subscript');
182
function superscript($match, $state, $pos) {
183
$this->_nestingTag($match, $state, $pos, 'superscript');
187
function deleted($match, $state, $pos) {
188
$this->_nestingTag($match, $state, $pos, 'deleted');
193
function footnote($match, $state, $pos) {
194
// $this->_nestingTag($match, $state, $pos, 'footnote');
195
if (!isset($this->_footnote)) $this->_footnote = false;
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);
206
$this->_footnote = true;
208
$ReWriter = & new Doku_Handler_Nest($this->CallWriter,'footnote_close');
209
$this->CallWriter = & $ReWriter;
210
$this->_addCall('footnote_open', array(), $pos);
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);
219
$this->_footnote = false;
221
$this->_addCall('footnote_close', array(), $pos);
222
$this->CallWriter->process();
223
$ReWriter = & $this->CallWriter;
224
$this->CallWriter = & $ReWriter->CallWriter;
226
case DOKU_LEXER_UNMATCHED:
227
$this->_addCall('cdata', array($match), $pos);
233
function listblock($match, $state, $pos) {
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);
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;
246
case DOKU_LEXER_MATCHED:
247
$this->_addCall('list_item', array($match), $pos);
249
case DOKU_LEXER_UNMATCHED:
250
$this->_addCall('cdata', array($match), $pos);
256
function unformatted($match, $state, $pos) {
257
if ( $state == DOKU_LEXER_UNMATCHED ) {
258
$this->_addCall('unformatted',array($match), $pos);
263
function php($match, $state, $pos) {
265
if ( $state == DOKU_LEXER_UNMATCHED ) {
266
if ($conf['phpok']) {
267
$this->_addCall('php',array($match), $pos);
269
$this->_addCall('file',array($match), $pos);
275
function html($match, $state, $pos) {
277
if ( $state == DOKU_LEXER_UNMATCHED ) {
279
$this->_addCall('html',array($match), $pos);
281
$this->_addCall('file',array($match), $pos);
287
function preformatted($match, $state, $pos) {
289
case DOKU_LEXER_ENTER:
290
$ReWriter = & new Doku_Handler_Preformatted($this->CallWriter);
291
$this->CallWriter = & $ReWriter;
292
$this->_addCall('preformatted_start',array(), $pos);
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;
300
case DOKU_LEXER_MATCHED:
301
$this->_addCall('preformatted_newline',array(), $pos);
303
case DOKU_LEXER_UNMATCHED:
304
$this->_addCall('preformatted_content',array($match), $pos);
311
function file($match, $state, $pos) {
312
if ( $state == DOKU_LEXER_UNMATCHED ) {
313
$this->_addCall('file',array($match), $pos);
318
function quote($match, $state, $pos) {
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);
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;
335
case DOKU_LEXER_MATCHED:
336
$this->_addCall('quote_newline',array($match), $pos);
339
case DOKU_LEXER_UNMATCHED:
340
$this->_addCall('cdata',array($match), $pos);
348
function code($match, $state, $pos) {
350
case DOKU_LEXER_UNMATCHED:
351
$matches = preg_split('/>/u',$match,2);
352
$matches[0] = trim($matches[0]);
353
if ( trim($matches[0]) == '' ) {
356
# $matches[0] contains name of programming language
357
# if available, We shortcut html here.
358
if($matches[0] == 'html') $matches[0] = 'html4strict';
361
array($matches[1],$matches[0]),
369
function acronym($match, $state, $pos) {
370
$this->_addCall('acronym',array($match), $pos);
374
function smiley($match, $state, $pos) {
375
$this->_addCall('smiley',array($match), $pos);
379
function wordblock($match, $state, $pos) {
380
$this->_addCall('wordblock',array($match), $pos);
384
function entity($match, $state, $pos) {
385
$this->_addCall('entity',array($match), $pos);
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);
395
function singlequoteopening($match, $state, $pos) {
396
$this->_addCall('singlequoteopening',array(), $pos);
400
function singlequoteclosing($match, $state, $pos) {
401
$this->_addCall('singlequoteclosing',array(), $pos);
405
function doublequoteopening($match, $state, $pos) {
406
$this->_addCall('doublequoteopening',array(), $pos);
410
function doublequoteclosing($match, $state, $pos) {
411
$this->_addCall('doublequoteclosing',array(), $pos);
415
function camelcaselink($match, $state, $pos) {
416
$this->_addCall('camelcaselink',array($match), $pos);
422
function internallink($match, $state, $pos) {
423
// Strip the opening and closing markup
424
$link = preg_replace(array('/^\[\[/','/\]\]$/u'),'',$match);
426
// Split title from URL
427
$link = preg_split('/\|/u',$link,2);
428
if ( !isset($link[1]) ) {
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]);
434
$link[0] = trim($link[0]);
436
//decide which kind of link it is
438
if ( preg_match('/^[a-zA-Z\.]+>{1}.*$/u',$link[0]) ) {
440
$interwiki = preg_split('/>/u',$link[0]);
443
array($link[0],$link[1],strtolower($interwiki[0]),$interwiki[1]),
446
}elseif ( preg_match('/^\\\\\\\\[\w.:?\-;,]+?\\\\/u',$link[0]) ) {
450
array($link[0],$link[1]),
453
}elseif ( preg_match('#^([a-z0-9\-\.+]+?)://#i',$link[0]) ) {
454
// external link (accepts all protocols)
457
array($link[0],$link[1]),
460
}elseif ( preg_match('#([a-z0-9\-_.]+?)@([\w\-]+\.([\w\-\.]+\.)*[\w]+)#i',$link[0]) ) {
464
array($link[0],$link[1]),
467
}elseif ( preg_match('!^#.+!',$link[0]) ){
471
array(substr($link[0],1),$link[1]),
478
array($link[0],$link[1]),
486
function filelink($match, $state, $pos) {
487
$this->_addCall('filelink',array($match, NULL), $pos);
491
function windowssharelink($match, $state, $pos) {
492
$this->_addCall('windowssharelink',array($match, NULL), $pos);
496
function media($match, $state, $pos) {
497
$p = Doku_Handler_Parse_Media($match);
501
array($p['src'], $p['title'], $p['align'], $p['width'],
502
$p['height'], $p['cache'], $p['linking']),
508
function rss($match, $state, $pos) {
509
$link = preg_replace(array('/^\{\{rss>/','/\}\}$/'),'',$match);
512
list($link,$params) = explode(' ',$link,2);
515
if(preg_match('/\b(\d+)\b/',$params,$match)){
516
$p['max'] = $match[1];
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));
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
529
$p['refresh'] = 14400; // default to 4 hours
532
$this->_addCall('rss',array($link,$p),$pos);
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);
544
$this->_addCall('externallink',array($match, NULL), $pos);
549
function emaillink($match, $state, $pos) {
550
$email = preg_replace(array('/^</','/>$/'),'',$match);
551
$this->_addCall('emaillink',array($email, NULL), $pos);
555
function table($match, $state, $pos) {
558
case DOKU_LEXER_ENTER:
560
$ReWriter = & new Doku_Handler_Table($this->CallWriter);
561
$this->CallWriter = & $ReWriter;
563
$this->_addCall('table_start', array(), $pos);
564
//$this->_addCall('table_row', array(), $pos);
565
if ( trim($match) == '^' ) {
566
$this->_addCall('tableheader', array(), $pos);
568
$this->_addCall('tablecell', array(), $pos);
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;
579
case DOKU_LEXER_UNMATCHED:
580
if ( trim($match) != '' ) {
581
$this->_addCall('cdata',array($match), $pos);
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);
609
//------------------------------------------------------------------------
610
function Doku_Handler_Parse_Media($match) {
612
// Strip the opening and closing markup
613
$link = preg_replace(array('/^\{\{/','/\}\}$/u'),'',$match);
615
// Split title from URL
616
$link = preg_split('/\|/u',$link,2);
620
$ralign = (bool)preg_match('/^ /',$link[0]);
621
$lalign = (bool)preg_match('/ $/',$link[0]);
623
// Logic = what's that ;)...
624
if ( $lalign & $ralign ) {
626
} else if ( $ralign ) {
628
} else if ( $lalign ) {
635
if ( !isset($link[1]) ) {
639
//remove aligning spaces
640
$link[0] = trim($link[0]);
642
//split into src and parameters (using the very last questionmark)
643
$pos = strrpos($link[0], '?');
645
$src = substr($link[0],0,$pos);
646
$param = substr($link[0],$pos+1);
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;
661
//get linking command
662
if(preg_match('/nolink/i',$param)){
664
}else if(preg_match('/direct/i',$param)){
667
$linking = 'details';
670
//get caching command
671
if (preg_match('/(nocache|recache)/i',$param,$cachemode)){
672
$cache = $cachemode[1];
677
// Check whether this is a local or remote image
678
if ( preg_match('#^(https?|ftp)#i',$src) ) {
679
$call = 'externalmedia';
681
$call = 'internalmedia';
698
//------------------------------------------------------------------------
699
class Doku_Handler_CallWriter {
703
function Doku_Handler_CallWriter(& $Handler) {
704
$this->Handler = & $Handler;
707
function writeCall($call) {
708
$this->Handler->calls[] = $call;
711
function writeCalls($calls) {
712
$this->Handler->calls = array_merge($this->Handler->calls, $calls);
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() {
721
//------------------------------------------------------------------------
723
* Generic call writer class to handle nesting of rendering instructions
724
* within a render instruction. Also see nest() method of renderer base class
726
* @author Chris Smith <chris@jalakai.co.uk>
728
class Doku_Handler_Nest {
731
var $calls = array();
733
var $closingInstruction;
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
742
function Doku_Handler_Nest(& $CallWriter, $close="nest_close") {
743
$this->CallWriter = & $CallWriter;
745
$this->closingInstruction = $close;
748
function writeCall($call) {
749
$this->calls[] = $call;
752
function writeCalls($calls) {
753
$this->calls = array_merge($this->calls, $calls);
756
function finalise() {
757
$last_call = end($this->calls);
758
$this->writeCall(array($this->closingInstruction,array(), $last_call[2]));
761
$this->CallWriter->finalise();
765
$first_call = reset($this->calls);
766
$this->CallWriter->writeCall(array("nest", array($this->calls), $first_call[2]));
770
class Doku_Handler_List {
774
var $calls = array();
775
var $listCalls = array();
776
var $listStack = array();
778
function Doku_Handler_List(& $CallWriter) {
779
$this->CallWriter = & $CallWriter;
782
function writeCall($call) {
783
$this->calls[] = $call;
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);
792
function finalise() {
793
$last_call = end($this->calls);
794
$this->writeCall(array('list_close',array(), $last_call[2]));
797
$this->CallWriter->finalise();
800
//------------------------------------------------------------------------
803
foreach ( $this->calls as $call ) {
806
$this->listOpen($call);
809
$this->listStart($call);
812
$this->listEnd($call);
815
$this->listContent($call);
820
$this->CallWriter->writeCalls($this->listCalls);
823
//------------------------------------------------------------------------
824
function listStart($call) {
825
$depth = $this->interpretSyntax($call[1][0], $listType);
827
$this->initialDepth = $depth;
828
$this->listStack[] = array($listType, $depth);
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]);
835
//------------------------------------------------------------------------
836
function listEnd($call) {
837
$closeContent = TRUE;
839
while ( $list = array_pop($this->listStack) ) {
840
if ( $closeContent ) {
841
$this->listCalls[] = array('listcontent_close',array(),$call[2]);
842
$closeContent = FALSE;
844
$this->listCalls[] = array('listitem_close',array(),$call[2]);
845
$this->listCalls[] = array('list'.$list[0].'_close', array(), $call[2]);
849
//------------------------------------------------------------------------
850
function listOpen($call) {
851
$depth = $this->interpretSyntax($call[1][0], $listType);
852
$end = end($this->listStack);
854
// Not allowed to be shallower than initialDepth
855
if ( $depth < $this->initialDepth ) {
856
$depth = $this->initialDepth;
859
//------------------------------------------------------------------------
860
if ( $depth == $end[1] ) {
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]);
869
// Switched list type...
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]);
879
array_pop($this->listStack);
880
$this->listStack[] = array($listType, $depth);
883
//------------------------------------------------------------------------
885
} else if ( $depth > $end[1] ) {
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]);
892
$this->listStack[] = array($listType, $depth);
894
//------------------------------------------------------------------------
895
// Getting shallower ( $depth < $end[1] )
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]);
901
// Throw away the end - done
902
array_pop($this->listStack);
905
$end = end($this->listStack);
907
if ( $end[1] <= $depth ) {
912
$this->listCalls[] = array('listitem_close',array(),$call[2]);
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]);
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]);
925
array_pop($this->listStack);
926
$this->listStack[] = array($listType, $depth);
931
// Haven't dropped down far enough yet.... ( $end[1] > $depth )
934
$this->listCalls[] = array('listitem_close',array(),$call[2]);
935
$this->listCalls[] = array('list'.$end[0].'_close',array(),$call[2]);
937
array_pop($this->listStack);
946
//------------------------------------------------------------------------
947
function listContent($call) {
948
$this->listCalls[] = $call;
951
//------------------------------------------------------------------------
952
function interpretSyntax($match, & $type) {
953
if ( substr($match,-1) == '*' ) {
958
return count(explode(' ',str_replace("\t",' ',$match)));
962
//------------------------------------------------------------------------
963
class Doku_Handler_Preformatted {
967
var $calls = array();
973
function Doku_Handler_Preformatted(& $CallWriter) {
974
$this->CallWriter = & $CallWriter;
977
function writeCall($call) {
978
$this->calls[] = $call;
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);
987
function finalise() {
988
$last_call = end($this->calls);
989
$this->writeCall(array('preformatted_end',array(), $last_call[2]));
992
$this->CallWriter->finalise();
996
foreach ( $this->calls as $call ) {
998
case 'preformatted_start':
999
$this->pos = $call[2];
1001
case 'preformatted_newline':
1002
$this->text .= "\n";
1004
case 'preformatted_content':
1005
$this->text .= $call[1][0];
1007
case 'preformatted_end':
1008
$this->CallWriter->writeCall(array('preformatted',array($this->text),$this->pos));
1016
//------------------------------------------------------------------------
1017
class Doku_Handler_Quote {
1021
var $calls = array();
1023
var $quoteCalls = array();
1025
function Doku_Handler_Quote(& $CallWriter) {
1026
$this->CallWriter = & $CallWriter;
1029
function writeCall($call) {
1030
$this->calls[] = $call;
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);
1039
function finalise() {
1040
$last_call = end($this->calls);
1041
$this->writeCall(array('quote_end',array(), $last_call[2]));
1044
$this->CallWriter->finalise();
1047
function process() {
1051
foreach ( $this->calls as $call ) {
1056
$this->quoteCalls[] = array('quote_open',array(),$call[2]);
1058
case 'quote_newline':
1060
$quoteLength = $this->getDepth($call[1][0]);
1062
if ( $quoteLength > $quoteDepth ) {
1063
$quoteDiff = $quoteLength - $quoteDepth;
1064
for ( $i = 1; $i <= $quoteDiff; $i++ ) {
1065
$this->quoteCalls[] = array('quote_open',array(),$call[2]);
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]);
1073
if ($call[0] != 'quote_start') $this->quoteCalls[] = array('linebreak',array(),$call[2]);
1076
$quoteDepth = $quoteLength;
1082
if ( $quoteDepth > 1 ) {
1083
$quoteDiff = $quoteDepth - 1;
1084
for ( $i = 1; $i <= $quoteDiff; $i++ ) {
1085
$this->quoteCalls[] = array('quote_close',array(),$call[2]);
1089
$this->quoteCalls[] = array('quote_close',array(),$call[2]);
1091
$this->CallWriter->writeCalls($this->quoteCalls);
1095
$this->quoteCalls[] = $call;
1101
function getDepth($marker) {
1102
preg_match('/>{1,}/', $marker, $matches);
1103
$quoteLength = strlen($matches[0]);
1104
return $quoteLength;
1108
//------------------------------------------------------------------------
1109
class Doku_Handler_Table {
1113
var $calls = array();
1114
var $tableCalls = array();
1117
var $currentCols = 0;
1118
var $firstCell = FALSE;
1119
var $lastCellType = 'tablecell';
1121
function Doku_Handler_Table(& $CallWriter) {
1122
$this->CallWriter = & $CallWriter;
1125
function writeCall($call) {
1126
$this->calls[] = $call;
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);
1135
function finalise() {
1136
$last_call = end($this->calls);
1137
$this->writeCall(array('table_end',array(), $last_call[2]));
1140
$this->CallWriter->finalise();
1143
//------------------------------------------------------------------------
1144
function process() {
1145
foreach ( $this->calls as $call ) {
1146
switch ( $call[0] ) {
1148
$this->tableStart($call);
1151
$this->tableRowClose(array('tablerow_close',$call[1],$call[2]));
1152
$this->tableRowOpen(array('tablerow_open',$call[1],$call[2]));
1156
$this->tableCell($call);
1159
$this->tableRowClose(array('tablerow_close',$call[1],$call[2]));
1160
$this->tableEnd($call);
1163
$this->tableDefault($call);
1167
$this->CallWriter->writeCalls($this->tableCalls);
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;
1176
function tableEnd($call) {
1177
$this->tableCalls[] = array('table_close',array(),$call[2]);
1178
$this->finalizeTable();
1181
function tableRowOpen($call) {
1182
$this->tableCalls[] = $call;
1183
$this->currentCols = 0;
1184
$this->firstCell = TRUE;
1185
$this->lastCellType = 'tablecell';
1189
function tableRowClose($call) {
1190
// Strip off final cell opening and anything after it
1191
while ( $discard = array_pop($this->tableCalls ) ) {
1193
if ( $discard[0] == 'tablecell_open' || $discard[0] == 'tableheader_open') {
1195
// Its a spanning element - put it back and close it
1196
if ( $discard[1][0] > 1 ) {
1198
$this->tableCalls[] = $discard;
1199
if ( strstr($discard[0],'cell') ) {
1200
$name = 'tablecell';
1202
$name = 'tableheader';
1204
$this->tableCalls[] = array($name.'_close',array(),$call[2]);
1210
$this->tableCalls[] = $call;
1212
if ( $this->currentCols > $this->maxCols ) {
1213
$this->maxCols = $this->currentCols;
1217
function tableCell($call) {
1218
if ( !$this->firstCell ) {
1220
// Increase the span
1221
$lastCall = end($this->tableCalls);
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]);
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];
1235
$this->tableCalls[] = array($call[0].'_open',array(1,NULL),$call[2]);
1236
$this->lastCellType = $call[0];
1237
$this->firstCell = FALSE;
1241
$this->currentCols++;
1244
function tableDefault($call) {
1245
$this->tableCalls[] = $call;
1248
function finalizeTable() {
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;
1256
trigger_error('First element in table call list is not table_open');
1261
$toDelete = array();
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 ) {
1268
if ( $call[0] == 'tablerow_open' ) {
1272
} else if ( $call[0] == 'tablecell_open' || $call[0] == 'tableheader_open' ) {
1276
} else if ( $call[0] == 'table_align' ) {
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';
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';
1287
$this->tableCalls[$lastCell][1][1] = 'left';
1292
// Now convert the whitespace back to cdata
1293
$this->tableCalls[$key][0] = 'cdata';
1295
} else if ( $call[0] == 'colspan' ) {
1297
$this->tableCalls[$key-1][1][0] = FALSE;
1299
for($i = $key-2; $i > $lastRow; $i--) {
1301
if ( $this->tableCalls[$i][0] == 'tablecell_open' || $this->tableCalls[$i][0] == 'tableheader_open' ) {
1303
if ( FALSE !== $this->tableCalls[$i][1][0] ) {
1304
$this->tableCalls[$i][1][0]++;
1312
$toDelete[] = $key-1;
1314
$toDelete[] = $key+1;
1320
$cnt = count($this->tableCalls);
1321
for( $key = 0; $key < $cnt; $key++){
1322
if($this->tableCalls[$key][0] == 'cdata'){
1325
while($this->tableCalls[$key][0] == 'cdata'){
1326
$this->tableCalls[$ckey][1][0] .= $this->tableCalls[$key][1][0];
1334
foreach ( $toDelete as $delete ) {
1335
unset($this->tableCalls[$delete]);
1337
$this->tableCalls = array_values($this->tableCalls);
1341
//------------------------------------------------------------------------
1342
class Doku_Handler_Section {
1344
function process($calls) {
1346
$sectionCalls = array();
1349
foreach ( $calls as $call ) {
1351
if ( $call[0] == 'header' ) {
1354
$sectionCalls[] = array('section_close',array(), $call[2]);
1357
$sectionCalls[] = $call;
1358
$sectionCalls[] = array('section_open',array($call[1][1]), $call[2]);
1363
if ($call[0] == 'section_open' ) {
1365
} else if ($call[0] == 'section_open' ) {
1368
$sectionCalls[] = $call;
1373
$sectionCalls[] = array('section_close',array(), $call[2]);
1376
return $sectionCalls;
1382
* Handler for paragraphs
1384
* @author Harry Fuecks <hfuecks@gmail.com>
1386
class Doku_Handler_Block {
1388
var $calls = array();
1390
var $blockStack = array();
1392
var $inParagraph = FALSE;
1393
var $atStart = TRUE;
1394
var $skipEolKey = -1;
1396
// Blocks these should not be inside paragraphs
1397
var $blockOpen = array(
1399
'listu_open','listo_open','listitem_open','listcontent_open',
1400
'table_open','tablerow_open','tablecell_open','tableheader_open',
1402
'section_open', // Needed to prevent p_open between header and section_open
1403
'code','file','hr','preformatted','rss',
1406
var $blockClose = array(
1408
'listu_close','listo_close','listitem_close','listcontent_close',
1409
'table_close','tablerow_close','tablecell_close','tableheader_close',
1411
'section_close', // Needed to prevent p_close after section_close
1412
'code','file','hr','preformatted','rss',
1415
// Stacks can contain paragraphs
1416
var $stackOpen = array(
1417
'footnote_open','section_open',
1420
var $stackClose = array(
1421
'footnote_close','section_close',
1426
* Constructor. Adds loaded syntax plugins to the block and stack
1429
* @author Andreas Gohr <andi@splitbrain.org>
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;
1448
* Close a paragraph if needed
1450
* This function makes sure there are no empty paragraphs on the stack
1452
* @author Andreas Gohr <andi@splitbrain.org>
1454
function closeParagraph($pos){
1455
// look back if there was any content - we don't want empty paragraphs
1457
for($i=count($this->calls)-1; $i>=0; $i--){
1458
if($this->calls[$i][0] == 'p_open'){
1460
}elseif($this->calls[$i][0] == 'cdata'){
1461
$content .= $this->calls[$i][1][0];
1463
$content = 'found markup';
1468
if(trim($content)==''){
1469
//remove the whole paragraph
1470
array_splice($this->calls,$i);
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;
1477
$this->calls[] = array('p_close',array(), $pos);
1481
$this->inParagraph = FALSE;
1485
* Processes the whole instruction stack to open and close paragraphs
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
1491
function process($calls) {
1492
foreach ( $calls as $key => $call ) {
1494
if($cname == 'plugin') {
1495
$cname='plugin_'.$call[1][0];
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));
1504
// Process blocks which are stack like... (contain linefeeds)
1505
if ( in_array($cname,$this->stackOpen ) && (!$plugin || $plugin_open) ) {
1507
$this->calls[] = $call;
1509
// Hack - footnotes shouldn't immediately contain a p_open
1510
if ( $cname != 'footnote_open' ) {
1511
$this->addToStack();
1513
$this->addToStack(FALSE);
1518
if ( in_array($cname,$this->stackClose ) && (!$plugin || $plugin_close)) {
1520
if ( $this->inParagraph ) {
1521
$this->closeParagraph($call[2]);
1523
$this->calls[] = $call;
1524
$this->removeFromStack();
1528
if ( !$this->atStart ) {
1530
if ( $cname == 'eol' ) {
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' ) {
1537
if ( $this->inParagraph ) {
1538
//$this->calls[] = array('p_close',array(), $call[2]);
1539
$this->closeParagraph($call[2]);
1542
$this->calls[] = array('p_open',array(), $call[2]);
1543
$this->inParagraph = TRUE;
1546
// Mark the next instruction for skipping
1547
$this->skipEolKey = $key+1;
1550
//if this is just a single eol make a space from it
1551
$this->calls[] = array('cdata',array(" "), $call[2]);
1559
if ( $this->inParagraph && (in_array($cname, $this->blockOpen) && (!$plugin || $plugin_open))) {
1560
$this->closeParagraph($call[2]);
1561
$this->calls[] = $call;
1565
if ( in_array($cname, $this->blockClose) && (!$plugin || $plugin_close)) {
1566
if ( $this->inParagraph ) {
1567
$this->closeParagraph($call[2]);
1570
$this->calls[] = $call;
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];
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);
1584
$plugin_plusone = false;
1586
if ((!in_array($cname_plusone, $this->blockOpen) && !in_array($cname_plusone, $this->blockClose)) ||
1587
($plugin_plusone && $plugin_test)
1590
$this->calls[] = array('p_open',array(), $call[2]);
1591
$this->inParagraph = TRUE;
1597
$this->calls[] = $call;
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;
1611
$this->atStart = FALSE;
1612
$this->inParagraph = TRUE;
1614
$this->calls[] = $call;
1615
$this->atStart = FALSE;
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]);
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;
1637
return $this->calls;
1640
function addToStack($newStart = TRUE) {
1641
$this->blockStack[] = array($this->atStart, $this->inParagraph);
1642
$this->atStart = $newStart;
1643
$this->inParagraph = FALSE;
1646
function removeFromStack() {
1647
$state = array_pop($this->blockStack);
1648
$this->atStart = $state[0];
1649
$this->inParagraph = $state[1];
1653
//Setup VIM: ex: et ts=4 enc=utf-8 :