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 _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('<'.PREG_PATTERN_VALID_EMAIL.'>',$link[0]) ) { 461 // E-Mail (pattern above is defined in inc/mail.php) 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 $url = $match; 538 $title = null; 539 540 // add protocol on simple short URLs 541 if(substr($url,0,3) == 'ftp' && (substr($url,0,6) != 'ftp://')){ 542 $title = $url; 543 $url = 'ftp://'.$url; 544 } 545 if(substr($url,0,3) == 'www' && (substr($url,0,7) != 'http://')){ 546 $title = $url; 547 $url = 'http://'.$url; 548 } 549 550 $this->_addCall('externallink',array($url, $title), $pos); 551 return true; 552 } 553 554 function emaillink($match, $state, $pos) { 555 $email = preg_replace(array('/^</','/>$/'),'',$match); 556 $this->_addCall('emaillink',array($email, NULL), $pos); 557 return true; 558 } 559 560 function table($match, $state, $pos) { 561 switch ( $state ) { 562 563 case DOKU_LEXER_ENTER: 564 565 $ReWriter = & new Doku_Handler_Table($this->CallWriter); 566 $this->CallWriter = & $ReWriter; 567 568 $this->_addCall('table_start', array(), $pos); 569 //$this->_addCall('table_row', array(), $pos); 570 if ( trim($match) == '^' ) { 571 $this->_addCall('tableheader', array(), $pos); 572 } else { 573 $this->_addCall('tablecell', array(), $pos); 574 } 575 break; 576 577 case DOKU_LEXER_EXIT: 578 $this->_addCall('table_end', array(), $pos); 579 $this->CallWriter->process(); 580 $ReWriter = & $this->CallWriter; 581 $this->CallWriter = & $ReWriter->CallWriter; 582 break; 583 584 case DOKU_LEXER_UNMATCHED: 585 if ( trim($match) != '' ) { 586 $this->_addCall('cdata',array($match), $pos); 587 } 588 break; 589 590 case DOKU_LEXER_MATCHED: 591 if ( $match == ' ' ){ 592 $this->_addCall('cdata', array($match), $pos); 593 } else if ( preg_match('/\t+/',$match) ) { 594 $this->_addCall('table_align', array($match), $pos); 595 } else if ( preg_match('/ {2,}/',$match) ) { 596 $this->_addCall('table_align', array($match), $pos); 597 } else if ( $match == "\n|" ) { 598 $this->_addCall('table_row', array(), $pos); 599 $this->_addCall('tablecell', array(), $pos); 600 } else if ( $match == "\n^" ) { 601 $this->_addCall('table_row', array(), $pos); 602 $this->_addCall('tableheader', array(), $pos); 603 } else if ( $match == '|' ) { 604 $this->_addCall('tablecell', array(), $pos); 605 } else if ( $match == '^' ) { 606 $this->_addCall('tableheader', array(), $pos); 607 } 608 break; 609 } 610 return true; 611 } 612} 613 614//------------------------------------------------------------------------ 615function Doku_Handler_Parse_Media($match) { 616 617 // Strip the opening and closing markup 618 $link = preg_replace(array('/^\{\{/','/\}\}$/u'),'',$match); 619 620 // Split title from URL 621 $link = preg_split('/\|/u',$link,2); 622 623 624 // Check alignment 625 $ralign = (bool)preg_match('/^ /',$link[0]); 626 $lalign = (bool)preg_match('/ $/',$link[0]); 627 628 // Logic = what's that ;)... 629 if ( $lalign & $ralign ) { 630 $align = 'center'; 631 } else if ( $ralign ) { 632 $align = 'right'; 633 } else if ( $lalign ) { 634 $align = 'left'; 635 } else { 636 $align = NULL; 637 } 638 639 // The title... 640 if ( !isset($link[1]) ) { 641 $link[1] = NULL; 642 } 643 644 //remove aligning spaces 645 $link[0] = trim($link[0]); 646 647 //split into src and parameters (using the very last questionmark) 648 $pos = strrpos($link[0], '?'); 649 if($pos !== false){ 650 $src = substr($link[0],0,$pos); 651 $param = substr($link[0],$pos+1); 652 }else{ 653 $src = $link[0]; 654 $param = ''; 655 } 656 657 //parse width and height 658 if(preg_match('#(\d+)(x(\d+))?#i',$param,$size)){ 659 ($size[1]) ? $w = $size[1] : $w = NULL; 660 ($size[3]) ? $h = $size[3] : $h = NULL; 661 } else { 662 $w = NULL; 663 $h = NULL; 664 } 665 666 //get linking command 667 if(preg_match('/nolink/i',$param)){ 668 $linking = 'nolink'; 669 }else if(preg_match('/direct/i',$param)){ 670 $linking = 'direct'; 671 }else{ 672 $linking = 'details'; 673 } 674 675 //get caching command 676 if (preg_match('/(nocache|recache)/i',$param,$cachemode)){ 677 $cache = $cachemode[1]; 678 }else{ 679 $cache = 'cache'; 680 } 681 682 // Check whether this is a local or remote image 683 if ( preg_match('#^(https?|ftp)#i',$src) ) { 684 $call = 'externalmedia'; 685 } else { 686 $call = 'internalmedia'; 687 } 688 689 $params = array( 690 'type'=>$call, 691 'src'=>$src, 692 'title'=>$link[1], 693 'align'=>$align, 694 'width'=>$w, 695 'height'=>$h, 696 'cache'=>$cache, 697 'linking'=>$linking, 698 ); 699 700 return $params; 701} 702 703//------------------------------------------------------------------------ 704class Doku_Handler_CallWriter { 705 706 var $Handler; 707 708 function Doku_Handler_CallWriter(& $Handler) { 709 $this->Handler = & $Handler; 710 } 711 712 function writeCall($call) { 713 $this->Handler->calls[] = $call; 714 } 715 716 function writeCalls($calls) { 717 $this->Handler->calls = array_merge($this->Handler->calls, $calls); 718 } 719 720 // function is required, but since this call writer is first/highest in 721 // the chain it is not required to do anything 722 function finalise() { 723 } 724} 725 726//------------------------------------------------------------------------ 727/** 728 * Generic call writer class to handle nesting of rendering instructions 729 * within a render instruction. Also see nest() method of renderer base class 730 * 731 * @author Chris Smith <chris@jalakai.co.uk> 732 */ 733class Doku_Handler_Nest { 734 735 var $CallWriter; 736 var $calls = array(); 737 738 var $closingInstruction; 739 740 /** 741 * constructor 742 * 743 * @param object $CallWriter the renderers current call writer 744 * @param string $close closing instruction name, this is required to properly terminate the 745 * syntax mode if the document ends without a closing pattern 746 */ 747 function Doku_Handler_Nest(& $CallWriter, $close="nest_close") { 748 $this->CallWriter = & $CallWriter; 749 750 $this->closingInstruction = $close; 751 } 752 753 function writeCall($call) { 754 $this->calls[] = $call; 755 } 756 757 function writeCalls($calls) { 758 $this->calls = array_merge($this->calls, $calls); 759 } 760 761 function finalise() { 762 $last_call = end($this->calls); 763 $this->writeCall(array($this->closingInstruction,array(), $last_call[2])); 764 765 $this->process(); 766 $this->CallWriter->finalise(); 767 } 768 769 function process() { 770 $first_call = reset($this->calls); 771 $this->CallWriter->writeCall(array("nest", array($this->calls), $first_call[2])); 772 } 773} 774 775class Doku_Handler_List { 776 777 var $CallWriter; 778 779 var $calls = array(); 780 var $listCalls = array(); 781 var $listStack = array(); 782 783 function Doku_Handler_List(& $CallWriter) { 784 $this->CallWriter = & $CallWriter; 785 } 786 787 function writeCall($call) { 788 $this->calls[] = $call; 789 } 790 791 // Probably not needed but just in case... 792 function writeCalls($calls) { 793 $this->calls = array_merge($this->calls, $calls); 794# $this->CallWriter->writeCalls($this->calls); 795 } 796 797 function finalise() { 798 $last_call = end($this->calls); 799 $this->writeCall(array('list_close',array(), $last_call[2])); 800 801 $this->process(); 802 $this->CallWriter->finalise(); 803 } 804 805 //------------------------------------------------------------------------ 806 function process() { 807 808 foreach ( $this->calls as $call ) { 809 switch ($call[0]) { 810 case 'list_item': 811 $this->listOpen($call); 812 break; 813 case 'list_open': 814 $this->listStart($call); 815 break; 816 case 'list_close': 817 $this->listEnd($call); 818 break; 819 default: 820 $this->listContent($call); 821 break; 822 } 823 } 824 825 $this->CallWriter->writeCalls($this->listCalls); 826 } 827 828 //------------------------------------------------------------------------ 829 function listStart($call) { 830 $depth = $this->interpretSyntax($call[1][0], $listType); 831 832 $this->initialDepth = $depth; 833 $this->listStack[] = array($listType, $depth); 834 835 $this->listCalls[] = array('list'.$listType.'_open',array(),$call[2]); 836 $this->listCalls[] = array('listitem_open',array(1),$call[2]); 837 $this->listCalls[] = array('listcontent_open',array(),$call[2]); 838 } 839 840 //------------------------------------------------------------------------ 841 function listEnd($call) { 842 $closeContent = true; 843 844 while ( $list = array_pop($this->listStack) ) { 845 if ( $closeContent ) { 846 $this->listCalls[] = array('listcontent_close',array(),$call[2]); 847 $closeContent = false; 848 } 849 $this->listCalls[] = array('listitem_close',array(),$call[2]); 850 $this->listCalls[] = array('list'.$list[0].'_close', array(), $call[2]); 851 } 852 } 853 854 //------------------------------------------------------------------------ 855 function listOpen($call) { 856 $depth = $this->interpretSyntax($call[1][0], $listType); 857 $end = end($this->listStack); 858 859 // Not allowed to be shallower than initialDepth 860 if ( $depth < $this->initialDepth ) { 861 $depth = $this->initialDepth; 862 } 863 864 //------------------------------------------------------------------------ 865 if ( $depth == $end[1] ) { 866 867 // Just another item in the list... 868 if ( $listType == $end[0] ) { 869 $this->listCalls[] = array('listcontent_close',array(),$call[2]); 870 $this->listCalls[] = array('listitem_close',array(),$call[2]); 871 $this->listCalls[] = array('listitem_open',array($depth-1),$call[2]); 872 $this->listCalls[] = array('listcontent_open',array(),$call[2]); 873 874 // Switched list type... 875 } else { 876 877 $this->listCalls[] = array('listcontent_close',array(),$call[2]); 878 $this->listCalls[] = array('listitem_close',array(),$call[2]); 879 $this->listCalls[] = array('list'.$end[0].'_close', array(), $call[2]); 880 $this->listCalls[] = array('list'.$listType.'_open', array(), $call[2]); 881 $this->listCalls[] = array('listitem_open', array($depth-1), $call[2]); 882 $this->listCalls[] = array('listcontent_open',array(),$call[2]); 883 884 array_pop($this->listStack); 885 $this->listStack[] = array($listType, $depth); 886 } 887 888 //------------------------------------------------------------------------ 889 // Getting deeper... 890 } else if ( $depth > $end[1] ) { 891 892 $this->listCalls[] = array('listcontent_close',array(),$call[2]); 893 $this->listCalls[] = array('list'.$listType.'_open', array(), $call[2]); 894 $this->listCalls[] = array('listitem_open', array($depth-1), $call[2]); 895 $this->listCalls[] = array('listcontent_open',array(),$call[2]); 896 897 $this->listStack[] = array($listType, $depth); 898 899 //------------------------------------------------------------------------ 900 // Getting shallower ( $depth < $end[1] ) 901 } else { 902 $this->listCalls[] = array('listcontent_close',array(),$call[2]); 903 $this->listCalls[] = array('listitem_close',array(),$call[2]); 904 $this->listCalls[] = array('list'.$end[0].'_close',array(),$call[2]); 905 906 // Throw away the end - done 907 array_pop($this->listStack); 908 909 while (1) { 910 $end = end($this->listStack); 911 912 if ( $end[1] <= $depth ) { 913 914 // Normalize depths 915 $depth = $end[1]; 916 917 $this->listCalls[] = array('listitem_close',array(),$call[2]); 918 919 if ( $end[0] == $listType ) { 920 $this->listCalls[] = array('listitem_open',array($depth-1),$call[2]); 921 $this->listCalls[] = array('listcontent_open',array(),$call[2]); 922 923 } else { 924 // Switching list type... 925 $this->listCalls[] = array('list'.$end[0].'_close', array(), $call[2]); 926 $this->listCalls[] = array('list'.$listType.'_open', array(), $call[2]); 927 $this->listCalls[] = array('listitem_open', array($depth-1), $call[2]); 928 $this->listCalls[] = array('listcontent_open',array(),$call[2]); 929 930 array_pop($this->listStack); 931 $this->listStack[] = array($listType, $depth); 932 } 933 934 break; 935 936 // Haven't dropped down far enough yet.... ( $end[1] > $depth ) 937 } else { 938 939 $this->listCalls[] = array('listitem_close',array(),$call[2]); 940 $this->listCalls[] = array('list'.$end[0].'_close',array(),$call[2]); 941 942 array_pop($this->listStack); 943 944 } 945 946 } 947 948 } 949 } 950 951 //------------------------------------------------------------------------ 952 function listContent($call) { 953 $this->listCalls[] = $call; 954 } 955 956 //------------------------------------------------------------------------ 957 function interpretSyntax($match, & $type) { 958 if ( substr($match,-1) == '*' ) { 959 $type = 'u'; 960 } else { 961 $type = 'o'; 962 } 963 return count(explode(' ',str_replace("\t",' ',$match))); 964 } 965} 966 967//------------------------------------------------------------------------ 968class Doku_Handler_Preformatted { 969 970 var $CallWriter; 971 972 var $calls = array(); 973 var $pos; 974 var $text =''; 975 976 977 978 function Doku_Handler_Preformatted(& $CallWriter) { 979 $this->CallWriter = & $CallWriter; 980 } 981 982 function writeCall($call) { 983 $this->calls[] = $call; 984 } 985 986 // Probably not needed but just in case... 987 function writeCalls($calls) { 988 $this->calls = array_merge($this->calls, $calls); 989# $this->CallWriter->writeCalls($this->calls); 990 } 991 992 function finalise() { 993 $last_call = end($this->calls); 994 $this->writeCall(array('preformatted_end',array(), $last_call[2])); 995 996 $this->process(); 997 $this->CallWriter->finalise(); 998 } 999 1000 function process() { 1001 foreach ( $this->calls as $call ) { 1002 switch ($call[0]) { 1003 case 'preformatted_start': 1004 $this->pos = $call[2]; 1005 break; 1006 case 'preformatted_newline': 1007 $this->text .= "\n"; 1008 break; 1009 case 'preformatted_content': 1010 $this->text .= $call[1][0]; 1011 break; 1012 case 'preformatted_end': 1013 $this->CallWriter->writeCall(array('preformatted',array($this->text),$this->pos)); 1014 break; 1015 } 1016 } 1017 } 1018 1019} 1020 1021//------------------------------------------------------------------------ 1022class Doku_Handler_Quote { 1023 1024 var $CallWriter; 1025 1026 var $calls = array(); 1027 1028 var $quoteCalls = array(); 1029 1030 function Doku_Handler_Quote(& $CallWriter) { 1031 $this->CallWriter = & $CallWriter; 1032 } 1033 1034 function writeCall($call) { 1035 $this->calls[] = $call; 1036 } 1037 1038 // Probably not needed but just in case... 1039 function writeCalls($calls) { 1040 $this->calls = array_merge($this->calls, $calls); 1041# $this->CallWriter->writeCalls($this->calls); 1042 } 1043 1044 function finalise() { 1045 $last_call = end($this->calls); 1046 $this->writeCall(array('quote_end',array(), $last_call[2])); 1047 1048 $this->process(); 1049 $this->CallWriter->finalise(); 1050 } 1051 1052 function process() { 1053 1054 $quoteDepth = 1; 1055 1056 foreach ( $this->calls as $call ) { 1057 switch ($call[0]) { 1058 1059 case 'quote_start': 1060 1061 $this->quoteCalls[] = array('quote_open',array(),$call[2]); 1062 1063 case 'quote_newline': 1064 1065 $quoteLength = $this->getDepth($call[1][0]); 1066 1067 if ( $quoteLength > $quoteDepth ) { 1068 $quoteDiff = $quoteLength - $quoteDepth; 1069 for ( $i = 1; $i <= $quoteDiff; $i++ ) { 1070 $this->quoteCalls[] = array('quote_open',array(),$call[2]); 1071 } 1072 } else if ( $quoteLength < $quoteDepth ) { 1073 $quoteDiff = $quoteDepth - $quoteLength; 1074 for ( $i = 1; $i <= $quoteDiff; $i++ ) { 1075 $this->quoteCalls[] = array('quote_close',array(),$call[2]); 1076 } 1077 } else { 1078 if ($call[0] != 'quote_start') $this->quoteCalls[] = array('linebreak',array(),$call[2]); 1079 } 1080 1081 $quoteDepth = $quoteLength; 1082 1083 break; 1084 1085 case 'quote_end': 1086 1087 if ( $quoteDepth > 1 ) { 1088 $quoteDiff = $quoteDepth - 1; 1089 for ( $i = 1; $i <= $quoteDiff; $i++ ) { 1090 $this->quoteCalls[] = array('quote_close',array(),$call[2]); 1091 } 1092 } 1093 1094 $this->quoteCalls[] = array('quote_close',array(),$call[2]); 1095 1096 $this->CallWriter->writeCalls($this->quoteCalls); 1097 break; 1098 1099 default: 1100 $this->quoteCalls[] = $call; 1101 break; 1102 } 1103 } 1104 } 1105 1106 function getDepth($marker) { 1107 preg_match('/>{1,}/', $marker, $matches); 1108 $quoteLength = strlen($matches[0]); 1109 return $quoteLength; 1110 } 1111} 1112 1113//------------------------------------------------------------------------ 1114class Doku_Handler_Table { 1115 1116 var $CallWriter; 1117 1118 var $calls = array(); 1119 var $tableCalls = array(); 1120 var $maxCols = 0; 1121 var $maxRows = 1; 1122 var $currentCols = 0; 1123 var $firstCell = false; 1124 var $lastCellType = 'tablecell'; 1125 1126 function Doku_Handler_Table(& $CallWriter) { 1127 $this->CallWriter = & $CallWriter; 1128 } 1129 1130 function writeCall($call) { 1131 $this->calls[] = $call; 1132 } 1133 1134 // Probably not needed but just in case... 1135 function writeCalls($calls) { 1136 $this->calls = array_merge($this->calls, $calls); 1137# $this->CallWriter->writeCalls($this->calls); 1138 } 1139 1140 function finalise() { 1141 $last_call = end($this->calls); 1142 $this->writeCall(array('table_end',array(), $last_call[2])); 1143 1144 $this->process(); 1145 $this->CallWriter->finalise(); 1146 } 1147 1148 //------------------------------------------------------------------------ 1149 function process() { 1150 foreach ( $this->calls as $call ) { 1151 switch ( $call[0] ) { 1152 case 'table_start': 1153 $this->tableStart($call); 1154 break; 1155 case 'table_row': 1156 $this->tableRowClose(array('tablerow_close',$call[1],$call[2])); 1157 $this->tableRowOpen(array('tablerow_open',$call[1],$call[2])); 1158 break; 1159 case 'tableheader': 1160 case 'tablecell': 1161 $this->tableCell($call); 1162 break; 1163 case 'table_end': 1164 $this->tableRowClose(array('tablerow_close',$call[1],$call[2])); 1165 $this->tableEnd($call); 1166 break; 1167 default: 1168 $this->tableDefault($call); 1169 break; 1170 } 1171 } 1172 $this->CallWriter->writeCalls($this->tableCalls); 1173 } 1174 1175 function tableStart($call) { 1176 $this->tableCalls[] = array('table_open',array(),$call[2]); 1177 $this->tableCalls[] = array('tablerow_open',array(),$call[2]); 1178 $this->firstCell = true; 1179 } 1180 1181 function tableEnd($call) { 1182 $this->tableCalls[] = array('table_close',array(),$call[2]); 1183 $this->finalizeTable(); 1184 } 1185 1186 function tableRowOpen($call) { 1187 $this->tableCalls[] = $call; 1188 $this->currentCols = 0; 1189 $this->firstCell = true; 1190 $this->lastCellType = 'tablecell'; 1191 $this->maxRows++; 1192 } 1193 1194 function tableRowClose($call) { 1195 // Strip off final cell opening and anything after it 1196 while ( $discard = array_pop($this->tableCalls ) ) { 1197 1198 if ( $discard[0] == 'tablecell_open' || $discard[0] == 'tableheader_open') { 1199 1200 // Its a spanning element - put it back and close it 1201 if ( $discard[1][0] > 1 ) { 1202 1203 $this->tableCalls[] = $discard; 1204 if ( strstr($discard[0],'cell') ) { 1205 $name = 'tablecell'; 1206 } else { 1207 $name = 'tableheader'; 1208 } 1209 $this->tableCalls[] = array($name.'_close',array(),$call[2]); 1210 } 1211 1212 break; 1213 } 1214 } 1215 $this->tableCalls[] = $call; 1216 1217 if ( $this->currentCols > $this->maxCols ) { 1218 $this->maxCols = $this->currentCols; 1219 } 1220 } 1221 1222 function tableCell($call) { 1223 if ( !$this->firstCell ) { 1224 1225 // Increase the span 1226 $lastCall = end($this->tableCalls); 1227 1228 // A cell call which follows an open cell means an empty cell so span 1229 if ( $lastCall[0] == 'tablecell_open' || $lastCall[0] == 'tableheader_open' ) { 1230 $this->tableCalls[] = array('colspan',array(),$call[2]); 1231 1232 } 1233 1234 $this->tableCalls[] = array($this->lastCellType.'_close',array(),$call[2]); 1235 $this->tableCalls[] = array($call[0].'_open',array(1,NULL),$call[2]); 1236 $this->lastCellType = $call[0]; 1237 1238 } else { 1239 1240 $this->tableCalls[] = array($call[0].'_open',array(1,NULL),$call[2]); 1241 $this->lastCellType = $call[0]; 1242 $this->firstCell = false; 1243 1244 } 1245 1246 $this->currentCols++; 1247 } 1248 1249 function tableDefault($call) { 1250 $this->tableCalls[] = $call; 1251 } 1252 1253 function finalizeTable() { 1254 1255 // Add the max cols and rows to the table opening 1256 if ( $this->tableCalls[0][0] == 'table_open' ) { 1257 // Adjust to num cols not num col delimeters 1258 $this->tableCalls[0][1][] = $this->maxCols - 1; 1259 $this->tableCalls[0][1][] = $this->maxRows; 1260 } else { 1261 trigger_error('First element in table call list is not table_open'); 1262 } 1263 1264 $lastRow = 0; 1265 $lastCell = 0; 1266 $toDelete = array(); 1267 1268 // Look for the colspan elements and increment the colspan on the 1269 // previous non-empty opening cell. Once done, delete all the cells 1270 // that contain colspans 1271 foreach ( $this->tableCalls as $key => $call ) { 1272 1273 if ( $call[0] == 'tablerow_open' ) { 1274 1275 $lastRow = $key; 1276 1277 } else if ( $call[0] == 'tablecell_open' || $call[0] == 'tableheader_open' ) { 1278 1279 $lastCell = $key; 1280 1281 } else if ( $call[0] == 'table_align' ) { 1282 1283 // If the previous element was a cell open, align right 1284 if ( $this->tableCalls[$key-1][0] == 'tablecell_open' || $this->tableCalls[$key-1][0] == 'tableheader_open' ) { 1285 $this->tableCalls[$key-1][1][1] = 'right'; 1286 1287 // If the next element if the close of an element, align either center or left 1288 } else if ( $this->tableCalls[$key+1][0] == 'tablecell_close' || $this->tableCalls[$key+1][0] == 'tableheader_close' ) { 1289 if ( $this->tableCalls[$lastCell][1][1] == 'right' ) { 1290 $this->tableCalls[$lastCell][1][1] = 'center'; 1291 } else { 1292 $this->tableCalls[$lastCell][1][1] = 'left'; 1293 } 1294 1295 } 1296 1297 // Now convert the whitespace back to cdata 1298 $this->tableCalls[$key][0] = 'cdata'; 1299 1300 } else if ( $call[0] == 'colspan' ) { 1301 1302 $this->tableCalls[$key-1][1][0] = false; 1303 1304 for($i = $key-2; $i > $lastRow; $i--) { 1305 1306 if ( $this->tableCalls[$i][0] == 'tablecell_open' || $this->tableCalls[$i][0] == 'tableheader_open' ) { 1307 1308 if ( false !== $this->tableCalls[$i][1][0] ) { 1309 $this->tableCalls[$i][1][0]++; 1310 break; 1311 } 1312 1313 1314 } 1315 } 1316 1317 $toDelete[] = $key-1; 1318 $toDelete[] = $key; 1319 $toDelete[] = $key+1; 1320 } 1321 } 1322 1323 1324 // condense cdata 1325 $cnt = count($this->tableCalls); 1326 for( $key = 0; $key < $cnt; $key++){ 1327 if($this->tableCalls[$key][0] == 'cdata'){ 1328 $ckey = $key; 1329 $key++; 1330 while($this->tableCalls[$key][0] == 'cdata'){ 1331 $this->tableCalls[$ckey][1][0] .= $this->tableCalls[$key][1][0]; 1332 $toDelete[] = $key; 1333 $key++; 1334 } 1335 continue; 1336 } 1337 } 1338 1339 foreach ( $toDelete as $delete ) { 1340 unset($this->tableCalls[$delete]); 1341 } 1342 $this->tableCalls = array_values($this->tableCalls); 1343 } 1344} 1345 1346//------------------------------------------------------------------------ 1347class Doku_Handler_Section { 1348 1349 function process($calls) { 1350 1351 $sectionCalls = array(); 1352 $inSection = false; 1353 1354 foreach ( $calls as $call ) { 1355 1356 if ( $call[0] == 'header' ) { 1357 1358 if ( $inSection ) { 1359 $sectionCalls[] = array('section_close',array(), $call[2]); 1360 } 1361 1362 $sectionCalls[] = $call; 1363 $sectionCalls[] = array('section_open',array($call[1][1]), $call[2]); 1364 $inSection = true; 1365 1366 } else { 1367 1368 if ($call[0] == 'section_open' ) { 1369 $inSection = true; 1370 } else if ($call[0] == 'section_open' ) { 1371 $inSection = false; 1372 } 1373 $sectionCalls[] = $call; 1374 } 1375 } 1376 1377 if ( $inSection ) { 1378 $sectionCalls[] = array('section_close',array(), $call[2]); 1379 } 1380 1381 return $sectionCalls; 1382 } 1383 1384} 1385 1386/** 1387 * Handler for paragraphs 1388 * 1389 * @author Harry Fuecks <hfuecks@gmail.com> 1390 */ 1391class Doku_Handler_Block { 1392 1393 var $calls = array(); 1394 1395 var $blockStack = array(); 1396 1397 var $inParagraph = false; 1398 var $atStart = true; 1399 var $skipEolKey = -1; 1400 1401 // Blocks these should not be inside paragraphs 1402 var $blockOpen = array( 1403 'header', 1404 'listu_open','listo_open','listitem_open','listcontent_open', 1405 'table_open','tablerow_open','tablecell_open','tableheader_open', 1406 'quote_open', 1407 'section_open', // Needed to prevent p_open between header and section_open 1408 'code','file','hr','preformatted','rss', 1409 ); 1410 1411 var $blockClose = array( 1412 'header', 1413 'listu_close','listo_close','listitem_close','listcontent_close', 1414 'table_close','tablerow_close','tablecell_close','tableheader_close', 1415 'quote_close', 1416 'section_close', // Needed to prevent p_close after section_close 1417 'code','file','hr','preformatted','rss', 1418 ); 1419 1420 // Stacks can contain paragraphs 1421 var $stackOpen = array( 1422 'footnote_open','section_open', 1423 ); 1424 1425 var $stackClose = array( 1426 'footnote_close','section_close', 1427 ); 1428 1429 1430 /** 1431 * Constructor. Adds loaded syntax plugins to the block and stack 1432 * arrays 1433 * 1434 * @author Andreas Gohr <andi@splitbrain.org> 1435 */ 1436 function Doku_Handler_Block(){ 1437 global $DOKU_PLUGINS; 1438 //check if syntax plugins were loaded 1439 if(empty($DOKU_PLUGINS['syntax'])) return; 1440 foreach($DOKU_PLUGINS['syntax'] as $n => $p){ 1441 $ptype = $p->getPType(); 1442 if($ptype == 'block'){ 1443 $this->blockOpen[] = 'plugin_'.$n; 1444 $this->blockClose[] = 'plugin_'.$n; 1445 }elseif($ptype == 'stack'){ 1446 $this->stackOpen[] = 'plugin_'.$n; 1447 $this->stackClose[] = 'plugin_'.$n; 1448 } 1449 } 1450 } 1451 1452 /** 1453 * Close a paragraph if needed 1454 * 1455 * This function makes sure there are no empty paragraphs on the stack 1456 * 1457 * @author Andreas Gohr <andi@splitbrain.org> 1458 */ 1459 function closeParagraph($pos){ 1460 // look back if there was any content - we don't want empty paragraphs 1461 $content = ''; 1462 for($i=count($this->calls)-1; $i>=0; $i--){ 1463 if($this->calls[$i][0] == 'p_open'){ 1464 break; 1465 }elseif($this->calls[$i][0] == 'cdata'){ 1466 $content .= $this->calls[$i][1][0]; 1467 }else{ 1468 $content = 'found markup'; 1469 break; 1470 } 1471 } 1472 1473 if(trim($content)==''){ 1474 //remove the whole paragraph 1475 array_splice($this->calls,$i); 1476 }else{ 1477 if ($this->calls[count($this->calls)-1][0] == 'section_edit') { 1478 $tmp = array_pop($this->calls); 1479 $this->calls[] = array('p_close',array(), $pos); 1480 $this->calls[] = $tmp; 1481 } else { 1482 $this->calls[] = array('p_close',array(), $pos); 1483 } 1484 } 1485 1486 $this->inParagraph = false; 1487 } 1488 1489 /** 1490 * Processes the whole instruction stack to open and close paragraphs 1491 * 1492 * @author Harry Fuecks <hfuecks@gmail.com> 1493 * @author Andreas Gohr <andi@splitbrain.org> 1494 * @todo This thing is really messy and should be rewritten 1495 */ 1496 function process($calls) { 1497 foreach ( $calls as $key => $call ) { 1498 $cname = $call[0]; 1499 if($cname == 'plugin') { 1500 $cname='plugin_'.$call[1][0]; 1501 1502 $plugin = true; 1503 $plugin_open = (($call[1][2] == DOKU_LEXER_ENTER) || ($call[1][2] == DOKU_LEXER_SPECIAL)); 1504 $plugin_close = (($call[1][2] == DOKU_LEXER_EXIT) || ($call[1][2] == DOKU_LEXER_SPECIAL)); 1505 } else { 1506 $plugin = false; 1507 } 1508 1509 // Process blocks which are stack like... (contain linefeeds) 1510 if ( in_array($cname,$this->stackOpen ) && (!$plugin || $plugin_open) ) { 1511 1512 $this->calls[] = $call; 1513 1514 // Hack - footnotes shouldn't immediately contain a p_open 1515 if ( $cname != 'footnote_open' ) { 1516 $this->addToStack(); 1517 } else { 1518 $this->addToStack(false); 1519 } 1520 continue; 1521 } 1522 1523 if ( in_array($cname,$this->stackClose ) && (!$plugin || $plugin_close)) { 1524 1525 if ( $this->inParagraph ) { 1526 $this->closeParagraph($call[2]); 1527 } 1528 $this->calls[] = $call; 1529 $this->removeFromStack(); 1530 continue; 1531 } 1532 1533 if ( !$this->atStart ) { 1534 1535 if ( $cname == 'eol' ) { 1536 1537 // Check this isn't an eol instruction to skip... 1538 if ( $this->skipEolKey != $key ) { 1539 // Look to see if the next instruction is an EOL 1540 if ( isset($calls[$key+1]) && $calls[$key+1][0] == 'eol' ) { 1541 1542 if ( $this->inParagraph ) { 1543 //$this->calls[] = array('p_close',array(), $call[2]); 1544 $this->closeParagraph($call[2]); 1545 } 1546 1547 $this->calls[] = array('p_open',array(), $call[2]); 1548 $this->inParagraph = true; 1549 1550 1551 // Mark the next instruction for skipping 1552 $this->skipEolKey = $key+1; 1553 1554 }else{ 1555 //if this is just a single eol make a space from it 1556 $this->calls[] = array('cdata',array(" "), $call[2]); 1557 } 1558 } 1559 1560 1561 } else { 1562 1563 $storeCall = true; 1564 if ( $this->inParagraph && (in_array($cname, $this->blockOpen) && (!$plugin || $plugin_open))) { 1565 $this->closeParagraph($call[2]); 1566 $this->calls[] = $call; 1567 $storeCall = false; 1568 } 1569 1570 if ( in_array($cname, $this->blockClose) && (!$plugin || $plugin_close)) { 1571 if ( $this->inParagraph ) { 1572 $this->closeParagraph($call[2]); 1573 } 1574 if ( $storeCall ) { 1575 $this->calls[] = $call; 1576 $storeCall = false; 1577 } 1578 1579 // This really sucks and suggests this whole class sucks but... 1580 if ( isset($calls[$key+1])) { 1581 $cname_plusone = $calls[$key+1][0]; 1582 if ($cname_plusone == 'plugin') { 1583 $cname_plusone = 'plugin'.$calls[$key+1][1][0]; 1584 1585 // plugin test, true if plugin has a state which precludes it requiring blockOpen or blockClose 1586 $plugin_plusone = true; 1587 $plugin_test = ($call[$key+1][1][2] == DOKU_LEXER_MATCHED) || ($call[$key+1][1][2] == DOKU_LEXER_MATCHED); 1588 } else { 1589 $plugin_plusone = false; 1590 } 1591 if ((!in_array($cname_plusone, $this->blockOpen) && !in_array($cname_plusone, $this->blockClose)) || 1592 ($plugin_plusone && $plugin_test) 1593 ) { 1594 1595 $this->calls[] = array('p_open',array(), $call[2]); 1596 $this->inParagraph = true; 1597 } 1598 } 1599 } 1600 1601 if ( $storeCall ) { 1602 $this->calls[] = $call; 1603 } 1604 1605 } 1606 1607 1608 } else { 1609 1610 // Unless there's already a block at the start, start a paragraph 1611 if ( !in_array($cname,$this->blockOpen) ) { 1612 $this->calls[] = array('p_open',array(), $call[2]); 1613 if ( $call[0] != 'eol' ) { 1614 $this->calls[] = $call; 1615 } 1616 $this->atStart = false; 1617 $this->inParagraph = true; 1618 } else { 1619 $this->calls[] = $call; 1620 $this->atStart = false; 1621 } 1622 1623 } 1624 1625 } 1626 1627 if ( $this->inParagraph ) { 1628 if ( $cname == 'p_open' ) { 1629 // Ditch the last call 1630 array_pop($this->calls); 1631 } else if ( !in_array($cname, $this->blockClose) ) { 1632 //$this->calls[] = array('p_close',array(), $call[2]); 1633 $this->closeParagraph($call[2]); 1634 } else { 1635 $last_call = array_pop($this->calls); 1636 //$this->calls[] = array('p_close',array(), $call[2]); 1637 $this->closeParagraph($call[2]); 1638 $this->calls[] = $last_call; 1639 } 1640 } 1641 1642 return $this->calls; 1643 } 1644 1645 function addToStack($newStart = true) { 1646 $this->blockStack[] = array($this->atStart, $this->inParagraph); 1647 $this->atStart = $newStart; 1648 $this->inParagraph = false; 1649 } 1650 1651 function removeFromStack() { 1652 $state = array_pop($this->blockStack); 1653 $this->atStart = $state[0]; 1654 $this->inParagraph = $state[1]; 1655 } 1656} 1657 1658//Setup VIM: ex: et ts=4 enc=utf-8 : 1659