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