xref: /dokuwiki/inc/parser/xhtml.php (revision a2d649c465f8f77c2b1dfdcc3724e69fde78a296)
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
9if(!defined('DOKU_INC')) define('DOKU_INC',realpath(dirname(__FILE__).'/../../').'/');
10
11if ( !defined('DOKU_LF') ) {
12    // Some whitespace to help View > Source
13    define ('DOKU_LF',"\n");
14}
15
16if ( !defined('DOKU_TAB') ) {
17    // Some whitespace to help View > Source
18    define ('DOKU_TAB',"\t");
19}
20
21require_once DOKU_INC . 'inc/parser/renderer.php';
22
23/**
24 * The Renderer
25 */
26class Doku_Renderer_xhtml extends Doku_Renderer {
27
28    var $doc = '';
29
30    var $headers = array();
31
32    var $footnotes = array();
33
34    var $footnoteIdStack = array();
35
36    var $acronyms = array();
37    var $smileys = array();
38    var $badwords = array();
39    var $entities = array();
40    var $interwiki = array();
41
42    var $lastsec = 0;
43
44    function document_start() {
45    }
46
47    function document_end() {
48        // add button for last section if any and more than one
49        if($this->lastsec > 1) $this->_secedit($this->lastsec,'');
50
51        if ( count ($this->footnotes) > 0 ) {
52            $this->doc .= '<div class="footnotes">'.DOKU_LF;
53            foreach ( $this->footnotes as $footnote ) {
54                $this->doc .= $footnote;
55            }
56            $this->doc .= '</div>'.DOKU_LF;
57        }
58    }
59
60    function toc_open() {
61        $this->doc .= '<div class="toc">'.DOKU_LF;
62        $this->doc .= '<div class="tocheader">Table of Contents <script type="text/javascript">showTocToggle("+","-")</script></div>'.DOKU_LF;
63        $this->doc .= '<div id="tocinside">'.DOKU_LF;
64    }
65
66    function tocbranch_open($level) {
67        $this->doc .= '<ul class="toc">'.DOKU_LF;
68    }
69
70    function tocitem_open($level, $empty = FALSE) {
71        if ( !$empty ) {
72            $this->doc .= '<li class="level'.$level.'">';
73        } else {
74            $this->doc .= '<li class="clear">';
75        }
76    }
77
78    function tocelement($level, $title) {
79        $this->doc .= '<span class="li"><a href="#'.$this->_headerToLink($title).'" class="toc">';
80        $this->doc .= $this->_xmlEntities($title);
81        $this->doc .= '</a></span>';
82    }
83
84    function tocitem_close($level) {
85        $this->doc .= '</li>'.DOKU_LF;
86    }
87
88    function tocbranch_close($level) {
89        $this->doc .= '</ul>'.DOKU_LF;
90    }
91
92    function toc_close() {
93        $this->doc .= '</div>'.DOKU_LF.'</div>'.DOKU_LF;
94    }
95
96    function header($text, $level, $pos) {
97        global $conf;
98        //handle section editing
99        if($level <= $conf['maxseclevel']){
100            // add button for last section if any
101            if($this->lastsec) $this->_secedit($this->lastsec,$pos-1);
102            // remember current position
103            $this->lastsec = $pos;
104        }
105
106        $this->doc .= DOKU_LF.'<a name="'.$this->_headerToLink($text).'"></a><h'.$level.'>';
107        $this->doc .= $this->_xmlEntities($text);
108        $this->doc .= "</h$level>".DOKU_LF;
109    }
110
111    function section_open($level) {
112        $this->doc .= "<div class=\"level$level\">".DOKU_LF;
113    }
114
115    function section_close() {
116        $this->doc .= DOKU_LF.'</div>'.DOKU_LF;
117    }
118
119    function cdata($text) {
120        $this->doc .= $this->_xmlEntities($text);
121    }
122
123    function p_open() {
124        $this->doc .= DOKU_LF.'<p>'.DOKU_LF;
125    }
126
127    function p_close() {
128        $this->doc .= DOKU_LF.'</p>'.DOKU_LF;
129    }
130
131    function linebreak() {
132        $this->doc .= '<br/>'.DOKU_LF;
133    }
134
135    function hr() {
136        $this->doc .= '<hr noshade="noshade" size="1" />'.DOKU_LF;
137    }
138
139    function strong_open() {
140        $this->doc .= '<strong>';
141    }
142
143    function strong_close() {
144        $this->doc .= '</strong>';
145    }
146
147    function emphasis_open() {
148        $this->doc .= '<em>';
149    }
150
151    function emphasis_close() {
152        $this->doc .= '</em>';
153    }
154
155    function underline_open() {
156        $this->doc .= '<u>';
157    }
158
159    function underline_close() {
160        $this->doc .= '</u>';
161    }
162
163    function monospace_open() {
164        $this->doc .= '<code>';
165    }
166
167    function monospace_close() {
168        $this->doc .= '</code>';
169    }
170
171    function subscript_open() {
172        $this->doc .= '<sub>';
173    }
174
175    function subscript_close() {
176        $this->doc .= '</sub>';
177    }
178
179    function superscript_open() {
180        $this->doc .= '<sup>';
181    }
182
183    function superscript_close() {
184        $this->doc .= '</sup>';
185    }
186
187    function deleted_open() {
188        $this->doc .= '<del>';
189    }
190
191    function deleted_close() {
192        $this->doc .= '</del>';
193    }
194
195    function footnote_open() {
196        $id = $this->_newFootnoteId();
197        $this->doc .= '<a href="#fn'.$id.'" name="fnt'.$id.'" class="fn_top">'.$id.')</a>';
198        $this->footnoteIdStack[] = $id;
199        ob_start();
200    }
201
202    function footnote_close() {
203        $contents = ob_get_contents();
204        ob_end_clean();
205        $id = array_pop($this->footnoteIdStack);
206
207        $contents = '<div class="fn"><a href="#fnt'.
208            $id.'" name="fn'.$id.'" class="fn_bot">'.
209                $id.')</a> ' .DOKU_LF .$contents. "\n" . '</div>' . DOKU_LF;
210        $this->footnotes[$id] = $contents;
211    }
212
213    function listu_open() {
214        $this->doc .= '<ul>'.DOKU_LF;
215    }
216
217    function listu_close() {
218        $this->doc .= '</ul>'.DOKU_LF;
219    }
220
221    function listo_open() {
222        $this->doc .= '<ol>'.DOKU_LF;
223    }
224
225    function listo_close() {
226        $this->doc .= '</ol>'.DOKU_LF;
227    }
228
229    function listitem_open($level) {
230        $this->doc .= '<li class="level'.$level.'">';
231    }
232
233    function listitem_close() {
234        $this->doc .= '</li>'.DOKU_LF;
235    }
236
237    function listcontent_open() {
238        $this->doc .= '<span class="li">';
239    }
240
241    function listcontent_close() {
242        $this->doc .= '</span>'.DOKU_LF;
243    }
244
245    function unformatted($text) {
246        $this->doc .= $this->_xmlEntities($text);
247    }
248
249    /**
250    */
251    function php($text) {
252        global $conf;
253        if($conf['phpok']){
254            eval($text);
255        }else{
256            $this->file($text);
257        }
258    }
259
260    /**
261    */
262    function html($text) {
263        global $conf;
264        if($conf['htmlok']){
265          $this->doc .= $text;
266        }else{
267          $this->file($text);
268        }
269    }
270
271    function preformatted($text) {
272        $this->doc .= '<pre class="code">' . $this->_xmlEntities($text) . '</pre>'. DOKU_LF;
273    }
274
275    function file($text) {
276        $this->doc .= '<pre class="file">' . $this->_xmlEntities($text). '</pre>'. DOKU_LF;
277    }
278
279    /**
280    * @TODO Shouldn't this output <blockquote??
281    */
282    function quote_open() {
283        $this->doc .= '<div class="quote">'.DOKU_LF;
284    }
285
286    /**
287    * @TODO Shouldn't this output </blockquote>?
288    */
289    function quote_close() {
290        $this->doc .= '</div>'.DOKU_LF;
291    }
292
293    /**
294    */
295    function code($text, $language = NULL) {
296        global $conf;
297
298        if ( is_null($language) ) {
299            $this->preformatted($text);
300        } else {
301            // Handle with Geshi here FIXME: strip first beginning newline
302            require_once(DOKU_INC . 'inc/geshi.php');
303            $geshi = new GeSHi($text, strtolower($language), DOKU_INC . 'inc/geshi');
304            $geshi->enable_classes();
305            $geshi->set_header_type(GESHI_HEADER_PRE);
306            $geshi->set_overall_class('code');
307            $geshi->set_link_target($conf['target']['extern']);
308
309            $text = $geshi->parse_code();
310            $this->doc .= $text;
311        }
312    }
313
314    function acronym($acronym) {
315
316        if ( array_key_exists($acronym, $this->acronyms) ) {
317
318            $title = $this->_xmlEntities($this->acronyms[$acronym]);
319
320            $this->doc .= '<acronym title="'.$title
321                .'">'.$this->_xmlEntities($acronym).'</acronym>';
322
323        } else {
324            $this->doc .= $this->_xmlEntities($acronym);
325        }
326    }
327
328    /**
329    */
330    function smiley($smiley) {
331        if ( array_key_exists($smiley, $this->smileys) ) {
332            $title = $this->_xmlEntities($this->smileys[$smiley]);
333            $this->doc .= '<img src="'.DOKU_BASE.'smileys/'.$this->smileys[$smiley].
334                '" align="middle" alt="'.
335                    $this->_xmlEntities($smiley).'" />';
336        } else {
337            $this->doc .= $this->_xmlEntities($smiley);
338        }
339    }
340
341    /**
342    * not used
343    function wordblock($word) {
344        if ( array_key_exists($word, $this->badwords) ) {
345            $this->doc .= '** BLEEP **';
346        } else {
347            $this->doc .= $this->_xmlEntities($word);
348        }
349    }
350    */
351
352    function entity($entity) {
353        if ( array_key_exists($entity, $this->entities) ) {
354            $this->doc .= $this->entities[$entity];
355        } else {
356            $this->doc .= $this->_xmlEntities($entity);
357        }
358    }
359
360    function multiplyentity($x, $y) {
361        $this->doc .= "$x&times;$y";
362    }
363
364    function singlequoteopening() {
365        $this->doc .= "&lsquo;";
366    }
367
368    function singlequoteclosing() {
369        $this->doc .= "&rsquo;";
370    }
371
372    function doublequoteopening() {
373        $this->doc .= "&ldquo;";
374    }
375
376    function doublequoteclosing() {
377        $this->doc .= "&rdquo;";
378    }
379
380    /**
381    */
382    function camelcaselink($link) {
383      $this->internallink($link,$link);
384    }
385
386    /**
387     * $search and $returnonly are not for the renderer but are used
388     * elsewhere - no need to implement them in other renderers
389     */
390    function internallink($id, $name = NULL, $search=NULL,$returnonly=false) {
391        global $conf;
392        global $ID;
393
394        $name = $this->_getLinkTitle($name, $this->_simpleTitle($id), $isImage, $id);
395        resolve_pageid(getNS($ID),$id,$exists);
396
397        if ( !$isImage ) {
398            if ( $exists ) {
399                $class='wikilink1';
400            } else {
401                $class='wikilink2';
402            }
403        } else {
404            $class='media';
405        }
406
407        //prepare for formating
408        $link['target'] = $conf['target']['wiki'];
409        $link['style']  = '';
410        $link['pre']    = '';
411        $link['suf']    = '';
412        $link['more']   = 'onclick="return svchk()" onkeypress="return svchk()"';
413        $link['class']  = $class;
414        $link['url']    = wl($id);
415        $link['name']   = $name;
416        $link['title']  = $id;
417
418        //add search string
419        if($search){
420            ($conf['userewrite']) ? $link['url'].='?s=' : $link['url'].='&amp;s=';
421            $link['url'] .= urlencode($search);
422        }
423
424        //output formatted
425        if($returnonly){
426            return $this->_formatLink($link);
427        }else{
428            $this->doc .= $this->_formatLink($link);
429        }
430    }
431
432    function externallink($url, $name = NULL) {
433        global $conf;
434
435        $name = $this->_getLinkTitle($name, $url, $isImage);
436
437        if ( !$isImage ) {
438            $class='urlextern';
439        } else {
440            $class='media';
441        }
442
443        //prepare for formating
444        $link['target'] = $conf['target']['extern'];
445        $link['style']  = '';
446        $link['pre']    = '';
447        $link['suf']    = '';
448        $link['more']   = 'onclick="return svchk()" onkeypress="return svchk()"';
449        $link['class']  = $class;
450        $link['url']    = $url;
451        $link['name']   = $name;
452        $link['title']  = $this->_xmlEntities($url);
453        if($conf['relnofollow']) $link['more'] .= ' rel="nofollow"';
454
455        //output formatted
456        $this->doc .= $this->_formatLink($link);
457    }
458
459    /**
460    */
461    function interwikilink($match, $name = NULL, $wikiName, $wikiUri) {
462        global $conf;
463
464        $link = array();
465        $link['target'] = $conf['target']['interwiki'];
466        $link['pre']    = '';
467        $link['suf']    = '';
468        $link['more']   = 'onclick="return svchk()" onkeypress="return svchk()"';
469        $link['name']   = $this->_getLinkTitle($name, $wikiUri, $isImage);
470
471        if ( !$isImage ) {
472            $link['class'] = 'interwiki';
473        } else {
474            $link['class'] = 'media';
475        }
476
477        //get interwiki URL
478        if ( isset($this->interwiki[$wikiName]) ) {
479            $url = $this->interwiki[$wikiName];
480        } else {
481            // Default to Google I'm feeling lucky
482            $url = 'http://www.google.com/search?q={URL}&amp;btnI=lucky';
483            $wikiName = 'go';
484        }
485
486        if(!$isImage){
487            //if ico exists set additional style
488            if(@file_exists(DOKU_INC.'interwiki/'.$wikiName.'.png')){
489                $link['style']='background: transparent url('.DOKU_BASE.'interwiki/'.$wikiName.'.png) 0px 1px no-repeat;';
490            }elseif(@file_exists(DOKU_INC.'interwiki/'.$wikiName.'.gif')){
491                $link['style']='background: transparent url('.DOKU_BASE.'interwiki/'.$wikiName.'.gif) 0px 1px no-repeat;';
492            }
493        }
494
495        //do we stay at the same server? Use local target
496        if( strpos($url,DOKU_URL) === 0 ){
497            $link['target'] = $conf['target']['wiki'];
498        }
499
500        //replace placeholder
501        if(preg_match('#\{(URL|NAME|SCHEME|HOST|PORT|PATH|QUERY)\}#',$url)){
502            //use placeholders
503            $url = str_replace('{URL}',urlencode($wikiUri),$url);
504            $url = str_replace('{NAME}',$wikiUri,$url);
505            $parsed = parse_url($wikiUri);
506            if(!$parsed['port']) $parsed['port'] = 80;
507            $url = str_replace('{SCHEME}',$parsed['scheme'],$url);
508            $url = str_replace('{HOST}',$parsed['host'],$url);
509            $url = str_replace('{PORT}',$parsed['port'],$url);
510            $url = str_replace('{PATH}',$parsed['path'],$url);
511            $url = str_replace('{QUERY}',$parsed['query'],$url);
512            $link['url'] = $url;
513        }else{
514            //default
515            $link['url'] = $url.urlencode($wikiUri);
516        }
517
518        $link['title'] = htmlspecialchars($link['url']);
519
520        //output formatted
521        $this->doc .= $this->_formatLink($link);
522    }
523
524    /*
525     * @deprecated not used!!!
526     * @TODO Correct the CSS class for files? (not windows)
527     * @TODO Remove hard coded URL to splitbrain.org
528    function filelink($link, $title = NULL) {
529        $this->doc .= '<a';
530
531        $title = $this->_getLinkTitle($title, $link, $isImage);
532
533        if ( !$isImage ) {
534            $this->doc .= ' class="windows"';
535        } else {
536            $this->doc .= ' class="media"';
537        }
538
539        $this->doc .= ' href="'.$this->_xmlEntities($link).'"';
540
541        $this->doc .= ' style="background: transparent url(http://wiki.splitbrain.org/images/windows.gif) 0px 1px no-repeat;"';
542
543        $this->doc .= ' onclick="return svchk()" onkeypress="return svchk()">';
544
545        $this->doc .= $title;
546
547        $this->doc .= '</a>';
548    }
549    */
550
551    /**
552    */
553    function windowssharelink($url, $name = NULL) {
554        global $conf;
555        global $lang;
556        //simple setup
557        $link['target'] = $conf['target']['windows'];
558        $link['pre']    = '';
559        $link['suf']   = '';
560        $link['style']  = '';
561        //Display error on browsers other than IE
562        $link['more'] = 'onclick="if(document.all == null){alert(\''.
563                        $this->_xmlEntities($lang['nosmblinks'],ENT_QUOTES).
564                        '\');}" onkeypress="if(document.all == null){alert(\''.
565                        $this->_xmlEntities($lang['nosmblinks'],ENT_QUOTES).'\');}"';
566
567        $link['name'] = $this->_getLinkTitle($name, $url, $isImage);
568        if ( !$isImage ) {
569            $link['class'] = 'windows';
570        } else {
571            $link['class'] = 'media';
572        }
573
574
575        $link['title'] = $this->_xmlEntities($url);
576        $url = str_replace('\\','/',$url);
577        $url = 'file:///'.$url;
578        $link['url'] = $url;
579
580        //output formatted
581        $this->doc .= $this->_formatLink($link);
582    }
583
584    function emaillink($address, $name = NULL) {
585        global $conf;
586        //simple setup
587        $link = array();
588        $link['target'] = '';
589        $link['pre']    = '';
590        $link['suf']   = '';
591        $link['style']  = '';
592        $link['more']   = '';
593
594        //we just test for image here - we need to encode the title our self
595        $this->_getLinkTitle($name, $address, $isImage);
596        if ( !$isImage ) {
597            $link['class']='mail';
598        } else {
599            $link['class']='media';
600        }
601
602        //shields up
603        if($conf['mailguard']=='visible'){
604            //the mail name gets some visible encoding
605            $address = str_replace('@',' [at] ',$address);
606            $address = str_replace('.',' [dot] ',$address);
607            $address = str_replace('-',' [dash] ',$address);
608
609            $title   = $this->_xmlEntities($address);
610            if(empty($name)){
611                $name = $this->_xmlEntities($address);
612            }else{
613                $name = $this->_xmlEntities($name);
614            }
615        }elseif($conf['mailguard']=='hex'){
616            //encode every char to a hex entity
617            for ($x=0; $x < strlen($address); $x++) {
618                $encode .= '&#x' . bin2hex($address[$x]).';';
619            }
620            $address = $encode;
621            $title   = $encode;
622            if(empty($name)){
623                $name = $encode;
624            }else{
625                $name = $this->_xmlEntities($name);
626            }
627        }else{
628            //keep address as is
629            $title   = $this->_xmlEntities($address);
630            if(empty($name)){
631                $name = $this->_xmlEntities($address);
632            }else{
633                $name = $this->_xmlEntities($name);
634            }
635        }
636
637        $link['url']   = 'mailto:'.$address;
638        $link['name']  = $name;
639        $link['title'] = $title;
640
641        //output formatted
642        $this->doc .= $this->_formatLink($link);
643    }
644
645    /**
646     * @todo don't add link for flash
647     */
648    function internalmedia ($src, $title=NULL, $align=NULL, $width=NULL,
649                            $height=NULL, $cache=NULL) {
650        global $conf;
651        global $ID;
652        resolve_mediaid(getNS($ID),$src, $exists);
653
654        $link = array();
655        $link['class']  = 'media';
656        $link['style']  = '';
657        $link['pre']    = '';
658        $link['suf']    = '';
659        $link['more']   = 'onclick="return svchk()" onkeypress="return svchk()"';
660        $link['target'] = $conf['target']['media'];
661
662        $link['title']  = $this->_xmlEntities($src);
663        $link['url']    = DOKU_BASE.'fetch.php?cache='.$cache.'&amp;media='.urlencode($src);
664        $link['name']   = $this->_media ($src, $title, $align, $width, $height, $cache);
665
666
667        //output formatted
668        $this->doc .= $this->_formatLink($link);
669    }
670
671    /**
672     * @todo don't add link for flash
673     */
674    function externalmedia ($src, $title=NULL, $align=NULL, $width=NULL,
675                            $height=NULL, $cache=NULL) {
676        global $conf;
677
678        $link = array();
679        $link['class']  = 'media';
680        $link['style']  = '';
681        $link['pre']    = '';
682        $link['suf']    = '';
683        $link['more']   = 'onclick="return svchk()" onkeypress="return svchk()"';
684        $link['target'] = $conf['target']['media'];
685
686        $link['title']  = $this->_xmlEntities($src);
687        $link['url']    = DOKU_BASE.'fetch.php?cache='.$cache.'&amp;media='.urlencode($src);
688        $link['name']   = $this->_media ($src, $title, $align, $width, $height, $cache);
689
690
691        //output formatted
692        $this->doc .= $this->_formatLink($link);
693    }
694
695    /**
696     * Renders an RSS feed using magpie
697     *
698     * @author Andreas Gohr <andi@splitbrain.org>
699     */
700    function rss ($url){
701        global $lang;
702        define('MAGPIE_CACHE_ON', false); //we do our own caching
703        define('MAGPIE_DIR', DOKU_INC.'inc/magpie/');
704        define('MAGPIE_OUTPUT_ENCODING','UTF-8'); //return all feeds as UTF-8
705        require_once(MAGPIE_DIR.'/rss_fetch.inc');
706
707        //disable warning while fetching
708        $elvl = error_reporting(E_ERROR);
709        $rss  = fetch_rss($url);
710        error_reporting($elvl);
711
712        $this->doc .= '<ul class="rss">';
713        if($rss){
714            foreach ($rss->items as $item ) {
715                $this->doc .= '<li>';
716                $this->externallink($item['link'],$item['title']);
717                $this->doc .= '</li>';
718            }
719        }else{
720            $this->doc .= '<li>';
721            $this->doc .= '<em>'.$lang['rssfailed'].'</em>';
722            $this->externallink($url);
723            $this->doc .= '</li>';
724        }
725        $this->doc .= '</ul>';
726    }
727
728    /**
729     * Renders internal and external media
730     *
731     * @author Andreas Gohr <andi@splitbrain.org>
732     * @todo   handle center align
733     * @todo   move to bottom
734     */
735    function _media ($src, $title=NULL, $align=NULL, $width=NULL,
736                      $height=NULL, $cache=NULL) {
737
738        $ret = '';
739
740        list($ext,$mime) = mimetype($src);
741        if(substr($mime,0,5) == 'image'){
742            //add image tag
743            $ret .= '<img src="'.DOKU_BASE.'fetch.php?w='.$width.'&amp;h='.$height.
744                    '&amp;cache='.$cache.'&amp;media='.urlencode($src).'"';
745
746            $ret .= ' class="media'.$align.'"';
747
748            if (!is_null($title)) {
749                $ret .= ' title="'.$this->_xmlEntities($title).'"';
750                $ret .= ' alt="'.$this->_xmlEntities($title).'"';
751            }else{
752                $ret .= ' alt=""';
753            }
754
755            if ( !is_null($width) )
756                $ret .= ' width="'.$this->_xmlEntities($width).'"';
757
758            if ( !is_null($height) )
759                $ret .= ' height="'.$this->_xmlEntities($height).'"';
760
761            $ret .= ' />';
762
763        }elseif($mime == 'application/x-shockwave-flash'){
764            //FIXME default to a higher flash version?
765
766            $ret .= '<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"'.
767                    ' codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=5,0,0,0"';
768            if ( !is_null($width) ) $ret .= ' width="'.$this->_xmlEntities($width).'"';
769            if ( !is_null($height) ) $ret .= ' height="'.$this->_xmlEntities($height).'"';
770            $ret .= '>'.DOKU_LF;
771            $ret .= '<param name="movie" value="'.DOKU_BASE.'fetch.php?media='.urlencode($src).'" />'.DOKU_LF;
772            $ret .= '<param name="quality" value="high" />'.DOKU_LF;
773            $ret .= '<embed src="'.DOKU_BASE.'fetch.php?media='.urlencode($src).'"'.
774                    ' quality="high" bgcolor="#000000"';
775            if ( !is_null($width) ) $ret .= ' width="'.$this->_xmlEntities($width).'"';
776            if ( !is_null($height) ) $ret .= ' height="'.$this->_xmlEntities($height).'"';
777            $ret .= ' type="application/x-shockwave-flash"'.
778                    ' pluginspage="http://www.macromedia.com/shockwave/download/index.cgi'.
779                    '?P1_Prod_Version=ShockwaveFlash"></embed>'.DOKU_LF;
780            $ret .= '</object>'.DOKU_LF;
781
782        }elseif(!is_null($title)){
783            // well at least we have a title to display
784            $ret .= $this->_xmlEntities($title);
785        }else{
786            // just show the source
787            $ret .= $this->_xmlEntities($src);
788        }
789
790        return $ret;
791    }
792
793    // $numrows not yet implemented
794    function table_open($maxcols = NULL, $numrows = NULL){
795        $this->doc .= '<table class="inline">'.DOKU_LF;
796    }
797
798    function table_close(){
799        $this->doc .= '</table>'.DOKU_LF.'<br />'.DOKU_LF;
800    }
801
802    function tablerow_open(){
803        $this->doc .= DOKU_TAB . '<tr>' . DOKU_LF . DOKU_TAB . DOKU_TAB;
804    }
805
806    function tablerow_close(){
807        $this->doc .= DOKU_LF . DOKU_TAB . '</tr>' . DOKU_LF;
808    }
809
810    function tableheader_open($colspan = 1, $align = NULL){
811        $this->doc .= '<th';
812        if ( !is_null($align) ) {
813            $this->doc .= ' class="'.$align.'align"';
814        }
815        if ( $colspan > 1 ) {
816            $this->doc .= ' colspan="'.$colspan.'"';
817        }
818        $this->doc .= '>';
819    }
820
821    function tableheader_close(){
822        $this->doc .= '</th>';
823    }
824
825    function tablecell_open($colspan = 1, $align = NULL){
826        $this->doc .= '<td';
827        if ( !is_null($align) ) {
828            $this->doc .= ' class="'.$align.'align"';
829        }
830        if ( $colspan > 1 ) {
831            $this->doc .= ' colspan="'.$colspan.'"';
832        }
833        $this->doc .= '>';
834    }
835
836    function tablecell_close(){
837        $this->doc .= '</td>';
838    }
839
840    //----------------------------------------------------------
841    // Utils
842
843    /**
844     * Assembles all parts defined by the link formater below
845     * Returns HTML for the link
846     *
847     * @author Andreas Gohr <andi@splitbrain.org>
848     */
849    function _formatLink($link){
850        //make sure the url is XHTML compliant (skip mailto)
851        if(substr($link['url'],0,7) != 'mailto:'){
852            $link['url'] = str_replace('&','&amp;',$link['url']);
853            $link['url'] = str_replace('&amp;amp;','&amp;',$link['url']);
854        }
855        //remove double encodings in titles
856        $link['title'] = str_replace('&amp;amp;','&amp;',$link['title']);
857
858        $ret  = '';
859        $ret .= $link['pre'];
860        $ret .= '<a href="'.$link['url'].'"';
861        if($link['class'])  $ret .= ' class="'.$link['class'].'"';
862        if($link['target']) $ret .= ' target="'.$link['target'].'"';
863        if($link['title'])  $ret .= ' title="'.$link['title'].'"';
864        if($link['style'])  $ret .= ' style="'.$link['style'].'"';
865        if($link['more'])   $ret .= ' '.$link['more'];
866        $ret .= '>';
867        $ret .= $link['name'];
868        $ret .= '</a>';
869        $ret .= $link['suf'];
870        return $ret;
871    }
872
873    /**
874     * Removes any Namespace from the given name but keeps
875     * casing and special chars
876     *
877     * @author Andreas Gohr <andi@splitbrain.org>
878     */
879    function _simpleTitle($name){
880        global $conf;
881        if($conf['useslash']){
882            $nssep = '[:;/]';
883        }else{
884            $nssep = '[:;]';
885        }
886        return preg_replace('!.*'.$nssep.'!','',$name);
887    }
888
889
890    function _newFootnoteId() {
891        static $id = 1;
892        return $id++;
893    }
894
895    function _xmlEntities($string) {
896        return htmlspecialchars($string);
897    }
898
899    /**
900    * @TODO Tuning needed - e.g. utf8 strtolower ?
901    */
902    function _headerToLink($title) {
903        return str_replace(':','',cleanID($title));
904    }
905
906    /**
907     * Adds code for section editing button
908     */
909    function _secedit($f, $t){
910        $this->doc .= '<!-- SECTION ['.$f.'-'.$t.'] -->';
911    }
912
913    function _getLinkTitle($title, $default, & $isImage, $id=NULL) {
914        global $conf;
915
916        $isImage = FALSE;
917
918        if ( is_null($title) ) {
919	  if ($conf['useheading'] && $id) {
920	    $heading = p_get_first_heading($id);
921	    if ($heading) {
922	      return $this->_xmlEntities($heading);
923	    }
924	  }
925	  return $this->_xmlEntities($default);
926
927        } else if ( is_string($title) ) {
928
929            return $this->_xmlEntities($title);
930
931        } else if ( is_array($title) ) {
932
933            $isImage = TRUE;
934            return $this->_imageTitle($title);
935
936        }
937    }
938
939    /**
940     * @TODO Resolve namespace on internal images
941     */
942    function _imageTitle($img) {
943
944        //FIXME resolve internal links
945
946        return $this->_media($img['src'],
947                              $img['title'],
948                              $img['align'],
949                              $img['width'],
950                              $img['height'],
951                              $img['cache']);
952
953/*
954        if ( $img['type'] == 'internalmedia' ) {
955
956            // Resolve here...
957            if ( strpos($img['src'],':') ) {
958                $src = explode(':',$img['src']);
959                $src = $src[1];
960            } else {
961                $src = $img['src'];
962            }
963
964            $imgStr = '<img class="media" src="http://wiki.splitbrain.org/media/wiki/'.$this->_xmlEntities($src).'"';
965
966        } else {
967
968            $imgStr = '<img class="media" src="'.$this->_xmlEntities($img['src']).'"';
969
970        }
971
972        if ( !is_null($img['title']) ) {
973            $imgStr .= ' alt="'.$this->_xmlEntities($img['title']).'"';
974        } else {
975            $imgStr .= ' alt=""';
976        }
977
978        if ( !is_null($img['align']) ) {
979            $imgStr .= ' align="'.$img['align'].'"';
980        }
981
982        if ( !is_null($img['width']) ) {
983            $imgStr .= ' width="'.$this->_xmlEntities($img['width']).'"';
984        }
985
986        if ( !is_null($img['height']) ) {
987            $imgStr .= ' height="'.$this->_xmlEntities($img['height']).'"';
988        }
989
990        $imgStr .= '/>';
991
992        return $imgStr;
993*/
994    }
995}
996
997/**
998* Test whether there's an image to display with this interwiki link
999*/
1000function interwikiImgExists($name) {
1001
1002    static $exists = array();
1003
1004    if ( array_key_exists($name,$exists) ) {
1005        return $exists[$name];
1006    }
1007
1008    if( @file_exists( DOKU. 'interwiki/'.$name.'.png') ) {
1009        $exists[$name] = 'png';
1010    } else if ( @file_exists( DOKU . 'interwiki/'.$name.'.gif') ) {
1011        $exists[$name] = 'gif';
1012    } else {
1013        $exists[$name] = FALSE;
1014    }
1015
1016    return $exists[$name];
1017}
1018
1019/**
1020 * For determining whether to use CSS class "wikilink1" or "wikilink2"
1021 * @todo use configinstead of DOKU_DATA
1022 * @deprecated -> resolve_pagename should be used
1023 */
1024function wikiPageExists($name) {
1025msg("deprecated wikiPageExists called",-1);
1026    static $pages = array();
1027
1028    if ( array_key_exists($name,$pages) ) {
1029        return $pages[$name];
1030    }
1031
1032    $file = str_replace(':','/',$name).'.txt';
1033
1034    if ( @file_exists( DOKU_DATA . $file ) ) {
1035        $pages[$name] = TRUE;
1036    } else {
1037        $pages[$name] = FALSE;
1038    }
1039
1040    return $pages[$name];
1041}
1042
1043
1044//Setup VIM: ex: et ts=4 enc=utf-8 :
1045