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 358 // If the title is an image, convert it to an array containing the image details 359 } else if ( preg_match('/^\{\{[^\}]+\}\}$/',$link[1]) ) { 360 $link[1] = Doku_Handler_Parse_Media($link[1]); 361 } 362 363 //decide which kind of link it is 364 365 if ( preg_match('/^[a-zA-Z]+>{1}[\w()\/\\#~:.?+=&%@!\-;,]+$/u',$link[0]) ) { 366 // Interwiki 367 $interwiki = preg_split('/>/u',$link[0]); 368 $this->__addCall( 369 'interwikilink', 370 array($link[0],$link[1],strtolower($interwiki[0]),$interwiki[1]), 371 $pos 372 ); 373 }elseif ( preg_match('/\\\\\\\\[\w.:?\-;,]+?\\\\/u',$link[0]) ) { 374 // Windows Share 375 $this->__addCall( 376 'windowssharelink', 377 array($link[0],$link[1]), 378 $pos 379 ); 380 }elseif ( preg_match('#([a-z0-9\-_.]+?)@([\w\-]+\.([\w\-\.]+\.)*[\w]+)#i',$link[0]) ) { 381 // E-Mail 382 $this->__addCall( 383 'emaillink', 384 array($link[0],$link[1]), 385 $pos 386 ); 387 }elseif ( preg_match('#^([a-z0-9]+?)://#i',$link[0]) ) { 388 // external link (accepts all protocols) 389 $this->__addCall( 390 'externallink', 391 array($link[0],$link[1]), 392 $pos 393 ); 394 }else{ 395 // internal link 396 $this->__addCall( 397 'internallink', 398 array($link[0],$link[1]), 399 $pos 400 ); 401 } 402 403 return TRUE; 404 } 405 406 function filelink($match, $state, $pos) { 407 $this->__addCall('filelink',array($match, NULL), $pos); 408 return TRUE; 409 } 410 411 function windowssharelink($match, $state, $pos) { 412 $this->__addCall('windowssharelink',array($match, NULL), $pos); 413 return TRUE; 414 } 415 416 function media($match, $state, $pos) { 417 $p = Doku_Handler_Parse_Media($match); 418 419 $this->__addCall( 420 $p['type'], 421 array($p['src'], $p['title'], $p['align'], $p['width'], $p['height'], $p['cache']), 422 $pos 423 ); 424 return TRUE; 425 } 426 427 function rss($match, $state, $pos) { 428 $link = preg_replace(array('/^\{\{rss>/','/\}\}$/'),'',$match); 429 $this->__addCall('rss',array($link),$pos); 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//------------------------------------------------------------------------ 1151class Doku_Handler_Block { 1152 1153 var $calls = array(); 1154 1155 var $blockStack = array(); 1156 1157 var $inParagraph = FALSE; 1158 var $atStart = TRUE; 1159 1160 // Blocks don't contain linefeeds 1161 var $blockOpen = array( 1162 'header', 1163 'listu_open','listo_open','listitem_open', 1164 'table_open','tablerow_open','tablecell_open','tableheader_open', 1165 'quote_open', 1166 'section_open', // Needed to prevent p_open between header and section_open 1167 'code','file','php','html','hr','preformatted', 1168 ); 1169 1170 var $blockClose = array( 1171 'header', 1172 'listu_close','listo_close','listitem_close', 1173 'table_close','tablerow_close','tablecell_close','tableheader_close', 1174 'quote_close', 1175 'section_close', // Needed to prevent p_close after section_close 1176 'code','file','php','html','hr','preformatted', 1177 ); 1178 1179 // Stacks can contain linefeeds 1180 var $stackOpen = array( 1181 'footnote_open','section_open', 1182 ); 1183 1184 var $stackClose = array( 1185 'footnote_close','section_close', 1186 ); 1187 1188 function process($calls) { 1189 foreach ( $calls as $key => $call ) { 1190 1191 // Process blocks which are stack like... (contain linefeeds) 1192 if ( in_array($call[0],$this->stackOpen ) ) { 1193 /* 1194 if ( $this->atStart ) { 1195 $this->calls[] = array('p_open',array(), $call[2]); 1196 $this->atStart = FALSE; 1197 $this->inParagraph = TRUE; 1198 } 1199 */ 1200 $this->calls[] = $call; 1201 1202 // Hack - footnotes shouldn't immediately contain a p_open 1203 if ( $call[0] != 'footnote_open' ) { 1204 $this->addToStack(); 1205 } else { 1206 $this->addToStack(FALSE); 1207 } 1208 continue; 1209 } 1210 1211 if ( in_array($call[0],$this->stackClose ) ) { 1212 1213 if ( $this->inParagraph ) { 1214 $this->calls[] = array('p_close',array(), $call[2]); 1215 } 1216 $this->calls[] = $call; 1217 $this->removeFromStack(); 1218 continue; 1219 } 1220 1221 if ( !$this->atStart ) { 1222 1223 if ( $call[0] == 'eol' ) { 1224 1225 if ( $this->inParagraph ) { 1226 $this->calls[] = array('p_close',array(), $call[2]); 1227 } 1228 $this->calls[] = array('p_open',array(), $call[2]); 1229 $this->inParagraph = TRUE; 1230 1231 } else { 1232 1233 $storeCall = TRUE; 1234 1235 if ( $this->inParagraph && in_array($call[0], $this->blockOpen) ) { 1236 $this->calls[] = array('p_close',array(), $call[2]); 1237 $this->inParagraph = FALSE; 1238 $this->calls[] = $call; 1239 $storeCall = FALSE; 1240 } 1241 1242 if ( in_array($call[0], $this->blockClose) ) { 1243 if ( $this->inParagraph ) { 1244 $this->calls[] = array('p_close',array(), $call[2]); 1245 $this->inParagraph = FALSE; 1246 } 1247 if ( $storeCall ) { 1248 $this->calls[] = $call; 1249 $storeCall = FALSE; 1250 } 1251 1252 // This really sucks and suggests this whole class sucks but... 1253 if ( isset($calls[$key+1]) 1254 && 1255 !in_array($calls[$key+1][0], $this->blockOpen) 1256 && 1257 !in_array($calls[$key+1][0], $this->blockClose) 1258 ) { 1259 1260 $this->calls[] = array('p_open',array(), $call[2]); 1261 $this->inParagraph = TRUE; 1262 } 1263 } 1264 1265 if ( $storeCall ) { 1266 $this->calls[] = $call; 1267 } 1268 1269 } 1270 1271 1272 } else { 1273 1274 // Unless there's already a block at the start, start a paragraph 1275 if ( !in_array($call[0],$this->blockOpen) ) { 1276 $this->calls[] = array('p_open',array(), $call[2]); 1277 if ( $call[0] != 'eol' ) { 1278 $this->calls[] = $call; 1279 } 1280 $this->atStart = FALSE; 1281 $this->inParagraph = TRUE; 1282 } else { 1283 $this->calls[] = $call; 1284 $this->atStart = FALSE; 1285 } 1286 1287 } 1288 1289 } 1290 1291 if ( $this->inParagraph ) { 1292 if ( $call[0] == 'p_open' ) { 1293 // Ditch the last call 1294 array_pop($this->calls); 1295 } else if ( !in_array($call[0], $this->blockClose) ) { 1296 $this->calls[] = array('p_close',array(), $call[2]); 1297 } else { 1298 $last_call = array_pop($this->calls); 1299 $this->calls[] = array('p_close',array(), $call[2]); 1300 $this->calls[] = $last_call; 1301 } 1302 } 1303 1304 return $this->calls; 1305 } 1306 1307 function addToStack($newStart = TRUE) { 1308 $this->blockStack[] = array($this->atStart, $this->inParagraph); 1309 $this->atStart = $newStart; 1310 $this->inParagraph = FALSE; 1311 } 1312 1313 function removeFromStack() { 1314 $state = array_pop($this->blockStack); 1315 $this->atStart = $state[0]; 1316 $this->inParagraph = $state[1]; 1317 } 1318} 1319//------------------------------------------------------------------------ 1320define('DOKU_TOC_OPEN',1); 1321define('DOKU_TOCBRANCH_OPEN',2); 1322define('DOKU_TOCITEM_OPEN',3); 1323define('DOKU_TOC_ELEMENT',4); 1324define('DOKU_TOCITEM_CLOSE',5); 1325define('DOKU_TOCBRANCH_CLOSE',6); 1326define('DOKU_TOC_CLOSE',7); 1327 1328class Doku_Handler_Toc { 1329 1330 var $calls = array(); 1331 var $tocStack = array(); 1332 var $toc = array(); 1333 var $numHeaders = 0; 1334 1335 function process($calls) { 1336 #FIXME can this be done better? 1337 global $conf; 1338 1339 foreach ( $calls as $call ) { 1340 if ( $call[0] == 'header' && $call[1][1] <= $conf['maxtoclevel'] ) { 1341 $this->numHeaders++; 1342 $this->addToToc($call); 1343 } 1344 $this->calls[] = $call; 1345 } 1346 1347 // Complete the table of contents then prepend to the calls 1348 $this->finalizeToc($call); 1349 return $this->calls; 1350 } 1351 1352 function addToToc($call) { 1353 1354 $depth = $call[1][1]; 1355 1356 // If it's the opening item... 1357 if ( count ( $this->toc) == 0 ) { 1358 1359 $this->addTocCall($call, DOKU_TOC_OPEN); 1360 1361 for ( $i = 1; $i <= $depth; $i++ ) { 1362 1363 $this->addTocCall(array($call[0],array($call[1][0],$i),$call[2]), DOKU_TOCBRANCH_OPEN); 1364 1365 if ( $i != $depth ) { 1366 $this->addTocCall(array($call[0],array($call[1][0], $i, TRUE),$call[2]), DOKU_TOCITEM_OPEN); 1367 } else { 1368 $this->addTocCall(array($call[0],array($call[1][0], $i),$call[2]), DOKU_TOCITEM_OPEN); 1369 $this->addTocCall(array($call[0],array($call[1][0], $i),$call[2]), DOKU_TOC_ELEMENT); 1370 } 1371 1372 $this->tocStack[] = $i; 1373 1374 } 1375 return; 1376 } 1377 1378 $currentDepth = end($this->tocStack); 1379 $initialDepth = $currentDepth; 1380 1381 // Create new branches as needed 1382 if ( $depth > $currentDepth ) { 1383 1384 for ($i = $currentDepth+1; $i <= $depth; $i++ ) { 1385 $this->addTocCall(array($call[0],array($call[1][0],$i),$call[2]), DOKU_TOCBRANCH_OPEN); 1386 // It's just a filler 1387 if ( $i != $depth ) { 1388 $this->addTocCall(array($call[0],array($call[1][0], $i, TRUE),$call[2]), DOKU_TOCITEM_OPEN); 1389 } else { 1390 $this->addTocCall(array($call[0],array($call[1][0], $i),$call[2]), DOKU_TOCITEM_OPEN); 1391 } 1392 $this->tocStack[] = $i; 1393 } 1394 1395 $currentDepth = $i-1; 1396 1397 } 1398 1399 // Going down 1400 if ( $depth < $currentDepth ) { 1401 for ( $i = $currentDepth; $i >= $depth; $i-- ) { 1402 if ( $i != $depth ) { 1403 array_pop($this->tocStack); 1404 $this->addTocCall(array($call[0],array($call[1][0],$i),$call[2]), DOKU_TOCITEM_CLOSE); 1405 $this->addTocCall(array($call[0],array($call[1][0],$i),$call[2]), DOKU_TOCBRANCH_CLOSE); 1406 } else { 1407 $this->addTocCall(array($call[0],array($call[1][0],$i),$call[2]), DOKU_TOCITEM_CLOSE); 1408 $this->addTocCall(array($call[0],array($call[1][0],$i),$call[2]), DOKU_TOCITEM_OPEN); 1409 $this->addTocCall($call, DOKU_TOC_ELEMENT); 1410 return; 1411 } 1412 } 1413 } 1414 1415 if ( $depth == $initialDepth ) { 1416 $this->addTocCall($call, DOKU_TOCITEM_CLOSE); 1417 $this->addTocCall($call, DOKU_TOCITEM_OPEN); 1418 } 1419 1420 $this->addTocCall($call, DOKU_TOC_ELEMENT); 1421 1422 1423 } 1424 1425 function addTocCall($call, $type) { 1426 switch ( $type ) { 1427 case DOKU_TOC_OPEN: 1428 $this->toc[] = array('toc_open',array(),$call[2]); 1429 break; 1430 1431 case DOKU_TOCBRANCH_OPEN: 1432 $this->toc[] = array('tocbranch_open',array($call[1][1]),$call[2]); 1433 break; 1434 1435 case DOKU_TOCITEM_OPEN: 1436 if ( isset( $call[1][2] ) ) { 1437 $this->toc[] = array('tocitem_open',array($call[1][1], TRUE),$call[2]); 1438 } else { 1439 $this->toc[] = array('tocitem_open',array($call[1][1]),$call[2]); 1440 } 1441 break; 1442 1443 case DOKU_TOC_ELEMENT: 1444 $this->toc[] = array('tocelement',array($call[1][1],$call[1][0]),$call[2]); 1445 break; 1446 1447 case DOKU_TOCITEM_CLOSE: 1448 $this->toc[] = array('tocitem_close',array($call[1][1]),$call[2]); 1449 break; 1450 1451 case DOKU_TOCBRANCH_CLOSE: 1452 $this->toc[] = array('tocbranch_close',array($call[1][1]),$call[2]); 1453 break; 1454 1455 case DOKU_TOC_CLOSE: 1456 if ( count($this->toc) > 0 ) { 1457 $this->toc[] = array('toc_close',array(),$call[2]); 1458 } 1459 break; 1460 } 1461 } 1462 1463 function finalizeToc($call) { 1464 if ( $this->numHeaders < 3 ) { 1465 return; 1466 } 1467 if ( count ($this->tocStack) > 0 ) { 1468 while ( NULL !== ($toc = array_pop($this->tocStack)) ) { 1469 $this->addTocCall(array($call[0],array('',$toc),$call[2]), DOKU_TOCITEM_CLOSE); 1470 $this->addTocCall(array($call[0],array('',$toc),$call[2]), DOKU_TOCBRANCH_CLOSE); 1471 } 1472 } 1473 $this->addTocCall($call, DOKU_TOC_CLOSE); 1474 $this->calls = array_merge($this->toc, $this->calls); 1475 } 1476 1477} 1478 1479 1480//Setup VIM: ex: et ts=4 enc=utf-8 : 1481