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