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