<?php /** * DokuWiki Plugin xml * * @author Patrick Bueker <Patrick@patrickbueker.de> * @author Danny Lin <danny0838@gmail.com> * @license GPLv2 or later (http://www.gnu.org/licenses/gpl.html) * */ // must be run within Dokuwiki if(!defined('DOKU_INC')) die(); if (!defined('DOKU_LF')) define('DOKU_LF', "\n"); if (!defined('DOKU_TAB')) define('DOKU_TAB', "\t"); require_once DOKU_INC . 'inc/parser/renderer.php'; class renderer_plugin_xml extends Doku_Renderer { function __construct() { $this->reset(); } /** * Allows renderer to be used again. Clean out any per-use values. */ function reset() { $this->info = array( 'cache' => true, // may the rendered result cached? 'toc' => false, // render the TOC? ); $this->precedinglevel = array(); $this->nextHeader = ""; $this->helper = &plugin_load('helper','xml'); $this->doc = ''; $this->tagStack = array(); } /** * Returns the format produced by this renderer. * * @return string */ function getFormat(){return 'xml';} /** * handle plugin rendering */ function plugin($name,$data){ $plugin =& plugin_load('syntax',$name); if ($plugin == null) return; if ($this->helper->_xml_extension($this,$name,$data)) return; $plugin->render($this->getFormat(),$this,$data); } /** * Handles instructions * * @Stack: add post-start linefeed and post-end linefeed * @Stack content: add post-start tab and post-end linefeed * @Block: add post-end linefeed * * ~~SOMETHING>params~~ treated as <macro type="something" params /> */ function document_start() { global $ID; global $INFO; $this->doc = '<?xml version="1.0" encoding="UTF-8"?>'.DOKU_LF; $this->doc .= '<document domain="' . DOKU_URL .'" id="' . cleanID($ID) . '" revision="' . $INFO['rev'] . '" lastmod="' . $INFO['lastmod'] . '">'.DOKU_LF; // store the content type headers in metadata $output_filename = str_replace(':','-',$ID).".xml"; $headers = array( 'Content-Type' => 'text/xml; charset=utf-8;', 'Content-Disposition' => 'attachment; filename="'.$output_filename.'";', ); p_set_metadata($ID,array('format' => array('xml' => $headers) )); } function document_end() { while(count($this->precedinglevel)>0) { $this->doc .= '</section>'.'<!--' . array_pop($this->precedinglevel) . '-->'.DOKU_LF; } $this->doc .= '</document>'.DOKU_LF; } function header($text, $level, $pos) { if (!$text) return; //skip empty headlines $this->nextHeader = '<header level="' . $level . '" pos="' . $pos . '">'. $this->nextHeader .= $this->_xmlEntities($text); $this->nextHeader .= '</header>'.DOKU_LF; } function section_open($level) { while(end($this->precedinglevel) >= $level) { $this->doc .= '</section>'.'<!--' . array_pop($this->precedinglevel) . '-->'.DOKU_LF; } $this->doc .= '<section level="' . $level . '">'.DOKU_LF; $this->doc .= $this->nextHeader; $this->nextHeader = ""; array_push($this->precedinglevel,$level); } function section_close() { #$this->doc .= '</section>'.DOKU_LF; } function nocache() { $this->info['cache'] = false; $this->doc .= '<macro name="nocache" />'.DOKU_LF; } function notoc() { $this->info['toc'] = false; $this->doc .= '<macro name="notoc" />'.DOKU_LF; } function cdata($text) { $this->doc .= $this->_xmlEntities($text); } function p_open() { $this->doc .= '<p>'; $this->_openTag($this, 'p_close', array()); } function p_close() { $this->_closeTags($this, __FUNCTION__); $this->doc .= '</p>'.DOKU_LF; } function linebreak() { $this->doc .= '<linebreak/>'; } function hr() { $this->doc .= '<hr/>'.DOKU_LF; } function strong_open() { $this->doc .= '<strong>'; $this->_openTag($this, 'strong_close', array()); } function strong_close() { $this->_closeTags($this, __FUNCTION__); $this->doc .= '</strong>'; } function emphasis_open() { $this->doc .= '<emphasis>'; $this->_openTag($this, 'emphasis_close', array()); } function emphasis_close() { $this->_closeTags($this, __FUNCTION__); $this->doc .= '</emphasis>'; } function underline_open() { $this->doc .= '<underline>'; $this->_openTag($this, 'underline_close', array()); } function underline_close() { $this->_closeTags($this, __FUNCTION__); $this->doc .= '</underline>'; } function monospace_open() { $this->doc .= '<monospace>'; $this->_openTag($this, 'monospace_close', array()); } function monospace_close() { $this->_closeTags($this, __FUNCTION__); $this->doc .= '</monospace>'; } function subscript_open() { $this->doc .= '<subscript>'; $this->_openTag($this, 'subscript_close', array()); } function subscript_close() { $this->_closeTags($this, __FUNCTION__); $this->doc .= '</subscript>'; } function superscript_open() { $this->doc .= '<superscript>'; $this->_openTag($this, 'superscript_close', array()); } function superscript_close() { $this->_closeTags($this, __FUNCTION__); $this->doc .= '</superscript>'; } function deleted_open() { $this->doc .= '<delete>'; $this->_openTag($this, 'deleted_close', array()); } function deleted_close() { $this->_closeTags($this, __FUNCTION__); $this->doc .= '</delete>'; } function footnote_open() { $this->doc .= '<footnote>'; $this->_openTag($this, 'footnote_close', array()); } function footnote_close() { $this->_closeTags($this, __FUNCTION__); $this->doc .= '</footnote>'; } function listu_open() { $this->doc .= '<listu>'.DOKU_LF; $this->_openTag($this, 'listu_close', array()); } function listu_close() { $this->_closeTags($this, __FUNCTION__); $this->doc .= '</listu>'.DOKU_LF; } function listo_open() { $this->doc .= '<listo>'.DOKU_LF; $this->_openTag($this, 'listo_close', array()); } function listo_close() { $this->_closeTags($this, __FUNCTION__); $this->doc .= '</listo>'.DOKU_LF; } function listitem_open($level) { $this->doc .= DOKU_TAB.'<listitem level="' . $level . '">'; $this->_openTag($this, 'listitem_close', array()); } function listitem_close() { $this->_closeTags($this, __FUNCTION__); $this->doc .= '</listitem>'.DOKU_LF; } function listcontent_open() { $this->doc .= '<listcontent>'; $this->_openTag($this, 'listcontent_close', array()); } function listcontent_close() { $this->_closeTags($this, __FUNCTION__); $this->doc .= '</listcontent>'; } function unformatted($text) { $this->doc .= '<unformatted>'; $this->doc .= $this->_xmlEntities($text); $this->doc .= '</unformatted>'; } function php($text) { $this->doc .= '<php>'; $this->doc .= $this->_xmlEntities($text); $this->doc .= '</php>'; } function phpblock($text) { $this->doc .= '<phpblock>'; $this->doc .= $this->_xmlEntities($text); $this->doc .= '</phpblock>'.DOKU_LF; } function html($text) { $this->doc .= '<html>'; $this->doc .= $this->_xmlEntities($text); $this->doc .= '</html>'; } function htmlblock($text) { $this->doc .= '<htmlblock>'; $this->doc .= $this->_xmlEntities($text); $this->doc .= '</htmlblock>'.DOKU_LF; } function preformatted($text) { $this->doc .= '<preformatted>'; $this->doc .= $this->_xmlEntities($text); $this->doc .= '</preformatted>'.DOKU_LF; } function quote_open() { $this->doc .= '<quote>'; $this->_openTag($this, 'quote_close', array()); } function quote_close() { $this->_closeTags($this, __FUNCTION__); $this->doc .= '</quote>'.DOKU_LF; } function code($text, $lang = null, $file = null) { $this->doc .= '<code lang="' . $lang . '" file="' . $file . '">'; $this->doc .= $this->_xmlEntities($text); $this->doc .= '</code>'.DOKU_LF; } function file($text, $lang = null, $file = null) { $this->doc .= '<file lang="' . $lang . '" file="' . $file . '">'; $this->doc .= $this->_xmlEntities($text); $this->doc .= '</file>'.DOKU_LF; } function acronym($acronym) { $this->doc .= '<acronym data="' . $this->_xmlEntities($this->acronyms[$acronym]) . '">'; $this->doc .= $this->_xmlEntities($acronym); $this->doc .= '</acronym>'; } function smiley($smiley) { $this->doc .= '<smiley>'; $this->doc .= $this->_xmlEntities($smiley); $this->doc .= '</smiley>'; } function entity($entity) { $this->doc .= '<entity data="' . $this->_xmlEntities($this->entities[$entity]) . '">'; $this->doc .= $this->_xmlEntities($entity); $this->doc .= '</entity>'; } /** * Multiply entities are of the form: 640x480 where $x=640 and $y=480 * * @param string $x The left hand operand * @param string $y The rigth hand operand */ function multiplyentity($x, $y) { $this->doc .= '<multiplyentity>'; $this->doc .= '<x>'.$this->_xmlEntities($x).'</x>'; $this->doc .= '<y>'.$this->_xmlEntities($y).'</y>'; $this->doc .= '</multiplyentity>'; } function singlequoteopening() { global $lang; $this->doc .= '<singlequote open="' . $this->_xmlEntities($lang['singlequoteopening']) . '" close="' . $this->_xmlEntities($lang['singlequoteclosing']) . '">'; $this->_openTag($this, 'singlequoteclosing', array()); } function singlequoteclosing() { $this->_closeTags($this, __FUNCTION__); $this->doc .= '</singlequote>'; } function apostrophe() { global $lang; $this->doc .= '<apostrophe data="' . $this->_xmlEntities($lang['apostrophe']) . '"/>'; } function doublequoteopening() { global $lang; $this->doc .= '<doublequote open="' . $this->_xmlEntities($lang['doublequoteopening']) . '" close="' . $this->_xmlEntities($lang['doublequoteclosing']) . '">'; $this->_openTag($this, 'doublequoteclosing', array()); } function doublequoteclosing() { $this->_closeTags($this, __FUNCTION__); $this->doc .= '</doublequote>'; } /** * Links in CamelCase format. * * @param string $link Link text */ function camelcaselink($link) { $this->internallink($link, $link, 'camelcase'); } function locallink($hash, $name = null) { $this->doc .= '<link type="locallink" link="'.$this->_xmlEntities($hash).'" href="'.$this->_xmlEntities($hash).'">'; $this->doc .= $this->_getLinkTitle($name, $hash, $isImage); $this->doc .= '</link>'; } /** * Links of the form 'wiki:syntax', where $title is either a string or (for * media links) an array. * * @param string $link The link text * @param mixed $title Title text (array for media links) * @param string $type overwrite the type (for camelcaselink) */ function internallink($link, $title = null, $type='internal') { global $ID; $id = $link; $name = $title; list($id, $hash) = explode('#', $id, 2); list($id, $search) = explode('?', $id, 2); if ($id === '') $id = $ID; $default = $this->_simpleTitle($id); resolve_pageid(getNS($ID), $id, $exists); $name = $this->_getLinkTitle($name, $default, $isImage, $id, 'content'); $this->doc .= '<link type="'.$type.'" link="'.$this->_xmlEntities($link).'" id="'.$id.'" search="'.$this->_xmlEntities($search).'" hash="'.$this->_xmlEntities($hash).'">'; $this->doc .= $name; $this->doc .= '</link>'; } /** * Full URL links with scheme. $title could be an array, for media links. * * @param string $link The link text * @param mixed $title Title text (array for media links) */ function externallink($link, $title = null) { $this->doc .= '<link type="external" link="'.$this->_xmlEntities($link).'" href="'.$this->_xmlEntities($link).'">'; $this->doc .= $this->_getLinkTitle($title, $link, $isImage); $this->doc .= '</link>'; } /** * @param string $link the original link - probably not much use * @param string $title * @param string $wikiName an indentifier for the wiki * @param string $wikiUri the URL fragment to append to some known URL */ function interwikilink($link, $title = null, $wikiName, $wikiUri) { $name = $this->_getLinkTitle($title, $wikiUri, $isImage); $url = $this->_resolveInterWiki($wikiName, $wikiUri); $this->doc .= '<link type="interwiki" link="'.$this->_xmlEntities($link).'" href="'.$this->_xmlEntities($url).'">'; $this->doc .= $name; $this->doc .= '</link>'; } /** * Link to a Windows share, $title could be an array (media) * * @param string $link * @param mixed $title */ function windowssharelink($link, $title = null) { $name = $this->_getLinkTitle($title, $link, $isImage); $url = str_replace('\\','/',$link); $url = 'file:///'.$url; $this->doc .= '<link type="windowssharelink" link="'.$this->_xmlEntities($link).'" href="'.$this->_xmlEntities($url).'">'; $this->doc .= $name; $this->doc .= '</link>'; } function emaillink($address, $name = null) { $name = $this->_getLinkTitle($name, '', $isImage); $url = $this->_xmlEntities($address); $url = obfuscate($url); $url = 'mailto:'.$url; $this->doc .= '<link type="emaillink" link="'.$this->_xmlEntities($address).'" href="'.$url.'">'; $this->doc .= $name; $this->doc .= '</link>'; } /** * Render media that is internal to the wiki. * * @param string $src * @param string $title * @param string $align * @param string $width * @param string $height * @param string $cache * @param string $linking */ function internalmedia ($src, $title=null, $align=null, $width=null, $height=null, $cache=null, $linking=null) { $this->doc .= $this->_media('internalmedia', $src, $title, $align, $width, $height, $cache, $linking); } /** * Render media that is external to the wiki. * * @param string $src * @param string $title * @param string $align * @param string $width * @param string $height * @param string $cache * @param string $linking */ function externalmedia ($src, $title=null, $align=null, $width=null, $height=null, $cache=null, $linking=null) { $this->doc .= $this->_media('externalmedia', $src, $title, $align, $width, $height, $cache, $linking); } function table_open($maxcols = null, $numrows = null){ $this->doc .= '<table maxcols="' . $maxcols . '" numrows="' . $numrows . '">'.DOKU_LF; $this->_openTag($this, 'table_close', array()); } function table_close(){ $this->_closeTags($this, __FUNCTION__); $this->doc .= '</table>'.DOKU_LF; } function tablerow_open(){ $this->doc .= DOKU_TAB.'<tablerow>'; $this->_openTag($this, 'tablerow_close', array()); } function tablerow_close(){ $this->_closeTags($this, __FUNCTION__); $this->doc .= '</tablerow>'.DOKU_LF; } function tableheader_open($colspan = 1, $align = null, $rowspan = 1){ $this->doc .= '<tableheader'; if ($colspan>1) $this->doc .= ' colspan="' . $colspan . '"'; if ($rowspan>1) $this->doc .= ' rowspan="' . $rowspan . '"'; if ($align) $this->doc .= ' align="' . $align . '"'; $this->doc .= '>'; $this->_openTag($this, 'tableheader_close', array()); } function tableheader_close(){ $this->_closeTags($this, __FUNCTION__); $this->doc .= '</tableheader>'; } function tablecell_open($colspan = 1, $align = null, $rowspan = 1) { $this->doc .= '<tablecell'; if ($colspan>1) $this->doc .= ' colspan="' . $colspan . '"'; if ($rowspan>1) $this->doc .= ' rowspan="' . $rowspan . '"'; if ($align) $this->doc .= ' align="' . $align . '"'; $this->doc .= '>'; $this->_openTag($this, 'tablecell_close', array()); } function tablecell_close(){ $this->_closeTags($this, __FUNCTION__); $this->doc .= '</tablecell>'; } /** * Private functions for internal handling */ function _xmlEntities($text){ return htmlspecialchars($text,ENT_COMPAT,'UTF-8'); } /** * Render media elements. * @see Doku_Renderer_xhtml::internalmedia() * * @param string $type Either 'internalmedia' or 'externalmedia' * @param string $src * @param string $title * @param string $align * @param string $width * @param string $height * @param string $cache * @param string $linking */ function _media($type, $src, $title=null, $align=null, $width=null, $height=null, $cache=null, $linking = null) { global $ID; $link = $src; list($src, $hash) = explode('#', $src, 2); if ($type == 'internalmedia') { resolve_mediaid(getNS($ID), $src, $exists); } $name = $title ? $this->_xmlEntities($title) : $this->_xmlEntities(utf8_basename(noNS($src))); if ($type == 'internalmedia') { $src = ' id="'.$this->_xmlEntities($src).'" hash="'.$this->_xmlEntities($hash).'"'; } else { $src = ' src="'.$this->_xmlEntities($src).'"'; } $out .= '<media type="'.$type.'" link="'.$this->_xmlEntities($link).'"'.($src).' align="'.$align.'" width="'.$width.'" height="'.$height.'" cache="'.$cache.'" linking="'.$linking.'">'; $out .= $name; $out .= '</media>'; return $out; } function _getLinkTitle($title, $default, & $isImage, $id=null, $linktype='content'){ $isImage = false; if ( is_array($title) ) { $isImage = true; return $this->_imageTitle($title); } elseif ( is_null($title) || trim($title)=='') { if (useHeading($linktype) && $id) { $heading = p_get_first_heading($id); if ($heading) { return $this->_xmlEntities($heading); } } return $this->_xmlEntities($default); } else { return $this->_xmlEntities($title); } } function _imageTitle($img) { global $ID; // some fixes on $img['src'] // see internalmedia() and externalmedia() list($img['src'], $hash) = explode('#', $img['src'], 2); if ($img['type'] == 'internalmedia') { resolve_mediaid(getNS($ID), $img['src'], $exists); } return $this->_media($img['type'], $img['src'], $img['title'], $img['align'], $img['width'], $img['height'], $img['cache']); } function _openTag($class, $func, $data=null) { $this->tagStack[] = array($class, $func, $data); } function _closeTags($class=null, $func=null) { if ($this->tagClosing==true) return; // skip nested calls $this->tagClosing = true; while(count($this->tagStack)>0) { list($lastclass, $lastfunc, $lastdata) = array_pop($this->tagStack); if (!($lastclass===$class && $lastfunc==$func)) call_user_func_array( array($lastclass, $lastfunc), $lastdata ); else break; } $this->tagClosing = false; } }