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