1<?php 2if(!defined('DOKU_INC')) define('DOKU_INC',realpath(dirname(__FILE__).'/../../').'/'); 3 4if ( !defined('DOKU_LF') ) { 5 // Some whitespace to help View > Source 6 define ('DOKU_LF',"\n"); 7} 8 9if ( !defined('DOKU_TAB') ) { 10 // Some whitespace to help View > Source 11 define ('DOKU_TAB',"\t"); 12} 13 14/** 15* @TODO Probably useful for have constant for linefeed formatting 16*/ 17class Doku_Renderer_XHTML { 18 19 var $doc = ''; 20 21 var $headers = array(); 22 23 var $footnotes = array(); 24 25 var $footnoteIdStack = array(); 26 27 var $acronyms = array(); 28 var $smileys = array(); 29 var $badwords = array(); 30 var $entities = array(); 31 var $interwiki = array(); 32 33 //@todo why ob? 34 function document_start() { 35 ob_start(); 36 } 37 38 function document_end() { 39 40 if ( count ($this->footnotes) > 0 ) { 41 echo '<div class="footnotes">'.DOKU_LF; 42 foreach ( $this->footnotes as $footnote ) { 43 echo $footnote; 44 } 45 echo '</div>'.DOKU_LF; 46 } 47 48 $this->doc .= ob_get_contents(); 49 ob_end_clean(); 50 51 } 52 53 function toc_open() { 54 echo '<div class="toc">'.DOKU_LF; 55 echo '<div class="tocheader">Table of Contents <script type="text/javascript">showTocToggle("+","-")</script></div>'.DOKU_LF; 56 echo '<div id="tocinside">'.DOKU_LF; 57 } 58 59 function tocbranch_open($level) { 60 echo '<ul class="toc">'.DOKU_LF; 61 } 62 63 function tocitem_open($level, $empty = FALSE) { 64 if ( !$empty ) { 65 echo '<li class="level'.$level.'">'; 66 } else { 67 echo '<li class="clear">'; 68 } 69 } 70 71 function tocelement($level, $title) { 72 echo '<span class="li"><a href="#'.$this->__headerToLink($title).'" class="toc">'; 73 echo $this->__xmlEntities($title); 74 echo '</a></span>'; 75 } 76 77 function tocitem_close($level) { 78 echo '</li>'.DOKU_LF; 79 } 80 81 function tocbranch_close($level) { 82 echo '</ul>'.DOKU_LF; 83 } 84 85 function toc_close() { 86 echo '</div>'.DOKU_LF.'</div>'.DOKU_LF; 87 } 88 89 function header($text, $level) { 90 echo DOKU_LF.'<a name="'.$this->__headerToLink($text).'"></a><h'.$level.'>'; 91 echo $this->__xmlEntities($text); 92 echo "</h$level>".DOKU_LF; 93 } 94 95 function section_open($level) { 96 echo "<div class=\"level$level\">".DOKU_LF; 97 } 98 99 function section_close() { 100 echo DOKU_LF.'</div>'.DOKU_LF; 101 } 102 103 function cdata($text) { 104 echo $this->__xmlEntities($text); 105 } 106 107 function p_open() { 108 echo DOKU_LF.'<p>'.DOKU_LF; 109 } 110 111 function p_close() { 112 echo DOKU_LF.'</p>'.DOKU_LF; 113 } 114 115 function linebreak() { 116 echo '<br/>'.DOKU_LF; 117 } 118 119 function hr() { 120 echo '<hr noshade="noshade" size="1" />'.DOKU_LF; 121 } 122 123 function strong_open() { 124 echo '<strong>'; 125 } 126 127 function strong_close() { 128 echo '</strong>'; 129 } 130 131 function emphasis_open() { 132 echo '<em>'; 133 } 134 135 function emphasis_close() { 136 echo '</em>'; 137 } 138 139 function underline_open() { 140 echo '<u>'; 141 } 142 143 function underline_close() { 144 echo '</u>'; 145 } 146 147 function monospace_open() { 148 echo '<code>'; 149 } 150 151 function monospace_close() { 152 echo '</code>'; 153 } 154 155 function subscript_open() { 156 echo '<sub>'; 157 } 158 159 function subscript_close() { 160 echo '</sub>'; 161 } 162 163 function superscript_open() { 164 echo '<sup>'; 165 } 166 167 function superscript_close() { 168 echo '</sup>'; 169 } 170 171 function deleted_open() { 172 echo '<del>'; 173 } 174 175 function deleted_close() { 176 echo '</del>'; 177 } 178 179 function footnote_open() { 180 $id = $this->__newFootnoteId(); 181 echo '<a href="#fn'.$id.'" name="fnt'.$id.'" class="fn_top">'.$id.')</a>'; 182 $this->footnoteIdStack[] = $id; 183 ob_start(); 184 } 185 186 function footnote_close() { 187 $contents = ob_get_contents(); 188 ob_end_clean(); 189 $id = array_pop($this->footnoteIdStack); 190 191 $contents = '<div class="fn"><a href="#fnt'. 192 $id.'" name="fn'.$id.'" class="fn_bot">'. 193 $id.')</a> ' .DOKU_LF .$contents. "\n" . '</div>' . DOKU_LF; 194 $this->footnotes[$id] = $contents; 195 } 196 197 function listu_open() { 198 echo '<ul>'.DOKU_LF; 199 } 200 201 function listu_close() { 202 echo '</ul>'.DOKU_LF; 203 } 204 205 function listo_open() { 206 echo '<ol>'.DOKU_LF; 207 } 208 209 function listo_close() { 210 echo '</ol>'.DOKU_LF; 211 } 212 213 function listitem_open($level) { 214 echo '<li class="level'.$level.'">'; 215 } 216 217 function listitem_close() { 218 echo '</li>'.DOKU_LF; 219 } 220 221 function listcontent_open() { 222 echo '<span class="li">'; 223 } 224 225 function listcontent_close() { 226 echo '</span>'.DOKU_LF; 227 } 228 229 function unformatted($text) { 230 echo $this->__xmlEntities($text); 231 } 232 233 /** 234 * @TODO Support optional eval of code depending on conf/dokuwiki.php 235 */ 236 function php($text) { 237 $this->preformatted($text); 238 } 239 240 /** 241 * @TODO Support optional echo of HTML depending on conf/dokuwiki.php 242 */ 243 function html($text) { 244 $this->file($text); 245 } 246 247 function preformatted($text) { 248 echo '<pre class="code">' . $this->__xmlEntities($text) . '</pre>'. DOKU_LF; 249 } 250 251 function file($text) { 252 echo '<pre class="file">' . $this->__xmlEntities($text). '</pre>'. DOKU_LF; 253 } 254 255 /** 256 * @TODO Shouldn't this output <blockquote?? 257 */ 258 function quote_open() { 259 echo '<div class="quote">'.DOKU_LF; 260 } 261 262 /** 263 * @TODO Shouldn't this output </blockquote>? 264 */ 265 function quote_close() { 266 echo '</div>'.DOKU_LF; 267 } 268 269 /** 270 * @TODO Hook up correctly with Geshi 271 */ 272 function code($text, $language = NULL) { 273 274 if ( is_null($language) ) { 275 $this->preformatted($text); 276 } else { 277 278 // Handle with Geshi here (needs tuning) 279 require_once(DOKU_INC . 'geshi.php'); 280 $geshi = new GeSHi($text, strtolower($language), DOKU_INC . 'geshi'); 281 $geshi->enable_classes(); 282 $geshi->set_header_type(GESHI_HEADER_PRE); 283 $geshi->set_overall_class('code'); 284 285 // Fix this 286 $geshi->set_link_target('_blank'); 287 288 $text = $geshi->parse_code(); 289 echo $text; 290 } 291 } 292 293 function acronym($acronym) { 294 295 if ( array_key_exists($acronym, $this->acronyms) ) { 296 297 $title = $this->__xmlEntities($this->acronyms[$acronym]); 298 299 echo '<acronym title="'.$title 300 .'">'.$this->__xmlEntities($acronym).'</acronym>'; 301 302 } else { 303 echo $this->__xmlEntities($acronym); 304 } 305 } 306 307 /** 308 * @TODO Remove hard coded link to splitbrain.org 309 */ 310 function smiley($smiley) { 311 312 if ( array_key_exists($smiley, $this->smileys) ) { 313 $title = $this->__xmlEntities($this->smileys[$smiley]); 314 echo '<img src="http://wiki.splitbrain.org/smileys/'.$this->smileys[$smiley]. 315 '" align="middle" alt="'. 316 $this->__xmlEntities($smiley).'" />'; 317 } else { 318 echo $this->__xmlEntities($smiley); 319 } 320 } 321 322 /** 323 * @TODO localization? 324 */ 325 function wordblock($word) { 326 if ( array_key_exists($word, $this->badwords) ) { 327 echo '** BLEEP **'; 328 } else { 329 echo $this->__xmlEntities($word); 330 } 331 } 332 333 function entity($entity) { 334 if ( array_key_exists($entity, $this->entities) ) { 335 echo $this->entities[$entity]; 336 } else { 337 echo $this->__xmlEntities($entity); 338 } 339 } 340 341 function multiplyentity($x, $y) { 342 echo "$x×$y"; 343 } 344 345 function singlequoteopening() { 346 echo "‘"; 347 } 348 349 function singlequoteclosing() { 350 echo "’"; 351 } 352 353 function doublequoteopening() { 354 echo "“"; 355 } 356 357 function doublequoteclosing() { 358 echo "”"; 359 } 360 361 /** 362 * @TODO Handle local vs. global namespace checks 363 */ 364 function camelcaselink($link) { 365 366 echo '<a href="'.$link.'"'; 367 368 if ( wikiPageExists($link) ) { 369 echo ' class="wikilink1"'; 370 } else { 371 echo ' class="wikilink2"'; 372 } 373 374 // Probably dont need to convert entities - parser would have rejected it 375 echo ' onclick="return svchk()" onkeypress="return svchk()">'; 376 echo $this->__xmlEntities($link); 377 echo '</a>'; 378 } 379 380 /** 381 * @TODO Hook up with page resolver. 382 * @TODO Support media 383 * @TODO correct attributes 384 */ 385 function internallink($link, $title = NULL) { 386 387 echo '<a'; 388 389 $title = $this->__getLinkTitle($title,$link, $isImage); 390 391 if ( !$isImage ) { 392 393 if ( wikiPageExists($link) ) { 394 echo ' class="wikilink1"'; 395 } else { 396 echo ' class="wikilink2"'; 397 } 398 399 } else { 400 echo ' class="media"'; 401 } 402 403 echo ' href="http://wiki.splitbrain.org/'.$this->__xmlEntities($link).'"'; 404 405 echo ' onclick="return svchk()" onkeypress="return svchk()">'; 406 407 echo $title; 408 409 echo '</a>'; 410 } 411 412 413 /** 414 * @TODO Should list assume blacklist check already made? 415 * @TODO External link icon 416 * @TODO correct attributes 417 */ 418 function externallink($link, $title = NULL) { 419 420 echo '<a'; 421 422 $title = $this->__getLinkTitle($title, $link, $isImage); 423 424 if ( !$isImage ) { 425 echo ' class="urlextern"'; 426 } else { 427 echo ' class="media"'; 428 } 429 430 echo ' target="_blank" href="'.$this->__xmlEntities($link).'"'; 431 432 echo ' onclick="return svchk()" onkeypress="return svchk()">'; 433 434 echo $title; 435 436 echo '</a>'; 437 } 438 439 /** 440 * @TODO Remove hard coded link to splitbrain.org on style 441 */ 442 function interwikilink($link, $title = NULL, $wikiName, $wikiUri) { 443 444 // RESOLVE THE URL 445 if ( isset($this->interwiki[$wikiName]) ) { 446 447 $wikiUriEnc = urlencode($wikiUri); 448 449 if ( strstr($this->interwiki[$wikiName],'{URL}' ) !== FALSE ) { 450 451 $url = str_replace('{URL}', $wikiUriEnc, $this->interwiki[$wikiName] ); 452 453 } else if ( strstr($this->interwiki[$wikiName],'{NAME}' ) !== FALSE ) { 454 455 $url = str_replace('{NAME}', $wikiUriEnc, $this->interwiki[$wikiName] ); 456 457 } else { 458 459 $url = $this->interwiki[$wikiName] . urlencode($wikiUri); 460 461 } 462 463 } else { 464 // Default to Google I'm feeling lucky 465 $url = 'http://www.google.com/search?q='.urlencode($wikiUri).'&btnI=lucky'; 466 } 467 468 // BUILD THE LINK 469 echo '<a'; 470 471 $title = $this->__getLinkTitle($title, $wikiUri, $isImage); 472 473 if ( !$isImage ) { 474 echo ' class="interwiki"'; 475 } else { 476 echo ' class="media"'; 477 } 478 479 echo ' href="'.$this->__xmlEntities($url).'"'; 480 481 if ( FALSE !== ( $type = interwikiImgExists($wikiName) ) ) { 482 echo ' style="background: transparent url(http://wiki.splitbrain.org/interwiki/'. 483 $wikiName.'.'.$type.') 0px 1px no-repeat;"'; 484 } 485 486 echo ' onclick="return svchk()" onkeypress="return svchk()">'; 487 488 echo $title; 489 490 echo '</a>'; 491 } 492 493 /** 494 * @TODO Correct the CSS class for files? (not windows) 495 * @TODO Remove hard coded URL to splitbrain.org 496 */ 497 function filelink($link, $title = NULL) { 498 echo '<a'; 499 500 $title = $this->__getLinkTitle($title, $link, $isImage); 501 502 if ( !$isImage ) { 503 echo ' class="windows"'; 504 } else { 505 echo ' class="media"'; 506 } 507 508 echo ' href="'.$this->__xmlEntities($link).'"'; 509 510 echo ' style="background: transparent url(http://wiki.splitbrain.org/images/windows.gif) 0px 1px no-repeat;"'; 511 512 echo ' onclick="return svchk()" onkeypress="return svchk()">'; 513 514 echo $title; 515 516 echo '</a>'; 517 } 518 519 /** 520 * @TODO Remove hard coded URL to splitbrain.org 521 * @TODO Add error message for non-IE users 522 */ 523 function windowssharelink($link, $title = NULL) { 524 echo '<a'; 525 526 $title = $this->__getLinkTitle($title, $link, $isImage); 527 528 if ( !$isImage ) { 529 echo ' class="windows"'; 530 } else { 531 echo ' class="media"'; 532 } 533 534 $link = str_replace('\\','/',$link); 535 $link = 'file:///'.$link; 536 echo ' href="'.$this->__xmlEntities($link).'"'; 537 538 echo ' style="background: transparent url(http://wiki.splitbrain.org/images/windows.gif) 0px 1px no-repeat;"'; 539 540 echo ' onclick="return svchk()" onkeypress="return svchk()">'; 541 542 echo $title; 543 544 echo '</a>'; 545 } 546 547 /** 548 * @TODO Protect email address from harvesters 549 * @TODO Remove hard coded link to splitbrain.org 550 */ 551 function email($address, $title = NULL) { 552 echo '<a'; 553 554 $title = $this->__getLinkTitle($title, $address, $isImage); 555 556 if ( !$isImage ) { 557 echo ' class="mail"'; 558 } else { 559 echo ' class="media"'; 560 } 561 562 echo ' href="mailto:'.$this->__xmlEntities($address).'"'; 563 564 echo ' style="background: transparent url(http://wiki.splitbrain.org/images/mail_icon.gif) 0px 1px no-repeat;"'; 565 566 echo ' onclick="return svchk()" onkeypress="return svchk()">'; 567 568 echo $title; 569 570 echo '</a>'; 571 572 } 573 574 /** 575 * @TODO Resolve namespaces 576 * @TODO Add image caching 577 * @TODO Remove hard coded link to splitbrain.org 578 */ 579 function internalmedia ( 580 $src,$title=NULL,$align=NULL,$width=NULL,$height=NULL,$cache=NULL 581 ) { 582 583 // Sort out the namespace here... 584 if ( strpos($src,':') ) { 585 $src = explode(':',$src); 586 $src = $src[1]; 587 } 588 echo '<img class="media" src="http://wiki.splitbrain.org/media/wiki/'.$this->__xmlEntities($src).'"'; 589 590 if ( !is_null($title) ) { 591 echo ' title="'.$this->__xmlEntities($title).'"'; 592 } 593 594 if ( !is_null($align) ) { 595 echo ' align="'.$align.'"'; 596 } 597 598 if ( !is_null($width) ) { 599 echo ' width="'.$this->__xmlEntities($width).'"'; 600 } 601 602 if ( !is_null($height) ) { 603 echo ' height="'.$this->__xmlEntities($height).'"'; 604 } 605 606 echo '/>'; 607 608 } 609 610 /** 611 * @TODO Add image caching 612 */ 613 function externalmedia ( 614 $src,$title=NULL,$align=NULL,$width=NULL,$height=NULL,$cache=NULL 615 ) { 616 617 echo '<img class="media" src="'.$this->__xmlEntities($src).'"'; 618 619 if ( !is_null($title) ) { 620 echo ' title="'.$this->__xmlEntities($title).'"'; 621 } 622 623 if ( !is_null($align) ) { 624 echo ' align="'.$align.'"'; 625 } 626 627 if ( !is_null($width) ) { 628 echo ' width="'.$this->__xmlEntities($width).'"'; 629 } 630 631 if ( !is_null($height) ) { 632 echo ' height="'.$this->__xmlEntities($height).'"'; 633 } 634 635 echo '/>'; 636 } 637 638 // $numrows not yet implemented 639 function table_open($maxcols = NULL, $numrows = NULL){ 640 echo '<table class="inline">'.DOKU_LF; 641 } 642 643 function table_close(){ 644 echo '</table>'.DOKU_LF.'<br />'.DOKU_LF; 645 } 646 647 function tablerow_open(){ 648 echo DOKU_TAB . '<tr>' . DOKU_LF . DOKU_TAB . DOKU_TAB; 649 } 650 651 function tablerow_close(){ 652 echo DOKU_LF . DOKU_TAB . '</tr>' . DOKU_LF; 653 } 654 655 function tableheader_open($colspan = 1, $align = NULL){ 656 echo '<th'; 657 if ( !is_null($align) ) { 658 echo ' class="'.$align.'align"'; 659 } 660 if ( $colspan > 1 ) { 661 echo ' colspan="'.$colspan.'"'; 662 } 663 echo '>'; 664 } 665 666 function tableheader_close(){ 667 echo '</th>'; 668 } 669 670 function tablecell_open($colspan = 1, $align = NULL){ 671 echo '<td'; 672 if ( !is_null($align) ) { 673 echo ' class="'.$align.'align"'; 674 } 675 if ( $colspan > 1 ) { 676 echo ' colspan="'.$colspan.'"'; 677 } 678 echo '>'; 679 } 680 681 function tablecell_close(){ 682 echo '</td>'; 683 } 684 685 //---------------------------------------------------------- 686 // Utils 687 688 function __newFootnoteId() { 689 static $id = 1; 690 return $id++; 691 } 692 693 function __xmlEntities($string) { 694 return htmlspecialchars($string); 695 } 696 697 /** 698 * @TODO Tuning needed - e.g. utf8 strtolower ? 699 */ 700 function __headerToLink($title) { 701 return preg_replace('/\W/','_',trim($title)); 702 } 703 704 function __getLinkTitle($title, $default, & $isImage) { 705 $isImage = FALSE; 706 707 if ( is_null($title) ) { 708 709 return $this->__xmlEntities($default); 710 711 } else if ( is_string($title) ) { 712 713 return $this->__xmlEntities($title); 714 715 } else if ( is_array($title) ) { 716 717 $isImage = TRUE; 718 return $this->__imageTitle($title); 719 720 } 721 } 722 723 /** 724 * @TODO Resolve namespace on internal images 725 * @TODO Remove hard coded url to splitbrain.org 726 * @TODO Image caching 727 */ 728 function __imageTitle($img) { 729 730 if ( $img['type'] == 'internalmedia' ) { 731 732 // Resolve here... 733 if ( strpos($img['src'],':') ) { 734 $src = explode(':',$img['src']); 735 $src = $src[1]; 736 } else { 737 $src = $img['src']; 738 } 739 740 $imgStr = '<img class="media" src="http://wiki.splitbrain.org/media/wiki/'.$this->__xmlEntities($src).'"'; 741 742 } else { 743 744 $imgStr = '<img class="media" src="'.$this->__xmlEntities($img['src']).'"'; 745 746 } 747 748 if ( !is_null($img['title']) ) { 749 $imgStr .= ' alt="'.$this->__xmlEntities($img['title']).'"'; 750 } else { 751 $imgStr .= ' alt=""'; 752 } 753 754 if ( !is_null($img['align']) ) { 755 $imgStr .= ' align="'.$img['align'].'"'; 756 } 757 758 if ( !is_null($img['width']) ) { 759 $imgStr .= ' width="'.$this->__xmlEntities($img['width']).'"'; 760 } 761 762 if ( !is_null($img['height']) ) { 763 $imgStr .= ' height="'.$this->__xmlEntities($img['height']).'"'; 764 } 765 766 $imgStr .= '/>'; 767 768 return $imgStr; 769 } 770} 771 772/** 773* Test whether there's an image to display with this interwiki link 774*/ 775function interwikiImgExists($name) { 776 777 static $exists = array(); 778 779 if ( array_key_exists($name,$exists) ) { 780 return $exists[$name]; 781 } 782 783 if( @file_exists( DOKU. 'interwiki/'.$name.'.png') ) { 784 $exists[$name] = 'png'; 785 } else if ( @file_exists( DOKU . 'interwiki/'.$name.'.gif') ) { 786 $exists[$name] = 'gif'; 787 } else { 788 $exists[$name] = FALSE; 789 } 790 791 return $exists[$name]; 792} 793 794/** 795* For determining whether to use CSS class "wikilink1" or "wikilink2" 796* @todo use configinstead of DOKU_DATA 797*/ 798function wikiPageExists($name) { 799 800 static $pages = array(); 801 802 if ( array_key_exists($name,$pages) ) { 803 return $pages[$name]; 804 } 805 806 $file = str_replace(':','/',$name).'.txt'; 807 808 if ( @file_exists( DOKU_DATA . $file ) ) { 809 $pages[$name] = TRUE; 810 } else { 811 $pages[$name] = FALSE; 812 } 813 814 return $pages[$name]; 815} 816 817