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 */ 236 function php($text) { 237 global $conf; 238 if($conf['phpok']){ 239 eval($text); 240 }else{ 241 $this->file($text); 242 } 243 } 244 245 /** 246 */ 247 function html($text) { 248 global $conf; 249 if($conf['htmlok']){ 250 echo $text; 251 }else{ 252 $this->file($text); 253 } 254 } 255 256 function preformatted($text) { 257 echo '<pre class="code">' . $this->__xmlEntities($text) . '</pre>'. DOKU_LF; 258 } 259 260 function file($text) { 261 echo '<pre class="file">' . $this->__xmlEntities($text). '</pre>'. DOKU_LF; 262 } 263 264 /** 265 * @TODO Shouldn't this output <blockquote?? 266 */ 267 function quote_open() { 268 echo '<div class="quote">'.DOKU_LF; 269 } 270 271 /** 272 * @TODO Shouldn't this output </blockquote>? 273 */ 274 function quote_close() { 275 echo '</div>'.DOKU_LF; 276 } 277 278 /** 279 */ 280 function code($text, $language = NULL) { 281 global $conf; 282 283 if ( is_null($language) ) { 284 $this->preformatted($text); 285 } else { 286 287 // Handle with Geshi here (needs tuning) 288 require_once(DOKU_INC . 'inc/geshi.php'); 289 $geshi = new GeSHi($text, strtolower($language), DOKU_INC . 'inc/geshi'); 290 $geshi->enable_classes(); 291 $geshi->set_header_type(GESHI_HEADER_PRE); 292 $geshi->set_overall_class('code'); 293 $geshi->set_link_target($conf['target']['extern']); 294 295 $text = $geshi->parse_code(); 296 echo $text; 297 } 298 } 299 300 function acronym($acronym) { 301 302 if ( array_key_exists($acronym, $this->acronyms) ) { 303 304 $title = $this->__xmlEntities($this->acronyms[$acronym]); 305 306 echo '<acronym title="'.$title 307 .'">'.$this->__xmlEntities($acronym).'</acronym>'; 308 309 } else { 310 echo $this->__xmlEntities($acronym); 311 } 312 } 313 314 /** 315 */ 316 function smiley($smiley) { 317 if ( array_key_exists($smiley, $this->smileys) ) { 318 $title = $this->__xmlEntities($this->smileys[$smiley]); 319 echo '<img src="'.DOKU_BASE.'smileys/'.$this->smileys[$smiley]. 320 '" align="middle" alt="'. 321 $this->__xmlEntities($smiley).'" />'; 322 } else { 323 echo $this->__xmlEntities($smiley); 324 } 325 } 326 327 /** 328 * not used 329 function wordblock($word) { 330 if ( array_key_exists($word, $this->badwords) ) { 331 echo '** BLEEP **'; 332 } else { 333 echo $this->__xmlEntities($word); 334 } 335 } 336 */ 337 338 function entity($entity) { 339 if ( array_key_exists($entity, $this->entities) ) { 340 echo $this->entities[$entity]; 341 } else { 342 echo $this->__xmlEntities($entity); 343 } 344 } 345 346 function multiplyentity($x, $y) { 347 echo "$x×$y"; 348 } 349 350 function singlequoteopening() { 351 echo "‘"; 352 } 353 354 function singlequoteclosing() { 355 echo "’"; 356 } 357 358 function doublequoteopening() { 359 echo "“"; 360 } 361 362 function doublequoteclosing() { 363 echo "”"; 364 } 365 366 /** 367 */ 368 function camelcaselink($link) { 369 $this->internallink($link,$link); 370 } 371 372 /** 373 * @TODO Support media 374 * @TODO correct attributes 375 */ 376 function internallink($id, $name = NULL) { 377 global $conf; 378 379 $name = $this->__getLinkTitle($name, $this->__simpleTitle($id), $isImage); 380 resolve_pageid($id,$exists); 381 382 if ( !$isImage ) { 383 if ( $exists ) { 384 $class='wikilink1'; 385 } else { 386 $class='wikilink2'; 387 } 388 } else { 389 $class='media'; 390 } 391 392 //prepare for formating 393 $link['target'] = $conf['target']['wiki']; 394 $link['style'] = ''; 395 $link['pre'] = ''; 396 $link['suf'] = ''; 397 $link['more'] = 'onclick="return svchk()" onkeypress="return svchk()"'; 398 $link['class'] = $class; 399 $link['url'] = wl($id); 400 $link['name'] = $name; 401 $link['title'] = $id; 402 403 //output formatted 404 echo $this->__formatLink($link); 405 } 406 407 408 /** 409 * @TODO Should list assume blacklist check already made? 410 * @TODO External link icon 411 * @TODO correct attributes 412 */ 413 function externallink($link, $title = NULL) { 414 415 echo '<a'; 416 417 $title = $this->__getLinkTitle($title, $link, $isImage); 418 419 if ( !$isImage ) { 420 echo ' class="urlextern"'; 421 } else { 422 echo ' class="media"'; 423 } 424 425 echo ' target="_blank" href="'.$this->__xmlEntities($link).'"'; 426 427 echo ' onclick="return svchk()" onkeypress="return svchk()">'; 428 429 echo $title; 430 431 echo '</a>'; 432 } 433 434 /** 435 * @TODO Remove hard coded link to splitbrain.org on style 436 */ 437 function interwikilink($link, $title = NULL, $wikiName, $wikiUri) { 438 439 // RESOLVE THE URL 440 if ( isset($this->interwiki[$wikiName]) ) { 441 442 $wikiUriEnc = urlencode($wikiUri); 443 444 if ( strstr($this->interwiki[$wikiName],'{URL}' ) !== FALSE ) { 445 446 $url = str_replace('{URL}', $wikiUriEnc, $this->interwiki[$wikiName] ); 447 448 } else if ( strstr($this->interwiki[$wikiName],'{NAME}' ) !== FALSE ) { 449 450 $url = str_replace('{NAME}', $wikiUriEnc, $this->interwiki[$wikiName] ); 451 452 } else { 453 454 $url = $this->interwiki[$wikiName] . urlencode($wikiUri); 455 456 } 457 458 } else { 459 // Default to Google I'm feeling lucky 460 $url = 'http://www.google.com/search?q='.urlencode($wikiUri).'&btnI=lucky'; 461 } 462 463 // BUILD THE LINK 464 echo '<a'; 465 466 $title = $this->__getLinkTitle($title, $wikiUri, $isImage); 467 468 if ( !$isImage ) { 469 echo ' class="interwiki"'; 470 } else { 471 echo ' class="media"'; 472 } 473 474 echo ' href="'.$this->__xmlEntities($url).'"'; 475 476 if ( FALSE !== ( $type = interwikiImgExists($wikiName) ) ) { 477 echo ' style="background: transparent url(http://wiki.splitbrain.org/interwiki/'. 478 $wikiName.'.'.$type.') 0px 1px no-repeat;"'; 479 } 480 481 echo ' onclick="return svchk()" onkeypress="return svchk()">'; 482 483 echo $title; 484 485 echo '</a>'; 486 } 487 488 /** 489 * @TODO Correct the CSS class for files? (not windows) 490 * @TODO Remove hard coded URL to splitbrain.org 491 */ 492 function filelink($link, $title = NULL) { 493 echo '<a'; 494 495 $title = $this->__getLinkTitle($title, $link, $isImage); 496 497 if ( !$isImage ) { 498 echo ' class="windows"'; 499 } else { 500 echo ' class="media"'; 501 } 502 503 echo ' href="'.$this->__xmlEntities($link).'"'; 504 505 echo ' style="background: transparent url(http://wiki.splitbrain.org/images/windows.gif) 0px 1px no-repeat;"'; 506 507 echo ' onclick="return svchk()" onkeypress="return svchk()">'; 508 509 echo $title; 510 511 echo '</a>'; 512 } 513 514 /** 515 * @TODO Remove hard coded URL to splitbrain.org 516 * @TODO Add error message for non-IE users 517 */ 518 function windowssharelink($link, $title = NULL) { 519 echo '<a'; 520 521 $title = $this->__getLinkTitle($title, $link, $isImage); 522 523 if ( !$isImage ) { 524 echo ' class="windows"'; 525 } else { 526 echo ' class="media"'; 527 } 528 529 $link = str_replace('\\','/',$link); 530 $link = 'file:///'.$link; 531 echo ' href="'.$this->__xmlEntities($link).'"'; 532 533 echo ' style="background: transparent url(http://wiki.splitbrain.org/images/windows.gif) 0px 1px no-repeat;"'; 534 535 echo ' onclick="return svchk()" onkeypress="return svchk()">'; 536 537 echo $title; 538 539 echo '</a>'; 540 } 541 542 /** 543 * @TODO Protect email address from harvesters 544 * @TODO Remove hard coded link to splitbrain.org 545 */ 546 function email($address, $title = NULL) { 547 echo '<a'; 548 549 $title = $this->__getLinkTitle($title, $address, $isImage); 550 551 if ( !$isImage ) { 552 echo ' class="mail"'; 553 } else { 554 echo ' class="media"'; 555 } 556 557 echo ' href="mailto:'.$this->__xmlEntities($address).'"'; 558 559 echo ' style="background: transparent url(http://wiki.splitbrain.org/images/mail_icon.gif) 0px 1px no-repeat;"'; 560 561 echo ' onclick="return svchk()" onkeypress="return svchk()">'; 562 563 echo $title; 564 565 echo '</a>'; 566 567 } 568 569 /** 570 * @TODO Resolve namespaces 571 * @TODO Add image caching 572 * @TODO Remove hard coded link to splitbrain.org 573 */ 574 function internalmedia ( 575 $src,$title=NULL,$align=NULL,$width=NULL,$height=NULL,$cache=NULL 576 ) { 577 578 // Sort out the namespace here... 579 if ( strpos($src,':') ) { 580 $src = explode(':',$src); 581 $src = $src[1]; 582 } 583 echo '<img class="media" src="http://wiki.splitbrain.org/media/wiki/'.$this->__xmlEntities($src).'"'; 584 585 if ( !is_null($title) ) { 586 echo ' title="'.$this->__xmlEntities($title).'"'; 587 } 588 589 if ( !is_null($align) ) { 590 echo ' align="'.$align.'"'; 591 } 592 593 if ( !is_null($width) ) { 594 echo ' width="'.$this->__xmlEntities($width).'"'; 595 } 596 597 if ( !is_null($height) ) { 598 echo ' height="'.$this->__xmlEntities($height).'"'; 599 } 600 601 echo '/>'; 602 603 } 604 605 /** 606 * @TODO Add image caching 607 */ 608 function externalmedia ( 609 $src,$title=NULL,$align=NULL,$width=NULL,$height=NULL,$cache=NULL 610 ) { 611 612 echo '<img class="media" src="'.$this->__xmlEntities($src).'"'; 613 614 if ( !is_null($title) ) { 615 echo ' title="'.$this->__xmlEntities($title).'"'; 616 } 617 618 if ( !is_null($align) ) { 619 echo ' align="'.$align.'"'; 620 } 621 622 if ( !is_null($width) ) { 623 echo ' width="'.$this->__xmlEntities($width).'"'; 624 } 625 626 if ( !is_null($height) ) { 627 echo ' height="'.$this->__xmlEntities($height).'"'; 628 } 629 630 echo '/>'; 631 } 632 633 // $numrows not yet implemented 634 function table_open($maxcols = NULL, $numrows = NULL){ 635 echo '<table class="inline">'.DOKU_LF; 636 } 637 638 function table_close(){ 639 echo '</table>'.DOKU_LF.'<br />'.DOKU_LF; 640 } 641 642 function tablerow_open(){ 643 echo DOKU_TAB . '<tr>' . DOKU_LF . DOKU_TAB . DOKU_TAB; 644 } 645 646 function tablerow_close(){ 647 echo DOKU_LF . DOKU_TAB . '</tr>' . DOKU_LF; 648 } 649 650 function tableheader_open($colspan = 1, $align = NULL){ 651 echo '<th'; 652 if ( !is_null($align) ) { 653 echo ' class="'.$align.'align"'; 654 } 655 if ( $colspan > 1 ) { 656 echo ' colspan="'.$colspan.'"'; 657 } 658 echo '>'; 659 } 660 661 function tableheader_close(){ 662 echo '</th>'; 663 } 664 665 function tablecell_open($colspan = 1, $align = NULL){ 666 echo '<td'; 667 if ( !is_null($align) ) { 668 echo ' class="'.$align.'align"'; 669 } 670 if ( $colspan > 1 ) { 671 echo ' colspan="'.$colspan.'"'; 672 } 673 echo '>'; 674 } 675 676 function tablecell_close(){ 677 echo '</td>'; 678 } 679 680 //---------------------------------------------------------- 681 // Utils 682 683 /** 684 * Assembles all parts defined by the link formater below 685 * Returns HTML for the link 686 * 687 * @author Andreas Gohr <andi@splitbrain.org> 688 */ 689 function __formatLink($link){ 690 //make sure the url is XHTML compliant (skip mailto) 691 if(substr($link['url'],0,7) != 'mailto:'){ 692 $link['url'] = str_replace('&','&',$link['url']); 693 $link['url'] = str_replace('&amp;','&',$link['url']); 694 } 695 //remove double encodings in titles 696 $link['title'] = str_replace('&amp;','&',$link['title']); 697 698 $ret = ''; 699 $ret .= $link['pre']; 700 $ret .= '<a href="'.$link['url'].'"'; 701 if($link['class']) $ret .= ' class="'.$link['class'].'"'; 702 if($link['target']) $ret .= ' target="'.$link['target'].'"'; 703 if($link['title']) $ret .= ' title="'.$link['title'].'"'; 704 if($link['style']) $ret .= ' style="'.$link['style'].'"'; 705 if($link['more']) $ret .= ' '.$link['more']; 706 $ret .= '>'; 707 $ret .= $link['name']; 708 $ret .= '</a>'; 709 $ret .= $link['suf']; 710 return $ret; 711 } 712 713 /** 714 * Removes any Namespace from the given name but keeps 715 * casing and special chars 716 * 717 * @author Andreas Gohr <andi@splitbrain.org> 718 */ 719 function __simpleTitle($name){ 720 global $conf; 721 if($conf['useslash']){ 722 $nssep = '[:;/]'; 723 }else{ 724 $nssep = '[:;]'; 725 } 726 return preg_replace('!.*'.$nssep.'!','',$name); 727 } 728 729 730 function __newFootnoteId() { 731 static $id = 1; 732 return $id++; 733 } 734 735 function __xmlEntities($string) { 736 return htmlspecialchars($string); 737 } 738 739 /** 740 * @TODO Tuning needed - e.g. utf8 strtolower ? 741 */ 742 function __headerToLink($title) { 743 return preg_replace('/\W/','_',trim($title)); 744 } 745 746 function __getLinkTitle($title, $default, & $isImage) { 747 $isImage = FALSE; 748 749 if ( is_null($title) ) { 750 return $this->__xmlEntities($default); 751 752 } else if ( is_string($title) ) { 753 754 return $this->__xmlEntities($title); 755 756 } else if ( is_array($title) ) { 757 758 $isImage = TRUE; 759 return $this->__imageTitle($title); 760 761 } 762 } 763 764 /** 765 * @TODO Resolve namespace on internal images 766 * @TODO Remove hard coded url to splitbrain.org 767 * @TODO Image caching 768 */ 769 function __imageTitle($img) { 770 771 if ( $img['type'] == 'internalmedia' ) { 772 773 // Resolve here... 774 if ( strpos($img['src'],':') ) { 775 $src = explode(':',$img['src']); 776 $src = $src[1]; 777 } else { 778 $src = $img['src']; 779 } 780 781 $imgStr = '<img class="media" src="http://wiki.splitbrain.org/media/wiki/'.$this->__xmlEntities($src).'"'; 782 783 } else { 784 785 $imgStr = '<img class="media" src="'.$this->__xmlEntities($img['src']).'"'; 786 787 } 788 789 if ( !is_null($img['title']) ) { 790 $imgStr .= ' alt="'.$this->__xmlEntities($img['title']).'"'; 791 } else { 792 $imgStr .= ' alt=""'; 793 } 794 795 if ( !is_null($img['align']) ) { 796 $imgStr .= ' align="'.$img['align'].'"'; 797 } 798 799 if ( !is_null($img['width']) ) { 800 $imgStr .= ' width="'.$this->__xmlEntities($img['width']).'"'; 801 } 802 803 if ( !is_null($img['height']) ) { 804 $imgStr .= ' height="'.$this->__xmlEntities($img['height']).'"'; 805 } 806 807 $imgStr .= '/>'; 808 809 return $imgStr; 810 } 811} 812 813/** 814* Test whether there's an image to display with this interwiki link 815*/ 816function interwikiImgExists($name) { 817 818 static $exists = array(); 819 820 if ( array_key_exists($name,$exists) ) { 821 return $exists[$name]; 822 } 823 824 if( @file_exists( DOKU. 'interwiki/'.$name.'.png') ) { 825 $exists[$name] = 'png'; 826 } else if ( @file_exists( DOKU . 'interwiki/'.$name.'.gif') ) { 827 $exists[$name] = 'gif'; 828 } else { 829 $exists[$name] = FALSE; 830 } 831 832 return $exists[$name]; 833} 834 835/** 836 * For determining whether to use CSS class "wikilink1" or "wikilink2" 837 * @todo use configinstead of DOKU_DATA 838 * @deprecated -> resolve_pagename should be used 839 */ 840function wikiPageExists($name) { 841 842 static $pages = array(); 843 844 if ( array_key_exists($name,$pages) ) { 845 return $pages[$name]; 846 } 847 848 $file = str_replace(':','/',$name).'.txt'; 849 850 if ( @file_exists( DOKU_DATA . $file ) ) { 851 $pages[$name] = TRUE; 852 } else { 853 $pages[$name] = FALSE; 854 } 855 856 return $pages[$name]; 857} 858 859