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