1<?php 2/** 3 * DokuWiki Plugin xml 4 * 5 * @author Patrick Bueker <Patrick@patrickbueker.de> 6 * @author Danny Lin <danny0838@gmail.com> 7 * @license GPLv2 or later (http://www.gnu.org/licenses/gpl.html) 8 * 9 */ 10 11// must be run within Dokuwiki 12if(!defined('DOKU_INC')) die(); 13 14if (!defined('DOKU_LF')) define('DOKU_LF', "\n"); 15if (!defined('DOKU_TAB')) define('DOKU_TAB', "\t"); 16require_once DOKU_INC . 'inc/parser/renderer.php'; 17 18class renderer_plugin_xml extends Doku_Renderer { 19 20 function __construct() { 21 $this->reset(); 22 } 23 24 /** 25 * Allows renderer to be used again. Clean out any per-use values. 26 */ 27 function reset() { 28 $this->info = array( 29 'cache' => true, // may the rendered result cached? 30 'toc' => false, // render the TOC? 31 ); 32 $this->precedinglevel = array(); 33 $this->nextHeader = ""; 34 $this->helper = &plugin_load('helper','xml'); 35 $this->doc = ''; 36 $this->tagStack = array(); 37 } 38 39 /** 40 * Returns the format produced by this renderer. 41 * 42 * @return string 43 */ 44 function getFormat(){return 'xml';} 45 46 /** 47 * handle plugin rendering 48 */ 49 function plugin($name,$data){ 50 $plugin =& plugin_load('syntax',$name); 51 if ($plugin == null) return; 52 if ($this->helper->_xml_extension($this,$name,$data)) return; 53 $plugin->render($this->getFormat(),$this,$data); 54 } 55 56 /** 57 * Handles instructions 58 * 59 * @Stack: add post-start linefeed and post-end linefeed 60 * @Stack content: add post-start tab and post-end linefeed 61 * @Block: add post-end linefeed 62 * 63 * ~~SOMETHING>params~~ treated as <macro type="something" params /> 64 */ 65 function document_start() { 66 global $ID; 67 global $INFO; 68 $this->doc = '<?xml version="1.0" encoding="UTF-8"?>'.DOKU_LF; 69 $this->doc .= '<document domain="' . DOKU_URL .'" id="' . cleanID($ID) . '" revision="' . $INFO['rev'] . '" lastmod="' . $INFO['lastmod'] . '">'.DOKU_LF; 70 // store the content type headers in metadata 71 $output_filename = str_replace(':','-',$ID).".xml"; 72 $headers = array( 73 'Content-Type' => 'text/xml; charset=utf-8;', 74 'Content-Disposition' => 'attachment; filename="'.$output_filename.'";', 75 ); 76 p_set_metadata($ID,array('format' => array('xml' => $headers) )); 77 } 78 79 function document_end() { 80 while(count($this->precedinglevel)>0) { 81 $this->doc .= '</section>'.'<!--' . array_pop($this->precedinglevel) . '-->'.DOKU_LF; 82 } 83 $this->doc .= '</document>'.DOKU_LF; 84 } 85 86 function header($text, $level, $pos) { 87 if (!$text) return; //skip empty headlines 88 $this->nextHeader = '<header level="' . $level . '" pos="' . $pos . '">'. 89 $this->nextHeader .= $this->_xmlEntities($text); 90 $this->nextHeader .= '</header>'.DOKU_LF; 91 } 92 93 function section_open($level) { 94 while(end($this->precedinglevel) >= $level) 95 { 96 $this->doc .= '</section>'.'<!--' . array_pop($this->precedinglevel) . '-->'.DOKU_LF; 97 } 98 99 $this->doc .= '<section level="' . $level . '">'.DOKU_LF; 100 $this->doc .= $this->nextHeader; 101 $this->nextHeader = ""; 102 array_push($this->precedinglevel,$level); 103 } 104 105 function section_close() { 106 #$this->doc .= '</section>'.DOKU_LF; 107 } 108 109 function nocache() { 110 $this->info['cache'] = false; 111 $this->doc .= '<macro name="nocache" />'.DOKU_LF; 112 } 113 114 function notoc() { 115 $this->info['toc'] = false; 116 $this->doc .= '<macro name="notoc" />'.DOKU_LF; 117 } 118 119 function cdata($text) { 120 $this->doc .= $this->_xmlEntities($text); 121 } 122 123 function p_open() { 124 $this->doc .= '<p>'; 125 $this->_openTag($this, 'p_close', array()); 126 } 127 128 function p_close() { 129 $this->_closeTags($this, __FUNCTION__); 130 $this->doc .= '</p>'.DOKU_LF; 131 } 132 133 function linebreak() { 134 $this->doc .= '<linebreak/>'; 135 } 136 137 function hr() { 138 $this->doc .= '<hr/>'.DOKU_LF; 139 } 140 141 function strong_open() { 142 $this->doc .= '<strong>'; 143 $this->_openTag($this, 'strong_close', array()); 144 } 145 146 function strong_close() { 147 $this->_closeTags($this, __FUNCTION__); 148 $this->doc .= '</strong>'; 149 } 150 151 function emphasis_open() { 152 $this->doc .= '<emphasis>'; 153 $this->_openTag($this, 'emphasis_close', array()); 154 } 155 156 function emphasis_close() { 157 $this->_closeTags($this, __FUNCTION__); 158 $this->doc .= '</emphasis>'; 159 } 160 161 function underline_open() { 162 $this->doc .= '<underline>'; 163 $this->_openTag($this, 'underline_close', array()); 164 } 165 166 function underline_close() { 167 $this->_closeTags($this, __FUNCTION__); 168 $this->doc .= '</underline>'; 169 } 170 171 function monospace_open() { 172 $this->doc .= '<monospace>'; 173 $this->_openTag($this, 'monospace_close', array()); 174 } 175 176 function monospace_close() { 177 $this->_closeTags($this, __FUNCTION__); 178 $this->doc .= '</monospace>'; 179 } 180 181 function subscript_open() { 182 $this->doc .= '<subscript>'; 183 $this->_openTag($this, 'subscript_close', array()); 184 } 185 186 function subscript_close() { 187 $this->_closeTags($this, __FUNCTION__); 188 $this->doc .= '</subscript>'; 189 } 190 191 function superscript_open() { 192 $this->doc .= '<superscript>'; 193 $this->_openTag($this, 'superscript_close', array()); 194 } 195 196 function superscript_close() { 197 $this->_closeTags($this, __FUNCTION__); 198 $this->doc .= '</superscript>'; 199 } 200 201 function deleted_open() { 202 $this->doc .= '<delete>'; 203 $this->_openTag($this, 'deleted_close', array()); 204 } 205 206 function deleted_close() { 207 $this->_closeTags($this, __FUNCTION__); 208 $this->doc .= '</delete>'; 209 } 210 211 function footnote_open() { 212 $this->doc .= '<footnote>'; 213 $this->_openTag($this, 'footnote_close', array()); 214 } 215 216 function footnote_close() { 217 $this->_closeTags($this, __FUNCTION__); 218 $this->doc .= '</footnote>'; 219 } 220 221 function listu_open() { 222 $this->doc .= '<listu>'.DOKU_LF; 223 $this->_openTag($this, 'listu_close', array()); 224 } 225 226 function listu_close() { 227 $this->_closeTags($this, __FUNCTION__); 228 $this->doc .= '</listu>'.DOKU_LF; 229 } 230 231 function listo_open() { 232 $this->doc .= '<listo>'.DOKU_LF; 233 $this->_openTag($this, 'listo_close', array()); 234 } 235 236 function listo_close() { 237 $this->_closeTags($this, __FUNCTION__); 238 $this->doc .= '</listo>'.DOKU_LF; 239 } 240 241 function listitem_open($level) { 242 $this->doc .= DOKU_TAB.'<listitem level="' . $level . '">'; 243 $this->_openTag($this, 'listitem_close', array()); 244 } 245 246 function listitem_close() { 247 $this->_closeTags($this, __FUNCTION__); 248 $this->doc .= '</listitem>'.DOKU_LF; 249 } 250 251 function listcontent_open() { 252 $this->doc .= '<listcontent>'; 253 $this->_openTag($this, 'listcontent_close', array()); 254 } 255 256 function listcontent_close() { 257 $this->_closeTags($this, __FUNCTION__); 258 $this->doc .= '</listcontent>'; 259 } 260 261 function unformatted($text) { 262 $this->doc .= '<unformatted>'; 263 $this->doc .= $this->_xmlEntities($text); 264 $this->doc .= '</unformatted>'; 265 } 266 267 function php($text) { 268 $this->doc .= '<php>'; 269 $this->doc .= $this->_xmlEntities($text); 270 $this->doc .= '</php>'; 271 } 272 273 function phpblock($text) { 274 $this->doc .= '<phpblock>'; 275 $this->doc .= $this->_xmlEntities($text); 276 $this->doc .= '</phpblock>'.DOKU_LF; 277 } 278 279 function html($text) { 280 $this->doc .= '<html>'; 281 $this->doc .= $this->_xmlEntities($text); 282 $this->doc .= '</html>'; 283 } 284 285 function htmlblock($text) { 286 $this->doc .= '<htmlblock>'; 287 $this->doc .= $this->_xmlEntities($text); 288 $this->doc .= '</htmlblock>'.DOKU_LF; 289 } 290 291 function preformatted($text) { 292 $this->doc .= '<preformatted>'; 293 $this->doc .= $this->_xmlEntities($text); 294 $this->doc .= '</preformatted>'.DOKU_LF; 295 } 296 297 function quote_open() { 298 $this->doc .= '<quote>'; 299 $this->_openTag($this, 'quote_close', array()); 300 } 301 302 function quote_close() { 303 $this->_closeTags($this, __FUNCTION__); 304 $this->doc .= '</quote>'.DOKU_LF; 305 } 306 307 function code($text, $lang = null, $file = null) { 308 $this->doc .= '<code lang="' . $lang . '" file="' . $file . '">'; 309 $this->doc .= $this->_xmlEntities($text); 310 $this->doc .= '</code>'.DOKU_LF; 311 } 312 313 function file($text, $lang = null, $file = null) { 314 $this->doc .= '<file lang="' . $lang . '" file="' . $file . '">'; 315 $this->doc .= $this->_xmlEntities($text); 316 $this->doc .= '</file>'.DOKU_LF; 317 } 318 319 function acronym($acronym) { 320 $this->doc .= '<acronym data="' . $this->_xmlEntities($this->acronyms[$acronym]) . '">'; 321 $this->doc .= $this->_xmlEntities($acronym); 322 $this->doc .= '</acronym>'; 323 } 324 325 function smiley($smiley) { 326 $this->doc .= '<smiley>'; 327 $this->doc .= $this->_xmlEntities($smiley); 328 $this->doc .= '</smiley>'; 329 } 330 331 function entity($entity) { 332 $this->doc .= '<entity data="' . $this->_xmlEntities($this->entities[$entity]) . '">'; 333 $this->doc .= $this->_xmlEntities($entity); 334 $this->doc .= '</entity>'; 335 } 336 337 /** 338 * Multiply entities are of the form: 640x480 where $x=640 and $y=480 339 * 340 * @param string $x The left hand operand 341 * @param string $y The rigth hand operand 342 */ 343 function multiplyentity($x, $y) { 344 $this->doc .= '<multiplyentity>'; 345 $this->doc .= '<x>'.$this->_xmlEntities($x).'</x>'; 346 $this->doc .= '<y>'.$this->_xmlEntities($y).'</y>'; 347 $this->doc .= '</multiplyentity>'; 348 } 349 350 function singlequoteopening() { 351 global $lang; 352 $this->doc .= '<singlequote open="' . $this->_xmlEntities($lang['singlequoteopening']) . '" close="' . $this->_xmlEntities($lang['singlequoteclosing']) . '">'; 353 $this->_openTag($this, 'singlequoteclosing', array()); 354 } 355 356 function singlequoteclosing() { 357 $this->_closeTags($this, __FUNCTION__); 358 $this->doc .= '</singlequote>'; 359 } 360 361 function apostrophe() { 362 global $lang; 363 $this->doc .= '<apostrophe data="' . $this->_xmlEntities($lang['apostrophe']) . '"/>'; 364 } 365 366 function doublequoteopening() { 367 global $lang; 368 $this->doc .= '<doublequote open="' . $this->_xmlEntities($lang['doublequoteopening']) . '" close="' . $this->_xmlEntities($lang['doublequoteclosing']) . '">'; 369 $this->_openTag($this, 'doublequoteclosing', array()); 370 } 371 372 function doublequoteclosing() { 373 $this->_closeTags($this, __FUNCTION__); 374 $this->doc .= '</doublequote>'; 375 } 376 377 /** 378 * Links in CamelCase format. 379 * 380 * @param string $link Link text 381 */ 382 function camelcaselink($link) { 383 $this->internallink($link, $link, 'camelcase'); 384 } 385 386 function locallink($hash, $name = null) { 387 $this->doc .= '<link type="locallink" link="'.$this->_xmlEntities($hash).'" href="'.$this->_xmlEntities($hash).'">'; 388 $this->doc .= $this->_getLinkTitle($name, $hash, $isImage); 389 $this->doc .= '</link>'; 390 } 391 392 /** 393 * Links of the form 'wiki:syntax', where $title is either a string or (for 394 * media links) an array. 395 * 396 * @param string $link The link text 397 * @param mixed $title Title text (array for media links) 398 * @param string $type overwrite the type (for camelcaselink) 399 */ 400 function internallink($link, $title = null, $type='internal') { 401 global $ID; 402 $id = $link; 403 $name = $title; 404 list($id, $hash) = explode('#', $id, 2); 405 list($id, $search) = explode('?', $id, 2); 406 if ($id === '') $id = $ID; 407 $default = $this->_simpleTitle($id); 408 resolve_pageid(getNS($ID), $id, $exists); 409 $name = $this->_getLinkTitle($name, $default, $isImage, $id, 'content'); 410 $this->doc .= '<link type="'.$type.'" link="'.$this->_xmlEntities($link).'" id="'.$id.'" search="'.$this->_xmlEntities($search).'" hash="'.$this->_xmlEntities($hash).'">'; 411 $this->doc .= $name; 412 $this->doc .= '</link>'; 413 } 414 415 /** 416 * Full URL links with scheme. $title could be an array, for media links. 417 * 418 * @param string $link The link text 419 * @param mixed $title Title text (array for media links) 420 */ 421 function externallink($link, $title = null) { 422 $this->doc .= '<link type="external" link="'.$this->_xmlEntities($link).'" href="'.$this->_xmlEntities($link).'">'; 423 $this->doc .= $this->_getLinkTitle($title, $link, $isImage); 424 $this->doc .= '</link>'; 425 } 426 427 /** 428 * @param string $link the original link - probably not much use 429 * @param string $title 430 * @param string $wikiName an indentifier for the wiki 431 * @param string $wikiUri the URL fragment to append to some known URL 432 */ 433 function interwikilink($link, $title = null, $wikiName, $wikiUri) { 434 $name = $this->_getLinkTitle($title, $wikiUri, $isImage); 435 $url = $this->_resolveInterWiki($wikiName, $wikiUri); 436 $this->doc .= '<link type="interwiki" link="'.$this->_xmlEntities($link).'" href="'.$this->_xmlEntities($url).'">'; 437 $this->doc .= $name; 438 $this->doc .= '</link>'; 439 } 440 441 /** 442 * Link to a Windows share, $title could be an array (media) 443 * 444 * @param string $link 445 * @param mixed $title 446 */ 447 function windowssharelink($link, $title = null) { 448 $name = $this->_getLinkTitle($title, $link, $isImage); 449 $url = str_replace('\\','/',$link); 450 $url = 'file:///'.$url; 451 $this->doc .= '<link type="windowssharelink" link="'.$this->_xmlEntities($link).'" href="'.$this->_xmlEntities($url).'">'; 452 $this->doc .= $name; 453 $this->doc .= '</link>'; 454 } 455 456 function emaillink($address, $name = null) { 457 $name = $this->_getLinkTitle($name, '', $isImage); 458 $url = $this->_xmlEntities($address); 459 $url = obfuscate($url); 460 $url = 'mailto:'.$url; 461 $this->doc .= '<link type="emaillink" link="'.$this->_xmlEntities($address).'" href="'.$url.'">'; 462 $this->doc .= $name; 463 $this->doc .= '</link>'; 464 } 465 466 /** 467 * Render media that is internal to the wiki. 468 * 469 * @param string $src 470 * @param string $title 471 * @param string $align 472 * @param string $width 473 * @param string $height 474 * @param string $cache 475 * @param string $linking 476 */ 477 function internalmedia ($src, $title=null, $align=null, $width=null, $height=null, $cache=null, $linking=null) { 478 $this->doc .= $this->_media('internalmedia', $src, $title, $align, $width, $height, $cache, $linking); 479 } 480 481 /** 482 * Render media that is external to the wiki. 483 * 484 * @param string $src 485 * @param string $title 486 * @param string $align 487 * @param string $width 488 * @param string $height 489 * @param string $cache 490 * @param string $linking 491 */ 492 function externalmedia ($src, $title=null, $align=null, $width=null, $height=null, $cache=null, $linking=null) { 493 $this->doc .= $this->_media('externalmedia', $src, $title, $align, $width, $height, $cache, $linking); 494 } 495 496 function table_open($maxcols = null, $numrows = null){ 497 $this->doc .= '<table maxcols="' . $maxcols . '" numrows="' . $numrows . '">'.DOKU_LF; 498 $this->_openTag($this, 'table_close', array()); 499 } 500 501 function table_close(){ 502 $this->_closeTags($this, __FUNCTION__); 503 $this->doc .= '</table>'.DOKU_LF; 504 } 505 506 function tablerow_open(){ 507 $this->doc .= DOKU_TAB.'<tablerow>'; 508 $this->_openTag($this, 'tablerow_close', array()); 509 } 510 511 function tablerow_close(){ 512 $this->_closeTags($this, __FUNCTION__); 513 $this->doc .= '</tablerow>'.DOKU_LF; 514 } 515 516 function tableheader_open($colspan = 1, $align = null, $rowspan = 1){ 517 $this->doc .= '<tableheader'; 518 if ($colspan>1) $this->doc .= ' colspan="' . $colspan . '"'; 519 if ($rowspan>1) $this->doc .= ' rowspan="' . $rowspan . '"'; 520 if ($align) $this->doc .= ' align="' . $align . '"'; 521 $this->doc .= '>'; 522 $this->_openTag($this, 'tableheader_close', array()); 523 } 524 525 function tableheader_close(){ 526 $this->_closeTags($this, __FUNCTION__); 527 $this->doc .= '</tableheader>'; 528 } 529 530 function tablecell_open($colspan = 1, $align = null, $rowspan = 1) { 531 $this->doc .= '<tablecell'; 532 if ($colspan>1) $this->doc .= ' colspan="' . $colspan . '"'; 533 if ($rowspan>1) $this->doc .= ' rowspan="' . $rowspan . '"'; 534 if ($align) $this->doc .= ' align="' . $align . '"'; 535 $this->doc .= '>'; 536 $this->_openTag($this, 'tablecell_close', array()); 537 } 538 539 function tablecell_close(){ 540 $this->_closeTags($this, __FUNCTION__); 541 $this->doc .= '</tablecell>'; 542 } 543 544 /** 545 * Private functions for internal handling 546 */ 547 function _xmlEntities($text){ 548 return htmlspecialchars($text,ENT_COMPAT,'UTF-8'); 549 } 550 551 /** 552 * Render media elements. 553 * @see Doku_Renderer_xhtml::internalmedia() 554 * 555 * @param string $type Either 'internalmedia' or 'externalmedia' 556 * @param string $src 557 * @param string $title 558 * @param string $align 559 * @param string $width 560 * @param string $height 561 * @param string $cache 562 * @param string $linking 563 */ 564 function _media($type, $src, $title=null, $align=null, $width=null, $height=null, $cache=null, $linking = null) { 565 global $ID; 566 $link = $src; 567 list($src, $hash) = explode('#', $src, 2); 568 if ($type == 'internalmedia') { 569 resolve_mediaid(getNS($ID), $src, $exists); 570 } 571 $name = $title ? $this->_xmlEntities($title) : $this->_xmlEntities(utf8_basename(noNS($src))); 572 if ($type == 'internalmedia') { 573 $src = ' id="'.$this->_xmlEntities($src).'" hash="'.$this->_xmlEntities($hash).'"'; 574 } 575 else { 576 $src = ' src="'.$this->_xmlEntities($src).'"'; 577 } 578 $out .= '<media type="'.$type.'" link="'.$this->_xmlEntities($link).'"'.($src).' align="'.$align.'" width="'.$width.'" height="'.$height.'" cache="'.$cache.'" linking="'.$linking.'">'; 579 $out .= $name; 580 $out .= '</media>'; 581 return $out; 582 } 583 584 function _getLinkTitle($title, $default, & $isImage, $id=null, $linktype='content'){ 585 $isImage = false; 586 if ( is_array($title) ) { 587 $isImage = true; 588 return $this->_imageTitle($title); 589 } elseif ( is_null($title) || trim($title)=='') { 590 if (useHeading($linktype) && $id) { 591 $heading = p_get_first_heading($id); 592 if ($heading) { 593 return $this->_xmlEntities($heading); 594 } 595 } 596 return $this->_xmlEntities($default); 597 } else { 598 return $this->_xmlEntities($title); 599 } 600 } 601 602 function _imageTitle($img) { 603 global $ID; 604 605 // some fixes on $img['src'] 606 // see internalmedia() and externalmedia() 607 list($img['src'], $hash) = explode('#', $img['src'], 2); 608 if ($img['type'] == 'internalmedia') { 609 resolve_mediaid(getNS($ID), $img['src'], $exists); 610 } 611 612 return $this->_media($img['type'], 613 $img['src'], 614 $img['title'], 615 $img['align'], 616 $img['width'], 617 $img['height'], 618 $img['cache']); 619 } 620 621 function _openTag($class, $func, $data=null) { 622 $this->tagStack[] = array($class, $func, $data); 623 } 624 625 function _closeTags($class=null, $func=null) { 626 if ($this->tagClosing==true) return; // skip nested calls 627 $this->tagClosing = true; 628 while(count($this->tagStack)>0) { 629 list($lastclass, $lastfunc, $lastdata) = array_pop($this->tagStack); 630 if (!($lastclass===$class && $lastfunc==$func)) call_user_func_array( array($lastclass, $lastfunc), $lastdata ); 631 else break; 632 } 633 $this->tagClosing = false; 634 } 635} 636