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