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