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