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