DokuWiki

It's better when it's simple

User Tools

Site Tools


plugin:odt:implementodtsupport

ODT Plugin homepage

Adding ODT Support To A Plugin

In the following I give a short introduction on how other plugins can make use of the ODT export plugin to export their content to ODT. As this is only a short introduction it is not meant to be a complete guide to the ODT export plugin functions or the ODT xml format itself.

The plugins that have added ODT support are listed at the ODT render support page.

Requirements

  1. You need to install the plugin “OpenOffice.org Export”. See the ODT plugin page.
  2. Of course, you need a working plugin as a base to be extended for ODT export. See extensions on how to write your own plugin.
  3. You should have a basic understanding of php, xhtml and xml of course.

Implementing the ODT export

In the following I assume that the plugin to be extended is a DokuWiki_Syntax_Plugin. The procedure to extend other kinds of plugins with ODT export might be different.

Differ between the renderer output formats

Each plugin which is of the type DokuWiki_Syntax_Plugin needs to implement the public function render() which is doing the main work of delivering the content for the different output formats. In a plugin which only supports XHTML (Extensible HyperText Markup Language) output yet, the obvious first step is to differ between the output formats and branch to the corresponding function for a certain output format.

Basically an existing render function looks like this:

    public function render($mode, Doku_Renderer $renderer, $indata) {
        if ($mode == 'xhtml') {
            /* @var Doku_Renderer_xhtml $renderer */
            // Generate XHTML content...
            $renderer->doc .= 'My XHTML code and content';
            return true;
        }
 
        // We do not support any other output formats yet.
        return false;
    }

So we have to extend the function to catch the mode 'odt' and add our ODT content generating code. The most beautiful solution is to use the function render only to branch to the corresponding function for content generation and pass through the parameters.

So the adopted code might look like this:

    public function render($mode, Doku_Renderer $renderer, $indata) {
        switch ($mode) {
            case "xhtml":
                /* @var Doku_Renderer_xhtml $renderer */
                // Calling the XHTML render function.
                $this->render_for_xhtml($renderer, $data);
                break;
            case "odt":
                /* @var renderer_plugin_odt_page $renderer */
                // Calling the ODT render function.
                $this->render_for_odt($renderer, $data);
                break;
            default:
                // Some other format that we do not support.
                return false;
        }
 
        // Success. We did support the format.
        return true;
    }
 
    protected function render_for_xhtml ($renderer, $indata) {
        // Generate XHTML content...
        $renderer->doc .= 'My XHTML code and content';
    }
 
    protected function render_for_odt ($renderer, $indata) {
        // Generate ODT content...
        $renderer->doc .= 'My ODT xml code and content';
    }

Generating valid ODT xml content

As you might have already noticed, the main work or effort is of course to generate a valid ODT file with valid ODT xml content. An ODT file is usually a zip archive consisting of the following file structure:

  • The folder “Thumbnails”:
    It includes thumbnails of the document pages. I assume these are used by the print preview function.
  • The folder “META-INF”:
    It includes the file “manifest.xml”. The file includes the list of files that are included in the ODT zip file and their media types.
  • The folder “Pictures”:
    This folder is only present if the ODT document includes pictures. Such pictures might be stored in this folder.
  • The file “content.xml”:
    This file includes the actual content of the document except binary files like pictures.
  • The file “styles.xml”:
    This file includes the style definitions. Style definitions describe the layout of the content. The style e.g. might define the font color, text alignment or the stroke color for graphics. Style definitions are also included in the content.xml file.
  • The file “meta.xml”:
    This file includes meta information about the ODT document such like the authors name for example.
  • The file “settings.xml”:
    This file includes information which is not belonging to the document content itself but is useful for the application which uses the document e.g. OpenOffice Writer.
  • The file “mimetype”:
    This file includes the mimetype.

If you like to have a look yourself, just rename an ODT document an change the file extension from .odt to .zip. Then un-zip the archive and have a look.

Even the generation of an empty file would mean some work for us. Thanks to the ODT export plugin from Andreas Gohr, Aurélien Bompard and Florian Lamml, we do not need to do this work ourselves. The only thing our plugin needs to do is to insert valid ODT xml code for 'our' content. The creation and packetization of the file is all done by the ODT export plugin.

Creating our first content

Let's create some first simple text output. Make sure your ODT render function looks like this:

    protected function render_for_odt ($renderer, $indata) {
        // Generate ODT content...
        $renderer->doc. = 'My ODT xml code and content';
    }

Open your wiki page, export it to ODT and have a look. The text was inserted at the current position directly between the other content.

Creating a paragraph

Now, we enclose the text with a paragraph. We call the functions p_open() and p_close() (see ODT export plugin, file renderer/page.php if you like). So we do not need to write the ODT xml code ourselves. Make sure your ODT render function looks like this:

    protected function render_for_odt ($renderer, $indata) {
        // Generate ODT content...
        $renderer->p_open();
        $renderer->doc .= 'My ODT xml code and content';
        $renderer->p_close();
    }

Again, test it and see (Open your wiki page, export it to ODT and have a look), no change.

Adding a linebreak

If you want to insert a new line simply call the linebreak() function. Make sure your ODT render function looks like this:

    protected function render_for_odt ($renderer, $indata) {
        // Generate ODT content...
        $renderer->p_open();
        $renderer->doc .= 'My ODT xml code and content';
        $renderer->linebreak();
        $renderer->doc .= 'This is another line';
        $renderer->p_close();
    }

Text styles

The ODT export plugin also includes functions for the various text styles like bold or italic. Make sure your ODT render function looks like this:

    protected function render_for_odt ($renderer, $indata) {
        // Generate ODT content...
        $renderer->p_open();
        $renderer->doc .= 'My ODT xml code and content';
        $renderer->linebreak();
        $renderer->doc .= 'This is another line';
        $renderer->linebreak();
        $renderer->linebreak();
 
        $renderer->strong_open();
        $renderer->doc .= 'This is strong text.';
        $renderer->strong_close();
        $renderer->linebreak();
        $renderer->linebreak();
 
        $renderer->emphasis_open();
        $renderer->doc .= 'This is emphasis text.';
        $renderer->emphasis_close();
        $renderer->linebreak();
        $renderer->linebreak();
 
        $renderer->underline_open();
        $renderer->doc .= 'This is underlined text.';
        $renderer->underline_close();
        $renderer->linebreak();
        $renderer->linebreak();
 
        $renderer->monospace_open();
        $renderer->doc .= 'This is monospace text.';
        $renderer->monospace_close();
        $renderer->linebreak();
        $renderer->linebreak();
 
        $renderer->subscript_open();
        $renderer->doc .= 'This is subscript text.';
        $renderer->subscript_close();
        $renderer->linebreak();
        $renderer->linebreak();
 
        $renderer->superscript_open();
        $renderer->doc .= 'This is superscript text.';
        $renderer->superscript_close();
        $renderer->linebreak();
        $renderer->linebreak();
 
        $renderer->deleted_open();
        $renderer->doc .= 'This is deleted text.';
        $renderer->deleted_close();
        $renderer->linebreak();
        $renderer->linebreak();
 
        $renderer->p_close();
    }

If you export the page, you will see all the various text styles. If you want to use different styles at the same time, just combine the functions. The following code shows an example which will create text which is bold and underlined.

    protected function render_for_odt ($renderer, $indata) {
        // Generate ODT content...
        $renderer->p_open();
 
        $renderer->strong_open();
        $renderer->underline_open();
        $renderer->doc .= 'This is strong AND underlined text.';
        $renderer->underline_close();
        $renderer->strong_close();
        $renderer->linebreak();
 
        $renderer->p_close();
    }

Tables

The following code creates a 3×3 table with a table header spanned over all columns:

    protected function render_for_odt ($renderer, $indata) {
        // Generate ODT content...
 
        // This line is important!!!
        $renderer->p_close();
 
        $renderer->table_open(3,3);
 
        $renderer->tablerow_open();
        $renderer->tableheader_open(3,1);
        $renderer->doc .= 'Tableheader.';
        $renderer->tableheader_close();
        $renderer->tablerow_close();
 
        $renderer->tablerow_open();
        $renderer->tablecell_open();
        $renderer->p_open();
        $renderer->doc .= 'Cell 1/1';
        $renderer->p_close();
        $renderer->tablecell_close();
        $renderer->tablecell_open();
        $renderer->p_open();
        $renderer->doc .= 'Cell 2/1';
        $renderer->p_close();
        $renderer->tablecell_close();
        $renderer->tablecell_open();
        $renderer->p_open();
        $renderer->doc .= 'Cell 3/1';
        $renderer->p_close();
        $renderer->tablecell_close();
        $renderer->tablerow_close();
 
        $renderer->tablerow_open();
        $renderer->tablecell_open();
        $renderer->p_open();
        $renderer->doc .= 'Cell 1/2';
        $renderer->p_close();
        $renderer->tablecell_close();
        $renderer->tablecell_open();
        $renderer->p_open();
        $renderer->doc .= 'Cell 2/2';
        $renderer->p_close();
        $renderer->tablecell_close();
        $renderer->tablecell_open();
        $renderer->p_open();
        $renderer->doc .= 'Cell 3/2';
        $renderer->p_close();
        $renderer->tablecell_close();
        $renderer->tablerow_close();
 
        $renderer->table_close();
    }

The initial call to p_close() is important to make sure the paragraph is closed before creating the table. You can even call p_close if no paragraph is open because it keeps count of the open paragraphs. If you forget to close the paragraph, then the resulting ODT file format will be corrupted because you inserted a table into a paragraph.

Images

An image can simply be included by a call to the function _odtAddImage() passing the file name as the parameter. Make sure that the file includes the full path, otherwise it might not be found. The following code shows an example in which the image “example.png” included in the plugins subdirectory “images” will be loaded (this assumes that DOKU_INC and DOKU_PLUGIN_IMAGES carry the standard values).

    protected function render_for_odt ($renderer, $indata) {
        // Generate ODT content...
        $renderer->_odtAddImage (DOKU_INC . DOKU_PLUGIN_IMAGES . 'example.png');
    }

Graphics

FIXME

CSS based functions

In the ODT format elements like spans, paragraphs, frames and tables use styles to specify the look of the element. These styles have properties which are often similar to CSS properties. But some facts make writing ODT styles an exhaustive and annoying work:

  • The style definition needs to match the ODT standard. This is quite huge and it costs time do find the information you require.
  • The style definitions are defined in XML and can easily span several lines and become confusing
  • The properties are similar but often the ODT standard has exceptions to CSS
  • An ODT style can not be defined inline inside a element.

To take the burden of writing/defining ODT styles from the plugin developers, ODT functions have been written which use CSS to define the style of an element. This makes it much simpler for plugin developers to export to ODT for the following reasons:

  • Existing CSS styles from the plugins 'style.css' or other files can be reused
  • Styles can be passed to some CSS based functions directly similar to XHTML code like style='color:red;'
  • Many plugin developers are familiar to CSS or know it very well
  • There is more information in the internet about CSS than about ODT styles

In the following paragraphs the CSS based functions of the ODT renderer are described. CSS support in the ODT renderer exists since release 2015-03-20 and are still in development.

Importing CSS from a file

Some of the functions reuse CSS code from other files like e.g. 'style.css'. The imported file needs to be passed as an argument to these functions. Here is an example code showing the import:

  // Import Wrap-CSS.
  if ( self::$import == NULL ) {
    self::$import = plugin_load('helper', 'odt_cssimport');
    self::$import->importFrom(DOKU_PLUGIN.'myplugin/style.css');
    self::$import->loadReplacements(DOKU_INC.DOKU_TPL.'style.ini');
  }
 
  // Now, $import can be used in later functions calls...

The code saves the imported file in the static class variable $import. This prevents importing the file every time the handle or render functions are called. In the following examples it is assumed that a static class variable $import carrying the information of a imported CSS file does exist.

Function _odtSpanOpenUseCSS()

The function opens a new text span. The style is specified by a class string which represents the CSS selector. An imported CSS file needs to be passed to the function also. Here is an example call:

$renderer->_odtSpanOpenUseCSS (self::$import,
                               'dokuwiki myplugin',
                               DOKU_PLUGIN.'myplugin/');

This will open a span which uses the CSS properties of the classes dokuwiki and myplugin. The third argument represents a path which can be used to convert the URL of a background-image into a local path. In this example a URL like from background-image: url(images/example.png); would become the local path DOKU_PLUGIN.'myplugin/images/example.png' .

The function makes use of the following CSS properties:

  • background-color
  • color
  • font-weight
  • font-size
  • border
  • font-family
  • font-variant
  • letter-spacing
  • vertical-align
  • background-image (emulated)

The function _odtSpanClose() needs to be called to close the text span.

Function _odtSpanOpenUseCSSStyle()

The function opens a new text span. The style is specified as a string including the CSS properties. Here is an example call:

$renderer->_odtSpanOpenUseCSSStyle('color:red;background-color:black');

This will open a span with a red text color on a black background.

As a second parameter the function also optionally accepts a URL replacement path, see function _odtSpanOpenUseCSS(). For supported properties, please see function _odtSpanOpenUseCSS(). The function _odtSpanClose() needs to be called to close the text span.

Function _odtSpanOpenUseProperties()

The function opens a new text span. The style is specified by passing an associative array carrying the CSS properties. Here is an example call:

$properties ['color'] = 'red';
$properties ['background-color'] = 'black';
$renderer->_odtSpanOpenUseProperties($properties);

This will open a span with a red text color on a black background.

For supported properties, please see function _odtSpanOpenUseCSS(). The function _odtSpanClose() needs to be called to close the text span.

Function _odtParagraphOpenUseCSS()

The function opens a new paragraph. The style is specified by a class string which represents the CSS selector. An imported CSS file needs to be passed to the function also. Here is an example call:

$renderer->_odtParagraphOpenUseCSS (self::$import,
                                    'dokuwiki myplugin',
                                    DOKU_PLUGIN.'myplugin/');

This will open a paragraph which uses the CSS properties of the classes dokuwiki and myplugin. The third argument represents a path which can be used to convert the URL of a background-image into a local path. In this example a URL like from background-image: url(images/example.png); would become the local path DOKU_PLUGIN.'myplugin/images/example.png' .

The function makes use of the following CSS properties:

  • background-color
  • color
  • font-weight
  • font-size
  • border
  • font-family
  • font-variant
  • letter-spacing
  • vertical-align
  • line-height
  • background-image (emulated)

The function p_close() needs to be called to close the paragraph.

Function _odtParagraphOpenUseCSSStyle()

The function opens a new paragraph. The style is specified as a string including the CSS properties. Here is an example call:

$renderer->_odtParagraphOpenUseCSSStyle('color:red;background-color:black');

This will open a paragraph with a red text color on a black background.

As a second parameter the function also optionally accepts a URL replacement path, see function _odtParagraphOpenUseCSS(). For supported properties, please see function _odtParagraphOpenUseCSS(). The function p_close() needs to be called to close the paragraph.

Function _odtParagraphOpenUseProperties()

The function opens a new paragraph. The style is specified by passing an associative array carrying the CSS properties. Here is an example call:

$properties ['color'] = 'red';
$properties ['background-color'] = 'black';
$renderer->_odtParagraphOpenUseCSSStyle($properties);

This will open a paragraph with a red text color on a black background.

For supported properties, please see function _odtParagraphOpenUseCSS. The function p_close() needs to be called to close the paragraph.

Function _odtDivOpenAsFrameUseCSS()

The function opens a new frame(s) to represent a div element. The style is specified by a class string which represents the CSS selector. An imported CSS file needs to be passed to the function also. Here is an example call:

$renderer->_odtDivOpenAsFrameUseCSS (self::$import,
                                    'dokuwiki myplugin',
                                    DOKU_PLUGIN.'myplugin/');

This will open a frame which uses the CSS properties of the classes dokuwiki and myplugin. The third argument represents a path which can be used to convert the URL of a background-image into a local path. In this example a URL like from background-image: url(images/example.png); would become the local path DOKU_PLUGIN.'myplugin/images/example.png' .

The function makes use of the following CSS properties:

  • background-color
  • color
  • padding
  • margin
  • display
  • border-radius
  • min-height
  • background-image (emulated via an extra picture frame)

The function _odtDivCloseAsFrame() needs to be called to close the paragraph.

plugin/odt/implementodtsupport.txt · Last modified: 2015-10-12 20:56 by Klap-in