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