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