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