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