DokuWiki
Clean HTML Parser
Wer, wie ich, nicht mag wie Dokuwiki den Wiki-Code umwandelt, mit vielen zusätzlich verschachtelten divs und Klassen, der möge folgenden Parser ausprobieren:
Datei: /inc/parser/xhtml.php Quelltext herunterladen
1: <?php 2: /** 3: * Renderer for XHTML output 4: * 5: * @author Harry Fuecks <hfuecks@gmail.com> 6: * @author Andreas Gohr <andi@splitbrain.org> 7: */ 8: 9: if(!defined('DOKU_INC')) define('DOKU_INC',fullpath(dirname(__FILE__).'/../../').'/'); 10: 11: if ( !defined('DOKU_LF') ) { 12: // Some whitespace to help View > Source 13: define ('DOKU_LF',"\n"); 14: } 15: 16: if ( !defined('DOKU_TAB') ) { 17: // Some whitespace to help View > Source 18: define ('DOKU_TAB',"\t"); 19: } 20: 21: require_once DOKU_INC . 'inc/parser/renderer.php'; 22: require_once DOKU_INC . 'inc/html.php'; 23: 24: /** 25: * The Renderer 26: */ 27: class Doku_Renderer_xhtml extends Doku_Renderer { 28: 29: // @access public 30: var $doc = ''; // will contain the whole document 31: var $toc = array(); // will contain the Table of Contents 32: 33: 34: var $headers = array(); 35: var $footnotes = array(); 36: var $lastsec = 0; 37: var $store = ''; 38: 39: var $_counter = array(); // used as global counter, introduced for table classes 40: 41: function getFormat(){ 42: return 'xhtml'; 43: } 44: 45: 46: function document_start() { 47: //reset some internals 48: $this->toc = array(); 49: $this->headers = array(); 50: } 51: 52: function document_end() { 53: if ( count ($this->footnotes) > 0 ) { 54: $this->doc .= '<div class="footnotes">'.DOKU_LF; 55: 56: $id = 0; 57: foreach ( $this->footnotes as $footnote ) { 58: $id++; // the number of the current footnote 59: 60: // check its not a placeholder that indicates actual footnote text is elsewhere 61: if (substr($footnote, 0, 5) != "@@FNT") { 62: 63: // open the footnote and set the anchor and backlink 64: $this->doc .= '<div class="fn">'; 65: $this->doc .= '<sup><a href="#fnt__'.$id.'" id="fn__'.$id.'" name="fn__'.$id.'" class="fn_bot">'; 66: $this->doc .= $id.')</a></sup> '.DOKU_LF; 67: 68: // get any other footnotes that use the same markup 69: $alt = array_keys($this->footnotes, "@@FNT$id"); 70: 71: if (count($alt)) { 72: foreach ($alt as $ref) { 73: // set anchor and backlink for the other footnotes 74: $this->doc .= ', <sup><a href="#fnt__'.($ref+1).'" id="fn__'.($ref+1).'" name="fn__'.($ref+1).'" class="fn_bot">'; 75: $this->doc .= ($ref+1).')</a></sup> '.DOKU_LF; 76: } 77: } 78: 79: // add footnote markup and close this footnote 80: $this->doc .= $footnote; 81: $this->doc .= '</div>' . DOKU_LF; 82: } 83: } 84: $this->doc .= '</div>'.DOKU_LF; 85: } 86: 87: // Prepare the TOC 88: if($this->info['toc'] && is_array($this->toc) && count($this->toc) > 2){ 89: global $TOC; 90: $TOC = $this->toc; 91: } 92: 93: // make sure there are no empty paragraphs 94: $this->doc = preg_replace('#<p>\s*</p>#','',$this->doc); 95: } 96: 97: function toc_additem($id, $text, $level) { 98: global $conf; 99: 100: //handle TOC 101: if($level >= $conf['toptoclevel'] && $level <= $conf['maxtoclevel']){ 102: $this->toc[] = html_mktocitem($id, $text, $level-$conf['toptoclevel']+1); 103: } 104: } 105: 106: function header($text, $level, $pos) { 107: 108: $hid = $this->_headerToLink($text,true); 109: 110: //only add items within configured levels 111: $this->toc_additem($hid, $text, $level); 112: 113: // write the header 114: $this->doc .= DOKU_LF.'<h'.$level.'>'; 115: $this->doc .= $this->_xmlEntities($text); 116: $this->doc .= "</h$level>".DOKU_LF; 117: } 118: 119: /** 120: * Section edit marker is replaced by an edit button when 121: * the page is editable. Replacement done in 'inc/html.php#html_secedit' 122: * 123: * @author Andreas Gohr <andi@splitbrain.org> 124: * @author Ben Coburn <btcoburn@silicodon.net> 125: */ 126: function section_edit($start, $end, $level, $name) { 127: global $conf; 128: 129: if ($start!=-1 && $level<=$conf['maxseclevel']) { 130: $name = str_replace('"', '', $name); 131: $this->doc .= '<!-- SECTION "'.$name.'" ['.$start.'-'.(($end===0)?'':$end).'] -->'; 132: } 133: } 134: 135: function section_open($level) { 136: //$this->doc .= "<div class=\"level$level\">".DOKU_LF; 137: } 138: 139: function section_close() { 140: //$this->doc .= DOKU_LF.'</div>'.DOKU_LF; 141: } 142: 143: function cdata($text) { 144: $this->doc .= $this->_xmlEntities($text); 145: } 146: 147: function p_open() { 148: $this->doc .= DOKU_LF.'<p>'.DOKU_LF; 149: } 150: 151: function p_close() { 152: $this->doc .= DOKU_LF.'</p>'.DOKU_LF; 153: } 154: 155: function linebreak() { 156: $this->doc .= '<br/>'.DOKU_LF; 157: } 158: 159: function hr() { 160: $this->doc .= '<hr />'.DOKU_LF; 161: } 162: 163: function strong_open() { 164: $this->doc .= '<strong>'; 165: } 166: 167: function strong_close() { 168: $this->doc .= '</strong>'; 169: } 170: 171: function emphasis_open() { 172: $this->doc .= '<em>'; 173: } 174: 175: function emphasis_close() { 176: $this->doc .= '</em>'; 177: } 178: 179: function underline_open() { 180: $this->doc .= '<em class="u">'; 181: } 182: 183: function underline_close() { 184: $this->doc .= '</em>'; 185: } 186: 187: function monospace_open() { 188: $this->doc .= '<code>'; 189: } 190: 191: function monospace_close() { 192: $this->doc .= '</code>'; 193: } 194: 195: function subscript_open() { 196: $this->doc .= '<sub>'; 197: } 198: 199: function subscript_close() { 200: $this->doc .= '</sub>'; 201: } 202: 203: function superscript_open() { 204: $this->doc .= '<sup>'; 205: } 206: 207: function superscript_close() { 208: $this->doc .= '</sup>'; 209: } 210: 211: function deleted_open() { 212: $this->doc .= '<del>'; 213: } 214: 215: function deleted_close() { 216: $this->doc .= '</del>'; 217: } 218: 219: /** 220: * Callback for footnote start syntax 221: * 222: * All following content will go to the footnote instead of 223: * the document. To achieve this the previous rendered content 224: * is moved to $store and $doc is cleared 225: * 226: * @author Andreas Gohr <andi@splitbrain.org> 227: */ 228: function footnote_open() { 229: 230: // move current content to store and record footnote 231: $this->store = $this->doc; 232: $this->doc = ''; 233: } 234: 235: /** 236: * Callback for footnote end syntax 237: * 238: * All rendered content is moved to the $footnotes array and the old 239: * content is restored from $store again 240: * 241: * @author Andreas Gohr 242: */ 243: function footnote_close() { 244: 245: // recover footnote into the stack and restore old content 246: $footnote = $this->doc; 247: $this->doc = $this->store; 248: $this->store = ''; 249: 250: // check to see if this footnote has been seen before 251: $i = array_search($footnote, $this->footnotes); 252: 253: if ($i === false) { 254: // its a new footnote, add it to the $footnotes array 255: $id = count($this->footnotes)+1; 256: $this->footnotes[count($this->footnotes)] = $footnote; 257: } else { 258: // seen this one before, translate the index to an id and save a placeholder 259: $i++; 260: $id = count($this->footnotes)+1; 261: $this->footnotes[count($this->footnotes)] = "@@FNT".($i); 262: } 263: 264: // output the footnote reference and link 265: $this->doc .= '<sup><a href="#fn__'.$id.'" name="fnt__'.$id.'" id="fnt__'.$id.'" class="fn_top">'.$id.')</a></sup>'; 266: } 267: 268: function listu_open() { 269: $this->doc .= '<ul>'.DOKU_LF; 270: } 271: 272: function listu_close() { 273: $this->doc .= '</ul>'.DOKU_LF; 274: } 275: 276: function listo_open() { 277: $this->doc .= '<ol>'.DOKU_LF; 278: } 279: 280: function listo_close() { 281: $this->doc .= '</ol>'.DOKU_LF; 282: } 283: 284: function listitem_open($level) { 285: $this->doc .= '<li>'; 286: } 287: 288: function listitem_close() { 289: $this->doc .= '</li>'.DOKU_LF; 290: } 291: 292: function listcontent_open() { 293: //$this->doc .= '<div class="li">'; 294: } 295: 296: function listcontent_close() { 297: //$this->doc .= '</div>'.DOKU_LF; 298: } 299: 300: function unformatted($text) { 301: $this->doc .= $this->_xmlEntities($text); 302: } 303: 304: /** 305: * Execute PHP code if allowed 306: * 307: * @param string $wrapper html element to wrap result if $conf['phpok'] is okff 308: * 309: * @author Andreas Gohr <andi@splitbrain.org> 310: */ 311: function php($text, $wrapper='code') { 312: global $conf; 313: 314: if($conf['phpok']){ 315: ob_start(); 316: eval($text); 317: $this->doc .= ob_get_contents(); 318: ob_end_clean(); 319: } else { 320: $this->doc .= p_xhtml_cached_geshi($text, 'php', $wrapper); 321: } 322: } 323: 324: function phpblock($text) { 325: $this->php($text, 'pre'); 326: } 327: 328: /** 329: * Insert HTML if allowed 330: * 331: * @param string $wrapper html element to wrap result if $conf['htmlok'] is okff 332: * 333: * @author Andreas Gohr <andi@splitbrain.org> 334: */ 335: function html($text, $wrapper='code') { 336: global $conf; 337: 338: if($conf['htmlok']){ 339: $this->doc .= $text; 340: } else { 341: $this->doc .= p_xhtml_cached_geshi($text, 'html4strict', $wrapper); 342: } 343: } 344: 345: function htmlblock($text) { 346: $this->html($text, 'pre'); 347: } 348: 349: function preformatted($text) { 350: $this->doc .= '<pre class="code">' . $this->_xmlEntities($text) . '</pre>'. DOKU_LF; 351: } 352: 353: function file($text) { 354: $this->doc .= '<pre class="file">' . $this->_xmlEntities($text). '</pre>'. DOKU_LF; 355: } 356: 357: function quote_open() { 358: $this->doc .= '<blockquote><div class="no">'.DOKU_LF; 359: } 360: 361: function quote_close() { 362: $this->doc .= '</div></blockquote>'.DOKU_LF; 363: } 364: 365: /** 366: * Callback for code text 367: * 368: * Uses GeSHi to highlight language syntax 369: * 370: * @author Andreas Gohr <andi@splitbrain.org> 371: */ 372: function code($text, $language = NULL) { 373: global $conf; 374: 375: if ( is_null($language) ) { 376: $this->preformatted($text); 377: } else { 378: $this->doc .= p_xhtml_cached_geshi($text, $language); 379: } 380: } 381: 382: function acronym($acronym) { 383: 384: if ( array_key_exists($acronym, $this->acronyms) ) { 385: 386: $title = $this->_xmlEntities($this->acronyms[$acronym]); 387: 388: $this->doc .= '<acronym title="'.$title 389: .'">'.$this->_xmlEntities($acronym).'</acronym>'; 390: 391: } else { 392: $this->doc .= $this->_xmlEntities($acronym); 393: } 394: } 395: 396: function smiley($smiley) { 397: if ( array_key_exists($smiley, $this->smileys) ) { 398: $title = $this->_xmlEntities($this->smileys[$smiley]); 399: $this->doc .= '<img src="'.DOKU_BASE.'lib/images/smileys/'.$this->smileys[$smiley]. 400: '" class="middle" alt="'. 401: $this->_xmlEntities($smiley).'" />'; 402: } else { 403: $this->doc .= $this->_xmlEntities($smiley); 404: } 405: } 406: 407: /* 408: * not used 409: function wordblock($word) { 410: if ( array_key_exists($word, $this->badwords) ) { 411: $this->doc .= '** BLEEP **'; 412: } else { 413: $this->doc .= $this->_xmlEntities($word); 414: } 415: } 416: */ 417: 418: function entity($entity) { 419: if ( array_key_exists($entity, $this->entities) ) { 420: $this->doc .= $this->entities[$entity]; 421: } else { 422: $this->doc .= $this->_xmlEntities($entity); 423: } 424: } 425: 426: function multiplyentity($x, $y) { 427: $this->doc .= "$x×$y"; 428: } 429: 430: function singlequoteopening() { 431: global $lang; 432: $this->doc .= $lang['singlequoteopening']; 433: } 434: 435: function singlequoteclosing() { 436: global $lang; 437: $this->doc .= $lang['singlequoteclosing']; 438: } 439: 440: function apostrophe() { 441: global $lang; 442: $this->doc .= $lang['apostrophe']; 443: } 444: 445: function doublequoteopening() { 446: global $lang; 447: $this->doc .= $lang['doublequoteopening']; 448: } 449: 450: function doublequoteclosing() { 451: global $lang; 452: $this->doc .= $lang['doublequoteclosing']; 453: } 454: 455: /** 456: */ 457: function camelcaselink($link) { 458: $this->internallink($link,$link); 459: } 460: 461: 462: function locallink($hash, $name = NULL){ 463: global $ID; 464: $name = $this->_getLinkTitle($name, $hash, $isImage); 465: $hash = $this->_headerToLink($hash); 466: $title = $ID.' ↵'; 467: $this->doc .= '<a href="#'.$hash.'" title="'.$title.'" class="wikilink1">'; 468: $this->doc .= $name; 469: $this->doc .= '</a>'; 470: } 471: 472: /** 473: * Render an internal Wiki Link 474: * 475: * $search and $returnonly are not for the renderer but are used 476: * elsewhere - no need to implement them in other renderers 477: * 478: * @author Andreas Gohr <andi@splitbrain.org> 479: */ 480: function internallink($id, $name = NULL, $search=NULL,$returnonly=false) { 481: global $conf; 482: global $ID; 483: // default name is based on $id as given 484: $default = $this->_simpleTitle($id); 485: 486: // now first resolve and clean up the $id 487: resolve_pageid(getNS($ID),$id,$exists); 488: $name = $this->_getLinkTitle($name, $default, $isImage, $id); 489: if ( !$isImage ) { 490: if ( $exists ) { 491: $class='wikilink1'; 492: } else { 493: $class='wikilink2'; 494: $link['rel']='nofollow'; 495: } 496: } else { 497: $class='media'; 498: } 499: 500: //keep hash anchor 501: list($id,$hash) = explode('#',$id,2); 502: if(!empty($hash)) $hash = $this->_headerToLink($hash); 503: 504: //prepare for formating 505: $link['target'] = $conf['target']['wiki']; 506: $link['style'] = ''; 507: $link['pre'] = ''; 508: $link['suf'] = ''; 509: // highlight link to current page 510: if ($id == $ID) { 511: $link['pre'] = '<span class="curid">'; 512: $link['suf'] = '</span>'; 513: } 514: $link['more'] = ''; 515: $link['class'] = $class; 516: $link['url'] = wl($id); 517: $link['name'] = $name; 518: $link['title'] = $id; 519: //add search string 520: if($search){ 521: ($conf['userewrite']) ? $link['url'].='?' : $link['url'].='&'; 522: if(is_array($search)){ 523: $search = array_map('rawurlencode',$search); 524: $link['url'] .= 's[]='.join('&s[]=',$search); 525: }else{ 526: $link['url'] .= 's='.rawurlencode($search); 527: } 528: } 529: 530: //keep hash 531: if($hash) $link['url'].='#'.$hash; 532: 533: //output formatted 534: if($returnonly){ 535: return $this->_formatLink($link); 536: }else{ 537: $this->doc .= $this->_formatLink($link); 538: } 539: } 540: 541: function externallink($url, $name = NULL) { 542: global $conf; 543: 544: $name = $this->_getLinkTitle($name, $url, $isImage); 545: 546: if ( !$isImage ) { 547: $class='urlextern'; 548: } else { 549: $class='media'; 550: } 551: 552: //prepare for formating 553: $link['target'] = $conf['target']['extern']; 554: $link['style'] = ''; 555: $link['pre'] = ''; 556: $link['suf'] = ''; 557: $link['more'] = ''; 558: $link['class'] = $class; 559: $link['url'] = $url; 560: 561: $link['name'] = $name; 562: $link['title'] = $this->_xmlEntities($url); 563: if($conf['relnofollow']) $link['more'] .= ' rel="nofollow"'; 564: 565: //output formatted 566: $this->doc .= $this->_formatLink($link); 567: } 568: 569: /** 570: */ 571: function interwikilink($match, $name = NULL, $wikiName, $wikiUri) { 572: global $conf; 573: 574: $link = array(); 575: $link['target'] = $conf['target']['interwiki']; 576: $link['pre'] = ''; 577: $link['suf'] = ''; 578: $link['more'] = ''; 579: $link['name'] = $this->_getLinkTitle($name, $wikiUri, $isImage); 580: 581: //get interwiki URL 582: $url = $this-> _resolveInterWiki($wikiName,$wikiUri); 583: 584: if ( !$isImage ) { 585: $class = preg_replace('/[^_\-a-z0-9]+/i','_',$wikiName); 586: $link['class'] = "interwiki iw_$class"; 587: } else { 588: $link['class'] = 'media'; 589: } 590: 591: //do we stay at the same server? Use local target 592: if( strpos($url,DOKU_URL) === 0 ){ 593: $link['target'] = $conf['target']['wiki']; 594: } 595: 596: $link['url'] = $url; 597: $link['title'] = htmlspecialchars($link['url']); 598: 599: //output formatted 600: $this->doc .= $this->_formatLink($link); 601: } 602: 603: /** 604: */ 605: function windowssharelink($url, $name = NULL) { 606: global $conf; 607: global $lang; 608: //simple setup 609: $link['target'] = $conf['target']['windows']; 610: $link['pre'] = ''; 611: $link['suf'] = ''; 612: $link['style'] = ''; 613: //Display error on browsers other than IE 614: $link['more'] = 'onclick="if(document.all == null){alert(\''. 615: str_replace('\\\\n','\\n',addslashes($lang['nosmblinks'])). 616: '\');}" onkeypress="if(document.all == null){alert(\''. 617: str_replace('\\\\n','\\n',addslashes($lang['nosmblinks'])).'\');}"'; 618: 619: $link['name'] = $this->_getLinkTitle($name, $url, $isImage); 620: if ( !$isImage ) { 621: $link['class'] = 'windows'; 622: } else { 623: $link['class'] = 'media'; 624: } 625: 626: 627: $link['title'] = $this->_xmlEntities($url); 628: $url = str_replace('\\','/',$url); 629: $url = 'file:///'.$url; 630: $link['url'] = $url; 631: 632: //output formatted 633: $this->doc .= $this->_formatLink($link); 634: } 635: 636: function emaillink($address, $name = NULL) { 637: global $conf; 638: //simple setup 639: $link = array(); 640: $link['target'] = ''; 641: $link['pre'] = ''; 642: $link['suf'] = ''; 643: $link['style'] = ''; 644: $link['more'] = ''; 645: 646: $name = $this->_getLinkTitle($name, '', $isImage); 647: if ( !$isImage ) { 648: $link['class']='mail JSnocheck'; 649: } else { 650: $link['class']='media JSnocheck'; 651: } 652: 653: $address = $this->_xmlEntities($address); 654: $address = obfuscate($address); 655: $title = $address; 656: 657: if(empty($name)){ 658: $name = $address; 659: } 660: #elseif($isImage{ 661: # $name = $this->_xmlEntities($name); 662: # } 663: 664: if($conf['mailguard'] == 'visible') $address = rawurlencode($address); 665: 666: $link['url'] = 'mailto:'.$address; 667: $link['name'] = $name; 668: $link['title'] = $title; 669: 670: //output formatted 671: $this->doc .= $this->_formatLink($link); 672: } 673: 674: function internalmedia ($src, $title=NULL, $align=NULL, $width=NULL, 675: $height=NULL, $cache=NULL, $linking=NULL) { 676: global $ID; 677: resolve_mediaid(getNS($ID),$src, $exists); 678: 679: $noLink = false; 680: $render = ($linking == 'linkonly') ? false : true; 681: $link = $this->_getMediaLinkConf($src, $title, $align, $width, $height, $cache, $render); 682: 683: list($ext,$mime) = mimetype($src); 684: if(substr($mime,0,5) == 'image' && $render){ 685: $link['url'] = ml($src,array('id'=>$ID,'cache'=>$cache),($linking=='direct')); 686: }elseif($mime == 'application/x-shockwave-flash'){ 687: // don't link flash movies 688: $noLink = true; 689: }else{ 690: // add file icons 691: $class = preg_replace('/[^_\-a-z0-9]+/i','_',$ext); 692: $link['class'] .= ' mediafile mf_'.$class; 693: $link['url'] = ml($src,array('id'=>$ID,'cache'=>$cache),true); 694: } 695: 696: //output formatted 697: if ($linking == 'nolink' || $noLink) $this->doc .= $link['name']; 698: else $this->doc .= $this->_formatLink($link); 699: } 700: 701: /** 702: * @todo don't add link for flash 703: */ 704: function externalmedia ($src, $title=NULL, $align=NULL, $width=NULL, 705: $height=NULL, $cache=NULL, $linking=NULL) { 706: $noLink = false; 707: $render = ($linking == 'linkonly') ? false : true; 708: $link = $this->_getMediaLinkConf($src, $title, $align, $width, $height, $cache, $render); 709: 710: $link['url'] = ml($src,array('cache'=>$cache)); 711: 712: list($ext,$mime) = mimetype($src); 713: if(substr($mime,0,5) == 'image' && $render){ 714: // link only jpeg images 715: // if ($ext != 'jpg' && $ext != 'jpeg') $noLink = true; 716: }elseif($mime == 'application/x-shockwave-flash'){ 717: // don't link flash movies 718: $noLink = true; 719: }else{ 720: // add file icons 721: $link['class'] .= ' mediafile mf_'.$ext; 722: } 723: 724: //output formatted 725: if ($linking == 'nolink' || $noLink) $this->doc .= $link['name']; 726: else $this->doc .= $this->_formatLink($link); 727: } 728: 729: /** 730: * Renders an RSS feed 731: * 732: * @author Andreas Gohr <andi@splitbrain.org> 733: */ 734: function rss ($url,$params){ 735: global $lang; 736: global $conf; 737: 738: require_once(DOKU_INC.'inc/FeedParser.php'); 739: $feed = new FeedParser(); 740: $feed->set_feed_url($url); 741: 742: //disable warning while fetching 743: if (!defined('DOKU_E_LEVEL')) { $elvl = error_reporting(E_ERROR); } 744: $rc = $feed->init(); 745: if (!defined('DOKU_E_LEVEL')) { error_reporting($elvl); } 746: 747: //decide on start and end 748: if($params['reverse']){ 749: $mod = -1; 750: $start = $feed->get_item_quantity()-1; 751: $end = $start - ($params['max']); 752: $end = ($end < -1) ? -1 : $end; 753: }else{ 754: $mod = 1; 755: $start = 0; 756: $end = $feed->get_item_quantity(); 757: $end = ($end > $params['max']) ? $params['max'] : $end;; 758: } 759: 760: $this->doc .= '<ul class="rss">'; 761: if($rc){ 762: for ($x = $start; $x != $end; $x += $mod) { 763: $item = $feed->get_item($x); 764: $this->doc .= '<li><div class="li">'; 765: $this->externallink($item->get_permalink(), 766: $item->get_title()); 767: if($params['author']){ 768: $author = $item->get_author(0); 769: if($author){ 770: $name = $author->get_name(); 771: if(!$name) $name = $author->get_email(); 772: if($name) $this->doc .= ' '.$lang['by'].' '.$name; 773: } 774: } 775: if($params['date']){ 776: $this->doc .= ' ('.$item->get_local_date($conf['dformat']).')'; 777: } 778: if($params['details']){ 779: $this->doc .= '<div class="detail">'; 780: if($conf['htmlok']){ 781: $this->doc .= $item->get_description(); 782: }else{ 783: $this->doc .= strip_tags($item->get_description()); 784: } 785: $this->doc .= '</div>'; 786: } 787: 788: $this->doc .= '</div></li>'; 789: } 790: }else{ 791: $this->doc .= '<li><div class="li">'; 792: $this->doc .= '<em>'.$lang['rssfailed'].'</em>'; 793: $this->externallink($url); 794: if($conf['allowdebug']){ 795: $this->doc .= '<!--'.hsc($feed->error).'-->'; 796: } 797: $this->doc .= '</div></li>'; 798: } 799: $this->doc .= '</ul>'; 800: } 801: 802: // $numrows not yet implemented 803: function table_open($maxcols = NULL, $numrows = NULL){ 804: // initialize the row counter used for classes 805: $this->_counter['row_counter'] = 0; 806: $this->doc .= '<table class="inline">'.DOKU_LF; 807: } 808: 809: function table_close(){ 810: $this->doc .= '</table>'.DOKU_LF; 811: } 812: 813: function tablerow_open(){ 814: // initialize the cell counter used for classes 815: $this->_counter['cell_counter'] = 0; 816: $class = 'row' . $this->_counter['row_counter']++; 817: $this->doc .= DOKU_TAB . '<tr class="'.$class.'">' . DOKU_LF . DOKU_TAB . DOKU_TAB; 818: } 819: 820: function tablerow_close(){ 821: $this->doc .= DOKU_LF . DOKU_TAB . '</tr>' . DOKU_LF; 822: } 823: 824: function tableheader_open($colspan = 1, $align = NULL){ 825: $class = 'class="col' . $this->_counter['cell_counter']++; 826: if ( !is_null($align) ) { 827: $class .= ' '.$align.'align'; 828: } 829: $class .= '"'; 830: $this->doc .= '<th ' . $class; 831: if ( $colspan > 1 ) { 832: $this->_counter['cell_counter'] += $colspan; 833: $this->doc .= ' colspan="'.$colspan.'"'; 834: } 835: $this->doc .= '>'; 836: } 837: 838: function tableheader_close(){ 839: $this->doc .= '</th>'; 840: } 841: 842: function tablecell_open($colspan = 1, $align = NULL){ 843: $class = 'class="col' . $this->_counter['cell_counter']++; 844: if ( !is_null($align) ) { 845: $class .= ' '.$align.'align'; 846: } 847: $class .= '"'; 848: $this->doc .= '<td '.$class; 849: if ( $colspan > 1 ) { 850: $this->_counter['cell_counter'] += $colspan; 851: $this->doc .= ' colspan="'.$colspan.'"'; 852: } 853: $this->doc .= '>'; 854: } 855: 856: function tablecell_close(){ 857: $this->doc .= '</td>'; 858: } 859: 860: //---------------------------------------------------------- 861: // Utils 862: 863: /** 864: * Build a link 865: * 866: * Assembles all parts defined in $link returns HTML for the link 867: * 868: * @author Andreas Gohr <andi@splitbrain.org> 869: */ 870: function _formatLink($link){ 871: //make sure the url is XHTML compliant (skip mailto) 872: if(substr($link['url'],0,7) != 'mailto:'){ 873: $link['url'] = str_replace('&','&',$link['url']); 874: $link['url'] = str_replace('&amp;','&',$link['url']); 875: } 876: //remove double encodings in titles 877: $link['title'] = str_replace('&amp;','&',$link['title']); 878: 879: // be sure there are no bad chars in url or title 880: // (we can't do this for name because it can contain an img tag) 881: $link['url'] = strtr($link['url'],array('>'=>'%3E','<'=>'%3C','"'=>'%22')); 882: $link['title'] = strtr($link['title'],array('>'=>'>','<'=>'<','"'=>'"')); 883: 884: $ret = ''; 885: $ret .= $link['pre']; 886: $ret .= '<a href="'.$link['url'].'"'; 887: if(!empty($link['class'])) $ret .= ' class="'.$link['class'].'"'; 888: if(!empty($link['target'])) $ret .= ' target="'.$link['target'].'"'; 889: if(!empty($link['title'])) $ret .= ' title="'.$link['title'].'"'; 890: if(!empty($link['style'])) $ret .= ' style="'.$link['style'].'"'; 891: if(!empty($link['rel'])) $ret .= ' rel="'.$link['rel'].'"'; 892: if(!empty($link['more'])) $ret .= ' '.$link['more']; 893: $ret .= '>'; 894: $ret .= $link['name']; 895: $ret .= '</a>'; 896: $ret .= $link['suf']; 897: return $ret; 898: } 899: 900: /** 901: * Renders internal and external media 902: * 903: * @author Andreas Gohr <andi@splitbrain.org> 904: */ 905: function _media ($src, $title=NULL, $align=NULL, $width=NULL, 906: $height=NULL, $cache=NULL, $render = true) { 907: 908: $ret = ''; 909: 910: list($ext,$mime) = mimetype($src); 911: if(substr($mime,0,5) == 'image'){ 912: // first get the $title 913: if (!is_null($title)) { 914: $title = $this->_xmlEntities($title); 915: }elseif($ext == 'jpg' || $ext == 'jpeg'){ 916: //try to use the caption from IPTC/EXIF 917: require_once(DOKU_INC.'inc/JpegMeta.php'); 918: $jpeg =& new JpegMeta(mediaFN($src)); 919: if($jpeg !== false) $cap = $jpeg->getTitle(); 920: if($cap){ 921: $title = $this->_xmlEntities($cap); 922: } 923: } 924: if (!$render) { 925: // if the picture is not supposed to be rendered 926: // return the title of the picture 927: if (!$title) { 928: // just show the sourcename 929: $title = $this->_xmlEntities(basename(noNS($src))); 930: } 931: return $title; 932: } 933: //add image tag 934: $ret .= '<img src="'.ml($src,array('w'=>$width,'h'=>$height,'cache'=>$cache)).'"'; 935: $ret .= ' class="media'.$align.'"'; 936: 937: // make left/right alignment for no-CSS view work (feeds) 938: if($align == 'right') $ret .= ' align="right"'; 939: if($align == 'left') $ret .= ' align="left"'; 940: 941: if ($title) { 942: $ret .= ' title="' . $title . '"'; 943: $ret .= ' alt="' . $title .'"'; 944: }else{ 945: $ret .= ' alt=""'; 946: } 947: 948: if ( !is_null($width) ) 949: $ret .= ' width="'.$this->_xmlEntities($width).'"'; 950: 951: if ( !is_null($height) ) 952: $ret .= ' height="'.$this->_xmlEntities($height).'"'; 953: 954: $ret .= ' />'; 955: 956: }elseif($mime == 'application/x-shockwave-flash'){ 957: $ret .= '<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"'. 958: ' codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0"'; 959: if ( !is_null($width) ) $ret .= ' width="'.$this->_xmlEntities($width).'"'; 960: if ( !is_null($height) ) $ret .= ' height="'.$this->_xmlEntities($height).'"'; 961: $ret .= '>'.DOKU_LF; 962: $ret .= '<param name="movie" value="'.ml($src).'" />'.DOKU_LF; 963: $ret .= '<param name="quality" value="high" />'.DOKU_LF; 964: $ret .= '<embed src="'.ml($src).'"'. 965: ' quality="high"'; 966: if ( !is_null($width) ) $ret .= ' width="'.$this->_xmlEntities($width).'"'; 967: if ( !is_null($height) ) $ret .= ' height="'.$this->_xmlEntities($height).'"'; 968: $ret .= ' type="application/x-shockwave-flash"'. 969: ' pluginspage="http://www.macromedia.com/go/getflashplayer"></embed>'.DOKU_LF; 970: $ret .= '</object>'.DOKU_LF; 971: 972: }elseif($title){ 973: // well at least we have a title to display 974: $ret .= $this->_xmlEntities($title); 975: }else{ 976: // just show the sourcename 977: $ret .= $this->_xmlEntities(basename(noNS($src))); 978: } 979: 980: return $ret; 981: } 982: 983: function _xmlEntities($string) { 984: return htmlspecialchars($string,ENT_QUOTES,'UTF-8'); 985: } 986: 987: /** 988: * Creates a linkid from a headline 989: * 990: * @param string $title The headline title 991: * @param boolean $create Create a new unique ID? 992: * @author Andreas Gohr <andi@splitbrain.org> 993: */ 994: function _headerToLink($title,$create=false) { 995: $title = str_replace(':','',cleanID($title)); 996: $title = ltrim($title,'0123456789._-'); 997: if(empty($title)) $title='section'; 998: 999: if($create){ 1000: // make sure tiles are unique 1001: $num = ''; 1002: while(in_array($title.$num,$this->headers)){ 1003: ($num) ? $num++ : $num = 1; 1004: } 1005: $title = $title.$num; 1006: $this->headers[] = $title; 1007: } 1008: 1009: return $title; 1010: } 1011: 1012: /** 1013: * Construct a title and handle images in titles 1014: * 1015: * @author Harry Fuecks <hfuecks@gmail.com> 1016: */ 1017: function _getLinkTitle($title, $default, & $isImage, $id=NULL) { 1018: global $conf; 1019: 1020: $isImage = false; 1021: if ( is_null($title) ) { 1022: if ($conf['useheading'] && $id) { 1023: $heading = p_get_first_heading($id,true); 1024: if ($heading) { 1025: return $this->_xmlEntities($heading); 1026: } 1027: } 1028: return $this->_xmlEntities($default); 1029: } else if ( is_array($title) ) { 1030: $isImage = true; 1031: return $this->_imageTitle($title); 1032: } else { 1033: return $this->_xmlEntities($title); 1034: } 1035: } 1036: 1037: /** 1038: * Returns an HTML code for images used in link titles 1039: * 1040: * @todo Resolve namespace on internal images 1041: * @author Andreas Gohr <andi@splitbrain.org> 1042: */ 1043: function _imageTitle($img) { 1044: return $this->_media($img['src'], 1045: $img['title'], 1046: $img['align'], 1047: $img['width'], 1048: $img['height'], 1049: $img['cache']); 1050: } 1051: 1052: /** 1053: * _getMediaLinkConf is a helperfunction to internalmedia() and externalmedia() 1054: * which returns a basic link to a media. 1055: * 1056: * @author Pierre Spring <pierre.spring@liip.ch> 1057: * @param string $src 1058: * @param string $title 1059: * @param string $align 1060: * @param string $width 1061: * @param string $height 1062: * @param string $cache 1063: * @param string $render 1064: * @access protected 1065: * @return array 1066: */ 1067: function _getMediaLinkConf($src, $title, $align, $width, $height, $cache, $render) 1068: { 1069: global $conf; 1070: 1071: $link = array(); 1072: $link['class'] = 'media'; 1073: $link['style'] = ''; 1074: $link['pre'] = ''; 1075: $link['suf'] = ''; 1076: $link['more'] = ''; 1077: $link['target'] = $conf['target']['media']; 1078: $link['title'] = $this->_xmlEntities($src); 1079: $link['name'] = $this->_media($src, $title, $align, $width, $height, $cache, $render); 1080: 1081: return $link; 1082: } 1083: } 1084: 1085: //Setup VIM: ex: et ts=4 enc=utf-8 :
