diff options
Diffstat (limited to 'mod/dokuwiki/vendors/dokuwiki/lib/plugins/odt/renderer.php')
-rw-r--r-- | mod/dokuwiki/vendors/dokuwiki/lib/plugins/odt/renderer.php | 1232 |
1 files changed, 1232 insertions, 0 deletions
diff --git a/mod/dokuwiki/vendors/dokuwiki/lib/plugins/odt/renderer.php b/mod/dokuwiki/vendors/dokuwiki/lib/plugins/odt/renderer.php new file mode 100644 index 000000000..5525e4926 --- /dev/null +++ b/mod/dokuwiki/vendors/dokuwiki/lib/plugins/odt/renderer.php @@ -0,0 +1,1232 @@ +<?php +/** + * ODT Plugin: Exports to ODT + * + * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) + * @author Andreas Gohr <andi@splitbrain.org> + * @author Aurelien Bompard <aurelien@bompard.org> + */ +// must be run within Dokuwiki +if(!defined('DOKU_INC')) die(); + +require_once DOKU_INC.'inc/parser/renderer.php'; + +// ZipLib.class.php +$dw_version = preg_replace('/[^\d]/', '', getversion()); +if (version_compare($dw_version, "20070626") and + version_compare(PHP_VERSION,'5.0.0','>')) { + // If strictly newer than 2007-06-26 and use PHP5, fixes to ZipLib are + // included in Dokuwiki's ZipLib + require_once DOKU_INC.'inc/ZipLib.class.php'; +} else { // for DW up to 2007-06-26, we need the patched version + require_once 'ZipLib.class.php'; +} + +/** + * The Renderer + */ +class renderer_plugin_odt extends Doku_Renderer { + var $ZIP = null; + var $meta; + var $store = ''; + var $footnotes = array(); + var $manifest = array(); + var $headers = array(); + var $template = ""; + var $fields = array(); + var $in_list_item = false; + var $in_paragraph = false; + // Automatic styles. Will always be added to content.xml and styles.xml + var $autostyles = array( + "pm1"=>' + <style:page-layout style:name="pm1"> + <style:page-layout-properties fo:page-width="21cm" fo:page-height="29.7cm" style:num-format="1" style:print-orientation="portrait" fo:margin-top="2cm" fo:margin-bottom="2cm" fo:margin-left="2cm" fo:margin-right="2cm" style:writing-mode="lr-tb" style:footnote-max-height="0cm"> + <style:footnote-sep style:width="0.018cm" style:distance-before-sep="0.1cm" style:distance-after-sep="0.1cm" style:adjustment="left" style:rel-width="25%" style:color="#000000"/> + </style:page-layout-properties> + <style:header-style/> + <style:footer-style/> + </style:page-layout>', + "sub"=>' + <style:style style:name="sub" style:family="text"> + <style:text-properties style:text-position="-33% 80%"/> + </style:style>', + "sup"=>' + <style:style style:name="sup" style:family="text"> + <style:text-properties style:text-position="33% 80%"/> + </style:style>', + "del"=>' + <style:style style:name="del" style:family="text"> + <style:text-properties style:text-line-through-style="solid"/> + </style:style>', + "underline"=>' + <style:style style:name="underline" style:family="text"> + <style:text-properties style:text-underline-style="solid" + style:text-underline-width="auto" style:text-underline-color="font-color"/> + </style:style>', + "media"=>' + <style:style style:name="media" style:family="graphic" style:parent-style-name="Graphics"> + <style:graphic-properties style:run-through="foreground" style:wrap="parallel" style:number-wrapped-paragraphs="no-limit" + style:wrap-contour="false" style:vertical-pos="top" style:vertical-rel="baseline" style:horizontal-pos="left" + style:horizontal-rel="paragraph"/> + </style:style>', + "medialeft"=>' + <style:style style:name="medialeft" style:family="graphic" style:parent-style-name="Graphics"> + <style:graphic-properties style:run-through="foreground" style:wrap="parallel" style:number-wrapped-paragraphs="no-limit" + style:wrap-contour="false" style:horizontal-pos="left" style:horizontal-rel="paragraph"/> + </style:style>', + "mediaright"=>' + <style:style style:name="mediaright" style:family="graphic" style:parent-style-name="Graphics"> + <style:graphic-properties style:run-through="foreground" style:wrap="parallel" style:number-wrapped-paragraphs="no-limit" + style:wrap-contour="false" style:horizontal-pos="right" style:horizontal-rel="paragraph"/> + </style:style>', + "mediacenter"=>' + <style:style style:name="mediacenter" style:family="graphic" style:parent-style-name="Graphics"> + <style:graphic-properties style:run-through="foreground" style:wrap="none" style:horizontal-pos="center" + style:horizontal-rel="paragraph"/> + </style:style>', + "tablealigncenter"=>' + <style:style style:name="tablealigncenter" style:family="paragraph" style:parent-style-name="Table_20_Contents"> + <style:paragraph-properties fo:text-align="center"/> + </style:style>', + "tablealignright"=>' + <style:style style:name="tablealignright" style:family="paragraph" style:parent-style-name="Table_20_Contents"> + <style:paragraph-properties fo:text-align="end"/> + </style:style>', + "tablealignleft"=>' + <style:style style:name="tablealignleft" style:family="paragraph" style:parent-style-name="Table_20_Contents"> + <style:paragraph-properties fo:text-align="left"/> + </style:style>', + "tableheader"=>' + <style:style style:name="tableheader" style:family="table-cell"> + <style:table-cell-properties fo:padding="0.05cm" fo:border-left="0.002cm solid #000000" fo:border-right="0.002cm solid #000000" fo:border-top="0.002cm solid #000000" fo:border-bottom="0.002cm solid #000000"/> + </style:style>', + "tablecell"=>' + <style:style style:name="tablecell" style:family="table-cell"> + <style:table-cell-properties fo:padding="0.05cm" fo:border-left="0.002cm solid #000000" fo:border-right="0.002cm solid #000000" fo:border-top="0.002cm solid #000000" fo:border-bottom="0.002cm solid #000000"/> + </style:style>', + "legendcenter"=>' + <style:style style:name="legendcenter" style:family="paragraph" style:parent-style-name="Illustration"> + <style:paragraph-properties fo:text-align="center"/> + </style:style>', + ); + // Regular styles. May not be present if in template mode, in which case they will be added to styles.xml + var $styles = array( + "Source_20_Text"=>' + <style:style style:name="Source_20_Text" style:display-name="Source Text" style:family="text"> + <style:text-properties style:font-name="Bitstream Vera Sans Mono" style:font-name-asian="Bitstream Vera Sans Mono" style:font-name-complex="Bitstream Vera Sans Mono"/> + </style:style>', + "Preformatted_20_Text"=>' + <style:style style:name="Preformatted_20_Text" style:display-name="Preformatted Text" style:family="paragraph" style:parent-style-name="Standard" style:class="html"> + <style:paragraph-properties fo:margin-top="0cm" fo:margin-bottom="0cm"/> + <style:text-properties style:font-name="Bitstream Vera Sans Mono" style:font-name-asian="Bitstream Vera Sans Mono" style:font-name-complex="Bitstream Vera Sans Mono"/> + </style:style>', + "Horizontal_20_Line"=>' + <style:style style:name="Horizontal_20_Line" style:display-name="Horizontal Line" style:family="paragraph" style:parent-style-name="Standard" style:next-style-name="Text_20_body" style:class="html"> + <style:paragraph-properties fo:margin-top="0cm" fo:margin-bottom="0.5cm" style:border-line-width-bottom="0.002cm 0.035cm 0.002cm" fo:padding="0cm" fo:border-left="none" fo:border-right="none" fo:border-top="none" fo:border-bottom="0.04cm double #808080" text:number-lines="false" text:line-number="0" style:join-border="false"/> + <style:text-properties fo:font-size="6pt" style:font-size-asian="6pt" style:font-size-complex="6pt"/> + </style:style>', + "Footnote"=>' + <style:style style:name="Footnote" style:family="paragraph" style:parent-style-name="Standard" style:class="extra"> + <style:paragraph-properties fo:margin-left="0.5cm" fo:margin-right="0cm" fo:text-indent="-0.5cm" style:auto-text-indent="false" text:number-lines="false" text:line-number="0"/> + <style:text-properties fo:font-size="10pt" style:font-size-asian="10pt" style:font-size-complex="10pt"/> + </style:style>', + "Emphasis"=>' + <style:style style:name="Emphasis" style:family="text"> + <style:text-properties fo:font-style="italic" style:font-style-asian="italic" style:font-style-complex="italic"/> + </style:style>', + "Strong_20_Emphasis"=>' + <style:style style:name="Strong_20_Emphasis" style:display-name="Strong Emphasis" style:family="text"> + <style:text-properties fo:font-weight="bold" style:font-weight-asian="bold" style:font-weight-complex="bold"/> + </style:style>', + ); + // Font definitions. May not be present if in template mode, in which case they will be added to styles.xml + var $fonts = array( + "StarSymbol"=>'<style:font-face style:name="StarSymbol" svg:font-family="StarSymbol"/>', // for bullets + "Bitstream Vera Sans Mono"=>'<style:font-face style:name="Bitstream Vera Sans Mono" svg:font-family="\'Bitstream Vera Sans Mono\'" style:font-family-generic="modern" style:font-pitch="fixed"/>', // for source code + ); + + /** + * Return version info + */ + function getInfo(){ + return confToHash(dirname(__FILE__).'/info.txt'); + } + + /** + * Returns the format produced by this renderer. + */ + function getFormat(){ + return "odt"; + } + + /** + * Do not make multiple instances of this class + */ + function isSingleton(){ + return true; + } + + + /** + * Initialize the rendering + */ + function document_start() { + global $ID; + + // If older or equal to 2007-06-26, we need to disable caching + $dw_version = preg_replace('/[^\d]/', '', getversion()); + if (version_compare($dw_version, "20070626", "<=")) { + $this->info["cache"] = false; + } + + // prepare the zipper + $this->ZIP = new ZipLib(); + + // prepare meta data + $this->meta = array( + 'meta:generator' => 'DokuWiki '.getversion(), + 'meta:initial-creator' => 'Generated', + 'meta:creation-date' => date('Y-m-d\\TH::i:s', null), //FIXME + 'dc:creator' => 'Generated', + 'dc:date' => date('Y-m-d\\TH::i:s', null), + 'dc:language' => 'en-US', + 'meta:editing-cycles' => '1', + 'meta:editing-duration' => 'PT0S', + ); + + // send the content type header, new method after 2007-06-26 (handles caching) + $output_filename = str_replace(':','-',$ID).".odt"; + if (version_compare($dw_version, "20070626")) { + // store the content type headers in metadata + $headers = array( + 'Content-Type' => 'application/vnd.oasis.opendocument.text', + 'Content-Disposition' => 'attachment; filename="'.$output_filename.'";', + ); + p_set_metadata($ID,array('format' => array('odt' => $headers) )); + } else { // older method + header('Content-Type: application/vnd.oasis.opendocument.text'); + header('Content-Disposition: attachment; filename="'.$output_filename.'";'); + } + } + + /** + * Prepare meta.xml + */ + function _odtMeta(){ + $value = '<' . '?xml version="1.0" encoding="UTF-8"?' . ">\n"; + $value .= '<office:document-meta '; + $value .= 'xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" '; + $value .= 'xmlns:xlink="http://www.w3.org/1999/xlink" '; + $value .= 'xmlns:dc="http://purl.org/dc/elements/1.1/" '; + $value .= 'xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" '; + $value .= 'office:version="1.0">'; + $value .= '<office:meta>'; + # FIXME + # foreach($meta as $meta_key => $meta_value) + # $value .= '<' . $meta_key . '>' . ODUtils::encode($meta_value) . '</' . $meta_key . '>'; + $value .= '</office:meta>'; + $value .= '</office:document-meta>'; + $this->ZIP->add_File($value,'meta.xml'); + } + + /** + * Prepare manifest.xml + */ + function _odtManifest(){ + $value = '<' . '?xml version="1.0" encoding="UTF-8"?' . ">\n"; + $value .= '<manifest:manifest xmlns:manifest="urn:oasis:names:tc:opendocument:xmlns:manifest:1.0">'; + $value .= '<manifest:file-entry manifest:media-type="application/vnd.oasis.opendocument.text" manifest:full-path="/"/>'; + $value .= '<manifest:file-entry manifest:media-type="text/xml" manifest:full-path="settings.xml"/>'; + $value .= '<manifest:file-entry manifest:media-type="text/xml" manifest:full-path="meta.xml"/>'; + $value .= '<manifest:file-entry manifest:media-type="text/xml" manifest:full-path="content.xml"/>'; + $value .= '<manifest:file-entry manifest:media-type="text/xml" manifest:full-path="styles.xml"/>'; + + foreach($this->manifest as $path => $type){ + $value .= '<manifest:file-entry manifest:media-type="'.$this->_xmlEntities($type). + '" manifest:full-path="'.$this->_xmlEntities($path).'"/>'; + } + + $value .= '</manifest:manifest>'; + $this->ZIP->add_File($value,'META-INF/manifest.xml'); + } + + /** + * Prepare settings.xml + */ + function _odtSettings(){ + $value = '<' . '?xml version="1.0" encoding="UTF-8"?' . ">\n"; + $value .= '<office:document-settings xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:config="urn:oasis:names:tc:opendocument:xmlns:config:1.0" office:version="1.0"><office:settings><config:config-item-set config:name="dummy-settings"><config:config-item config:name="MakeValidatorHappy" config:type="boolean">true</config:config-item></config:config-item-set></office:settings></office:document-settings>'; + $this->ZIP->add_File($value,'settings.xml'); + } + + + + + /** + * Closes the document + */ + function document_end(){ + global $conf; + if ($this->template) { // template chosen + if (file_exists($conf['mediadir'].'/'.$this->getConf("tpl_dir")."/".$this->template)) { //template found + $this->document_end_template(); + } else { // template chosen but not found : warn the user and use the default template + $this->doc = '<text:p text:style-name="Text_20_body"><text:span text:style-name="Strong_20_Emphasis">' + .$this->_xmlEntities( sprintf($this->getLang('tpl_not_found'),$this->template,$this->getConf("tpl_dir")) ) + .'</text:span></text:p>'.$this->doc; + $this->document_end_scratch(); + } + } else { + $this->document_end_scratch(); + } + $this->doc = $this->ZIP->get_file(); + } + + + /** + * Closes the document when not using a template + */ + function document_end_scratch(){ + $autostyles = $this->_odtAutoStyles(); + $userfields = $this->_odtUserFields(); + + // add defaults + $this->ZIP->add_File('application/vnd.oasis.opendocument.text', 'mimetype', 0); + + $this->_odtMeta(); + $this->_odtSettings(); + + $value = '<' . '?xml version="1.0" encoding="UTF-8"?' . ">\n"; + $value .= '<office:document-content '; + $value .= 'xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" '; + $value .= 'xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" '; + $value .= 'xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" '; + $value .= 'xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" '; + $value .= 'xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" '; + $value .= 'xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" '; + $value .= 'xmlns:xlink="http://www.w3.org/1999/xlink" '; + $value .= 'xmlns:dc="http://purl.org/dc/elements/1.1/" '; + $value .= 'xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" '; + $value .= 'xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0" '; + $value .= 'xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" '; + $value .= 'xmlns:chart="urn:oasis:names:tc:opendocument:xmlns:chart:1.0" '; + $value .= 'xmlns:dr3d="urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0" '; + $value .= 'xmlns:math="http://www.w3.org/1998/Math/MathML" '; + $value .= 'xmlns:form="urn:oasis:names:tc:opendocument:xmlns:form:1.0" '; + $value .= 'xmlns:script="urn:oasis:names:tc:opendocument:xmlns:script:1.0" '; + $value .= 'xmlns:dom="http://www.w3.org/2001/xml-events" '; + $value .= 'xmlns:xforms="http://www.w3.org/2002/xforms" '; + $value .= 'xmlns:xsd="http://www.w3.org/2001/XMLSchema" '; + $value .= 'xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" '; + $value .= 'office:version="1.0">'; + $value .= '<office:scripts/>'; + $value .= '<office:font-face-decls>'; + $value .= '<style:font-face style:name="Tahoma1" svg:font-family="Tahoma"/>'; + $value .= '<style:font-face style:name="Lucida Sans Unicode" svg:font-family="'Lucida Sans Unicode'" style:font-pitch="variable"/>'; + $value .= '<style:font-face style:name="Tahoma" svg:font-family="Tahoma" style:font-pitch="variable"/>'; + $value .= '<style:font-face style:name="Times New Roman" svg:font-family="'Times New Roman'" style:font-family-generic="roman" style:font-pitch="variable"/>'; + $value .= '</office:font-face-decls>'; + $value .= $autostyles; + $value .= '<office:body>'; + $value .= '<office:text>'; + $value .= '<office:forms form:automatic-focus="false" form:apply-design-mode="false"/>'; + $value .= '<text:sequence-decls>'; + $value .= '<text:sequence-decl text:display-outline-level="0" text:name="Illustration"/>'; + $value .= '<text:sequence-decl text:display-outline-level="0" text:name="Table"/>'; + $value .= '<text:sequence-decl text:display-outline-level="0" text:name="Text"/>'; + $value .= '<text:sequence-decl text:display-outline-level="0" text:name="Drawing"/>'; + $value .= '</text:sequence-decls>'; + $value .= $userfields; + $value .= $this->doc; + $value .= '</office:text>'; + $value .= '</office:body>'; + $value .= '</office:document-content>'; + + $this->ZIP->add_File($value,'content.xml'); + + $value = io_readFile(DOKU_PLUGIN.'odt/styles.xml'); + $value = str_replace('<office:automatic-styles/>', $autostyles, $value); + $this->ZIP->add_File($value,'styles.xml'); + + // build final manifest + $this->_odtManifest(); + } + + /** + * Closes the document using a template + */ + function document_end_template(){ + global $conf, $ID; // for the temp dir + + // Temp dir + if (is_dir($conf['tmpdir'])) { + $temp_dir = $conf['tmpdir']; // version > 20070626 + } else { + $temp_dir = $conf['savedir'].'/cache/tmp'; // version <= 20070626 + } + $this->temp_dir = $temp_dir."/odt/".str_replace(':','-',$ID); + if (is_dir($this->temp_dir)) { $this->io_rm_rf($this->temp_dir); } + io_mkdir_p($this->temp_dir); + + // Extract template + $template_path = $conf['mediadir'].'/'.$this->getConf("tpl_dir")."/".$this->template; + $this->ZIP->Extract($template_path, $this->temp_dir); + + // Prepare content + $autostyles = $this->_odtAutoStyles(); + $missingstyles = $this->_odtStyles(); + $missingfonts = $this->_odtFonts(); + $userfields = $this->_odtUserFields(); + + // Insert content + $old_content = io_readFile($this->temp_dir.'/content.xml'); + if (strpos($old_content, 'DOKUWIKI-ODT-INSERT') !== FALSE) { // Replace the mark + $this->_odtReplaceInFile('/<text:p[^>]*>DOKUWIKI-ODT-INSERT<\/text:p>/', + $this->doc, $this->temp_dir.'/content.xml', true); + } else { // Append to the template + $this->_odtReplaceInFile('</office:text>', $this->doc.'</office:text>', $this->temp_dir.'/content.xml'); + } + + // Cut off unwanted content + if (strpos($old_content, 'DOKUWIKI-ODT-CUT-START') !== FALSE + && strpos($old_content, 'DOKUWIKI-ODT-CUT-STOP') !== FALSE) { + $this->_odtReplaceInFile('/DOKUWIKI-ODT-CUT-START.*DOKUWIKI-ODT-CUT-STOP/', + '', $this->temp_dir.'/content.xml', true); + } + + // Insert userfields + if (strpos($old_content, "text:user-field-decls") === FALSE) { // no existing userfields + $this->_odtReplaceInFile('/<office:text([^>]*)>/U', '<office:text\1>'.$userfields, $this->temp_dir.'/content.xml', TRUE); + } else { + $this->_odtReplaceInFile('</text:user-field-decls>', substr($userfields,23), $this->temp_dir.'/content.xml'); + } + + // Insert styles & fonts + $this->_odtReplaceInFile('</office:automatic-styles>', substr($autostyles, 25), $this->temp_dir.'/content.xml'); + $this->_odtReplaceInFile('</office:automatic-styles>', substr($autostyles, 25), $this->temp_dir.'/styles.xml'); + $this->_odtReplaceInFile('</office:styles>', $missingstyles.'</office:styles>', $this->temp_dir.'/styles.xml'); + $this->_odtReplaceInFile('</office:font-face-decls>', $missingfonts.'</office:font-face-decls>', $this->temp_dir.'/styles.xml'); + + // Build the Zip + $this->ZIP->Compress(null, $this->temp_dir, null); + $this->io_rm_rf($this->temp_dir); + } + + function _odtReplaceInFile($from, $to, $file, $regexp=FALSE) { + $value = io_readFile($file); + if ($regexp) { + $value = preg_replace($from, $to, $value); + } else { + $value = str_replace($from, $to, $value); + } + $file_f = fopen($file, 'w'); + fwrite($file_f, $value); + fclose($file_f); + } + + /** + * Recursively deletes a directory (equivalent to the "rm -rf" command) + * Found in comments on http://www.php.net/rmdir + */ + function io_rm_rf($f) { + if (is_dir($f)) { + foreach(glob($f.'/*') as $sf) { + if (is_dir($sf) && !is_link($sf)) { + $this->io_rm_rf($sf); + } else { + unlink($sf); + } + } + } else { // avoid nasty consequenses if something wrong is given + die("Error: not a directory - $f"); + } + rmdir($f); + } + + // not supported - use OpenOffice builtin tools instead! + function render_TOC() { return ''; } + + function toc_additem($id, $text, $level) {} + + function _odtAutoStyles() { + $value = '<office:automatic-styles>'; + foreach ($this->autostyles as $stylename=>$stylexml) { + $value .= $stylexml; + } + $value .= '</office:automatic-styles>'; + return $value; + } + + function _odtUserFields() { + $value = '<text:user-field-decls>'; + foreach ($this->fields as $fname=>$fvalue) { + $value .= '<text:user-field-decl office:value-type="string" text:name="'.$fname.'" office:string-value="'.$fvalue.'"/>'; + } + $value .= '</text:user-field-decls>'; + return $value; + } + + /* Add missing styles in the template */ + function _odtStyles() { + $value = ''; + $existing_styles = io_readFile($this->temp_dir.'/styles.xml'); + foreach ($this->styles as $stylename=>$stylexml) { + if (strpos($existing_styles, 'style:name="'.$stylename.'"') === FALSE) { + $value .= $stylexml; + } + } + // Loop on bullet/numerotation styles + if (strpos($existing_styles, 'style:name="List_20_1"') === FALSE) { + $value .= '<text:list-style style:name="List_20_1" style:display-name="List 1">'; + for ($i=1;$i<=10;$i++) { + $value .= '<text:list-level-style-bullet text:level="'.$i.'" text:style-name="Numbering_20_Symbols" text:bullet-char="•"> + <style:list-level-properties text:space-before="'.(0.4*($i-1)).'cm" text:min-label-width="0.4cm"/> + <style:text-properties style:font-name="StarSymbol"/> + </text:list-level-style-bullet>'; + } + $value .= '</text:list-style>'; + } + if (strpos($existing_styles, 'style:name="Numbering_20_1"') === FALSE) { + $value .= '<text:list-style style:name="Numbering_20_1" style:display-name="Numbering 1">'; + for ($i=1;$i<=10;$i++) { + $value .= '<text:list-level-style-number text:level="'.$i.'" text:style-name="Numbering_20_Symbols" style:num-suffix="." style:num-format="1"> + <style:list-level-properties text:space-before="'.(0.5*($i-1)).'cm" text:min-label-width="0.5cm"/> + </text:list-level-style-number>'; + } + $value .= '</text:list-style>'; + } + return $value; + } + + /* Add missing fonts in the template */ + function _odtFonts() { + $value = ''; + $existing_styles = io_readFile($this->temp_dir.'/styles.xml'); + foreach ($this->fonts as $name=>$xml) { + if (strpos($existing_styles, 'style:name="'.$name.'"') === FALSE) { + $value .= $xml; + } + } + return $value; + } + + function cdata($text) { + $this->doc .= $this->_xmlEntities($text); + } + + function p_open($style='Text_20_body'){ + if (!$this->in_paragraph) { // opening a paragraph inside another paragraph is illegal + $this->in_paragraph = true; + $this->doc .= '<text:p text:style-name="'.$style.'">'; + } + } + + function p_close(){ + if ($this->in_paragraph) { + $this->in_paragraph = false; + $this->doc .= '</text:p>'; + } + } + + function header($text, $level, $pos){ + $hid = $this->_headerToLink($text,true); + $this->doc .= '<text:h text:style-name="Heading_20_'.$level.'" text:outline-level="'.$level.'">'; + $this->doc .= '<text:bookmark-start text:name="'.$hid.'"/>'; + $this->doc .= $this->_xmlEntities($text); + $this->doc .= '<text:bookmark-end text:name="'.$hid.'"/>'; + $this->doc .= '</text:h>'; + } + + function hr() { + $this->doc .= '<text:p text:style-name="Horizontal_20_Line"/>'; + } + + function linebreak() { + $this->doc .= '<text:line-break/>'; + } + + function strong_open() { + $this->doc .= '<text:span text:style-name="Strong_20_Emphasis">'; + } + + function strong_close() { + $this->doc .= '</text:span>'; + } + + function emphasis_open() { + $this->doc .= '<text:span text:style-name="Emphasis">'; + } + + function emphasis_close() { + $this->doc .= '</text:span>'; + } + + function underline_open() { + $this->doc .= '<text:span text:style-name="underline">'; + } + + function underline_close() { + $this->doc .= '</text:span>'; + } + + function monospace_open() { + $this->doc .= '<text:span text:style-name="Source_20_Text">'; + } + + function monospace_close() { + $this->doc .= '</text:span>'; + } + + function subscript_open() { + $this->doc .= '<text:span text:style-name="sub">'; + } + + function subscript_close() { + $this->doc .= '</text:span>'; + } + + function superscript_open() { + $this->doc .= '<text:span text:style-name="sup">'; + } + + function superscript_close() { + $this->doc .= '</text:span>'; + } + + function deleted_open() { + $this->doc .= '<text:span text:style-name="del">'; + } + + function deleted_close() { + $this->doc .= '</text:span>'; + } + + /* + * Tables + */ + function table_open($maxcols = NULL, $numrows = NULL){ + $this->doc .= '<table:table>'; + for($i=0; $i<$maxcols; $i++){ + $this->doc .= '<table:table-column />'; + } + } + + function table_close(){ + $this->doc .= '</table:table>'; + } + + function tablerow_open(){ + $this->doc .= '<table:table-row>'; + } + + function tablerow_close(){ + $this->doc .= '</table:table-row>'; + } + + function tableheader_open($colspan = 1, $align = "left"){ + $this->doc .= '<table:table-cell office:value-type="string" table:style-name="tableheader" '; + //$this->doc .= ' table:style-name="tablealign'.$align.'"'; + if ( $colspan > 1 ) { + $this->doc .= ' table:number-columns-spanned="'.$colspan.'"'; + } + $this->doc .= '>'; + $this->p_open('Table_20_Heading'); + } + + function tableheader_close(){ + $this->p_close(); + $this->doc .= '</table:table-cell>'; + } + + function tablecell_open($colspan = 1, $align = "left"){ + $this->doc .= '<table:table-cell office:value-type="string" table:style-name="tablecell" '; + if ( $colspan > 1 ) { + $this->doc .= ' table:number-columns-spanned="'.$colspan.'"'; + } + $this->doc .= '>'; + if (!$align) $align = "left"; + $style = "tablealign".$align; + $this->p_open($style); + } + + function tablecell_close(){ + $this->p_close(); + $this->doc .= '</table:table-cell>'; + } + + /** + * Callback for footnote start syntax + * + * All following content will go to the footnote instead of + * the document. To achieve this the previous rendered content + * is moved to $store and $doc is cleared + * + * @author Andreas Gohr <andi@splitbrain.org> + */ + function footnote_open() { + + // move current content to store and record footnote + $this->store = $this->doc; + $this->doc = ''; + } + + /** + * Callback for footnote end syntax + * + * All rendered content is moved to the $footnotes array and the old + * content is restored from $store again + * + * @author Andreas Gohr + */ + function footnote_close() { + // recover footnote into the stack and restore old content + $footnote = $this->doc; + $this->doc = $this->store; + $this->store = ''; + + // check to see if this footnote has been seen before + $i = array_search($footnote, $this->footnotes); + + if ($i === false) { + $i = count($this->footnotes); + // its a new footnote, add it to the $footnotes array + $this->footnotes[$i] = $footnote; + + $this->doc .= '<text:note text:id="ftn'.$i.'" text:note-class="footnote">'; + $this->doc .= '<text:note-citation>'.($i+1).'</text:note-citation>'; + $this->doc .= '<text:note-body>'; + $this->doc .= '<text:p text:style-name="Footnote">'; + $this->doc .= $footnote; + $this->doc .= '</text:p>'; + $this->doc .= '</text:note-body>'; + $this->doc .= '</text:note>'; + + } else { + // seen this one before - just reference it FIXME: style isn't correct yet + $this->doc .= '<text:note-ref text:note-class="footnote" text:ref-name="ftn'.$i.'">'.($i+1).'</text:note-ref>'; + } + } + + function listu_open() { + $this->p_close(); + $this->doc .= '<text:list text:style-name="List_20_1">'; + } + + function listu_close() { + $this->doc .= '</text:list>'; + } + + function listo_open() { + $this->p_close(); + $this->doc .= '<text:list text:style-name="Numbering_20_1">'; + } + + function listo_close() { + $this->doc .= '</text:list>'; + } + + function listitem_open($level) { + $this->in_list_item = true; + $this->doc .= '<text:list-item>'; + } + + function listitem_close() { + $this->in_list_item = false; + $this->doc .= '</text:list-item>'; + } + + function listcontent_open() { + $this->doc .= '<text:p text:style-name="Text_20_body">'; + } + + function listcontent_close() { + $this->doc .= '</text:p>'; + } + + function unformatted($text) { + $this->doc .= $this->_xmlEntities($text); + } + + function acronym($acronym) { + $this->doc .= $this->_xmlEntities($acronym); + } + + function smiley($smiley) { + if ( array_key_exists($smiley, $this->smileys) ) { + $src = DOKU_INC."lib/images/smileys/".$this->smileys[$smiley]; + $this->_odtAddImage($src); + } else { + $this->doc .= $this->_xmlEntities($smiley); + } + } + + function entity($entity) { + # UTF-8 entity decoding is broken in PHP <5 + if (version_compare(phpversion(), "5.0.0") and array_key_exists($entity, $this->entities) ) { + # decoding may fail for missing Multibyte-Support in entity_decode + $dec = @html_entity_decode($this->entities[$entity],ENT_NOQUOTES,'UTF-8'); + if($dec){ + $this->doc .= $this->_xmlEntities($dec); + }else{ + $this->doc .= $this->_xmlEntities($entity); + } + } else { + $this->doc .= $this->_xmlEntities($entity); + } + } + + function multiplyentity($x, $y) { + $this->doc .= $x.'×'.$y; + } + + function singlequoteopening() { + global $lang; + $this->doc .= $lang['singlequoteopening']; + } + + function singlequoteclosing() { + global $lang; + $this->doc .= $lang['singlequoteclosing']; + } + + function apostrophe() { + global $lang; + $this->doc .= $lang['apostrophe']; + } + + function doublequoteopening() { + global $lang; + $this->doc .= $lang['doublequoteopening']; + } + + function doublequoteclosing() { + global $lang; + $this->doc .= $lang['doublequoteclosing']; + } + + function php($text, $wrapper='dummy') { + $this->monospace_open(); + $this->doc .= $this->_xmlEntities($text); + $this->monospace_close(); + } + function phpblock($text) { + $this->file($text); + } + + function html($text, $wrapper='dummy') { + $this->monospace_open(); + $this->doc .= $this->_xmlEntities($text); + $this->monospace_close(); + } + function htmlblock($text) { + $this->file($text); + } + + /** + * static call back to replace spaces + */ + function _preserveSpace($matches){ + $spaces = $matches[1]; + $len = strlen($spaces); + return '<text:s text:c="'.$len.'"/>'; + } + + function preformatted($text) { + $text = $this->_xmlEntities($text); + if (strpos($text, "\n") !== FALSE and strpos($text, "\n") == 0) { + // text starts with a newline, remove it + $text = substr($text,1); + } + $text = str_replace("\n",'<text:line-break/>',$text); + $text = preg_replace_callback('/( +)/',array('renderer_plugin_odt','_preserveSpace'),$text); + + if ($this->in_list_item) { // if we're in a list item, we must close the <text:p> tag + $this->doc .= '</text:p>'; + $this->doc .= '<text:p text:style-name="Preformatted_20_Text">'; + $this->doc .= $text; + $this->doc .= '</text:p>'; + $this->doc .= '<text:p>'; + } else { + $this->doc .= '<text:p text:style-name="Preformatted_20_Text">'; + $this->doc .= $text; + $this->doc .= '</text:p>'; + } + } + + function file($text) { + $this->preformatted($text); + } + + function quote_open() { + if (!$this->in_paragraph) { // only start a new par if we're not already in one + $this->p_open(); + } + $this->doc .= ">"; + } + + function quote_close() { + if ($this->in_paragraph) { // only close the paragraph if we're actually in one + $this->p_close(); + } + } + + function code($text, $language = NULL) { + // FIXME we could convert GeSHi HTML and CSS classes to ODT XML here + $this->preformatted($text); + } + + function internalmedia ($src, $title=NULL, $align=NULL, $width=NULL, + $height=NULL, $cache=NULL, $linking=NULL) { + global $conf; + global $ID; + resolve_mediaid(getNS($ID),$src, $exists); + list($ext,$mime) = mimetype($src); + + if(substr($mime,0,5) == 'image'){ + $file = mediaFN($src); + $this->_odtAddImage($file, $width, $height, $align, $title); + }else{ + // FIXME build absolute medialink and call externallink() + $this->code('FIXME internalmedia: '.$src); + } + } + + function externalmedia ($src, $title=NULL, $align=NULL, $width=NULL, + $height=NULL, $cache=NULL, $linking=NULL) { + global $conf; + global $ID; + list($ext,$mime) = mimetype($src); + + if(substr($mime,0,5) == 'image'){ + if($width) { + $width = 'svg:width="'.(($width/96.0)*2.54).'cm"'; + } else { + $width = 'svg:rel-width="100%"'; + } + if($height) { + $height = 'svg:height="'.(($height/96.0)*2.54).'cm"'; + } else { + $height = 'svg:rel-height="100%"'; + } + + $style = 'media'.$align; + if($align){ + $anchor = 'paragraph'; + }else{ + $anchor = 'as-char'; + } + + $this->doc .= '<draw:frame draw:style-name="'.$style.'" draw:name="'.$this->_xmlEntities($title).'" + text:anchor-type="'.$anchor.'" draw:z-index="0" + '.$width.' '.$height.' >'; + $this->doc .= '<draw:image xlink:href="'.$this->_xmlEntities($src).'" + xlink:type="simple" xlink:show="embed" xlink:actuate="onLoad"/>'; + $this->doc .= '</draw:frame>'; + }else{ + $this->externallink($src,$title); + } + } + + function camelcaselink($link) { + $this->internallink($link,$link); + } + + function reference($id, $name = NULL) { + $this->doc .= '<text:a xlink:type="simple" xlink:href="#'.$id.'"'; + if ($name) { + $this->doc .= '>'.$this->_xmlEntities($name).'</text:a>'; + } else { + $this->doc .= '/>'; + } + } + + /** + * Render an internal Wiki Link + * + * @author Andreas Gohr <andi@splitbrain.org> + */ + function internallink($id, $name = NULL) { + global $conf; + global $ID; + // default name is based on $id as given + $default = $this->_simpleTitle($id); + // now first resolve and clean up the $id + resolve_pageid(getNS($ID),$id,$exists); + $name = $this->_getLinkTitle($name, $default, $isImage, $id); + + // build the absolute URL (keeping a hash if any) + list($id,$hash) = explode('#',$id,2); + $url = wl($id,'',true); + if($hash) $url .='#'.$hash; + + if ($ID == $id) { + $this->reference($hash, $name); + } else { + $this->_doLink($url,$name); + } + } + + /** + * Add external link + */ + function externallink($url, $name = NULL) { + global $conf; + + $name = $this->_getLinkTitle($name, $url, $isImage); + + $this->_doLink($url,$name); + } + + /** + * Just print local links + * + * @fixme add image handling + */ + function locallink($hash, $name = NULL){ + $name = $this->_getLinkTitle($name, $hash, $isImage); + $this->doc .= $name; + } + + /** + * InterWiki links + */ + function interwikilink($match, $name = NULL, $wikiName, $wikiUri) { + $name = $this->_getLinkTitle($name, $wikiUri, $isImage); + $url = $this-> _resolveInterWiki($wikiName,$wikiUri); + $this->_doLink($url,$name); + } + + /** + * Just print WindowsShare links + * + * @fixme add image handling + */ + function windowssharelink($url, $name = NULL) { + $name = $this->_getLinkTitle($name, $url, $isImage); + $this->doc .= $name; + } + + /** + * Just print email links + * + * @fixme add image handling + */ + function emaillink($address, $name = NULL) { + $name = $this->_getLinkTitle($name, $address, $isImage); + $this->_doLink("mailto:".$address,$name); + } + + /** + * Add a hyperlink, handling Images correctly + * + * @author Andreas Gohr <andi@splitbrain.org> + */ + function _doLink($url,$name){ + $url = $this->_xmlEntities($url); + if(is_array($name)){ + // Images + if($url) $this->doc .= '<draw:a xlink:type="simple" xlink:href="'.$url.'">'; + + if($name['type'] == 'internalmedia'){ + $this->internalmedia($name['src'], + $name['title'], + $name['align'], + $name['width'], + $name['height'], + $name['cache'], + $name['linking']); + } + + if($url) $this->doc .= '</draw:a>'; + }else{ + // Text + if($url) $this->doc .= '<text:a xlink:type="simple" xlink:href="'.$url.'">'; + $this->doc .= $name; // we get the name already XML encoded + if($url) $this->doc .= '</text:a>'; + } + } + + /** + * Construct a title and handle images in titles + * + * @author Harry Fuecks <hfuecks@gmail.com> + */ + function _getLinkTitle($title, $default, & $isImage, $id=null) { + global $conf; + + $isImage = false; + if ( is_null($title) ) { + if ($conf['useheading'] && $id) { + $heading = p_get_first_heading($id); + if ($heading) { + return $this->_xmlEntities($heading); + } + } + return $this->_xmlEntities($default); + } else if ( is_string($title) ) { + return $this->_xmlEntities($title); + } else if ( is_array($title) ) { + $isImage = true; + return $title; + } + } + + /** + * Creates a linkid from a headline + * + * @param string $title The headline title + * @param boolean $create Create a new unique ID? + * @author Andreas Gohr <andi@splitbrain.org> + */ + function _headerToLink($title,$create=false) { + $title = str_replace(':','',cleanID($title)); + $title = ltrim($title,'0123456789._-'); + if(empty($title)) $title='section'; + + if($create){ + // make sure tiles are unique + $num = ''; + while(in_array($title.$num,$this->headers)){ + ($num) ? $num++ : $num = 1; + } + $title = $title.$num; + $this->headers[] = $title; + } + + return $title; + } + + + function _xmlEntities($value) { + return str_replace( array('&','"',"'",'<','>'), array('&','"',''','<','>'), $value); + } + + function rss ($url,$params){ + global $lang; + global $conf; + + require_once(DOKU_INC.'inc/FeedParser.php'); + $feed = new FeedParser(); + $feed->feed_url($url); + + //disable warning while fetching + if (!defined('DOKU_E_LEVEL')) { $elvl = error_reporting(E_ERROR); } + $rc = $feed->init(); + if (!defined('DOKU_E_LEVEL')) { error_reporting($elvl); } + + //decide on start and end + if($params['reverse']){ + $mod = -1; + $start = $feed->get_item_quantity()-1; + $end = $start - ($params['max']); + $end = ($end < -1) ? -1 : $end; + }else{ + $mod = 1; + $start = 0; + $end = $feed->get_item_quantity(); + $end = ($end > $params['max']) ? $params['max'] : $end;; + } + + $this->listu_open(); + if($rc){ + for ($x = $start; $x != $end; $x += $mod) { + $item = $feed->get_item($x); + $this->listitem_open(0); + $this->listcontent_open(); + $this->externallink($item->get_permalink(), + $item->get_title()); + if($params['author']){ + $author = $item->get_author(0); + if($author){ + $name = $author->get_name(); + if(!$name) $name = $author->get_email(); + if($name) $this->cdata(' '.$lang['by'].' '.$name); + } + } + if($params['date']){ + $this->cdata(' ('.$item->get_date($conf['dformat']).')'); + } + if($params['details']){ + $this->cdata(strip_tags($item->get_description())); + } + $this->listcontent_close(); + $this->listitem_close(); + } + }else{ + $this->listitem_open(0); + $this->listcontent_open(); + $this->emphasis_open(); + $this->cdata($lang['rssfailed']); + $this->emphasis_close(); + $this->externallink($url); + $this->listcontent_close(); + $this->listitem_close(); + } + $this->listu_close(); + } + + + function _odtAddImage($src, $width = NULL, $height = NULL, $align = NULL, $title = NULL, $style = NULL){ + list($ext,$mime) = mimetype($src); + $name = 'Pictures/'.md5($src).'.'.$ext; + if(!$this->manifest[$name]){ + $this->manifest[$name] = $mime; + $this->ZIP->add_File(io_readfile($src,false),$name,0); + } + // make sure width and height is available + // FIXME we don't have the dimension of an external file + // (except it's cached, but this is not the default) there seems + // to be no way to specify "use original image" in ODF - thus + // a hardcoded default size of 200 pixel here + if(!$width || !$height){ + $info = getimagesize($src); + if(!$width){ + $width = $info[0]; + $height = $info[1]; + }else{ + $height = round(($width * $info[1]) / $info[0]); + } + } + + // convert from pixel to centimeters + $width = (($width/96.0)*2.54); + $height = (($height/96.0)*2.54); + // Don't be wider than the page + if ($width >= 17){ // FIXME : this assumes A4 page format with 2cm margins + $width = $width.'cm" style:rel-width="100%'; + $height = $height.'cm" style:rel-height="scale'; + } else { + $width = $width.'cm'; + $height = $height.'cm'; + } + + if($align){ + $anchor = 'paragraph'; + }else{ + $anchor = 'as-char'; + } + + if (!$style or !array_key_exists($style, $this->autostyles)) { + $style = 'media'.$align; + } + + if ($title) { + $this->doc .= '<draw:frame draw:style-name="'.$style.'" draw:name="'.$this->_xmlEntities($title).' Legend" + text:anchor-type="'.$anchor.'" draw:z-index="0" svg:width="'.$width.'">'; + $this->doc .= '<draw:text-box>'; + $this->doc .= '<text:p text:style-name="legendcenter">'; + } + $this->doc .= '<draw:frame draw:style-name="'.$style.'" draw:name="'.$this->_xmlEntities($title).'" + text:anchor-type="'.$anchor.'" draw:z-index="0" + svg:width="'.$width.'" svg:height="'.$height.'" >'; + $this->doc .= '<draw:image xlink:href="'.$this->_xmlEntities($name).'" + xlink:type="simple" xlink:show="embed" xlink:actuate="onLoad"/>'; + $this->doc .= '</draw:frame>'; + if ($title) { + $this->doc .= $this->_xmlEntities($title).'</text:p></draw:text-box></draw:frame>'; + } + } + +} + +//Setup VIM: ex: et ts=4 enc=utf-8 : |