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