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