Table of Contents

Syntax Plugins

FIXME(this needs to be refactored)

Syntax Plugins are plugins to extend DokuWiki's syntax. To be able to understand what is needed to register new Syntax within DokuWiki you should read how the Parser works.

Synopsis

A Syntax Plugin example needs to define a class named syntax_plugin_example which extends DokuWiki_Syntax_Plugin1). The class needs to be stored in a file called lib/plugins/example/syntax.php. Moreover, a plugin.info.txt file is needed. For full details of plugins and their files refer to plugin file structure.

The class needs to implement at least the following functions:


The following additional methods can be overridden when required:

Additional functions can be defined as needed. It is recommended to prepend an underscore to self defined functions to avoid possible nameclashes with future plugin specification enhancements.


Inherited Properties

Syntax Types

DokuWiki uses different syntax types to determine which syntax may be nested. Eg. you can have text formatting inside of tables. To integrate your plugin into this system it needs to specify which type it is and which types can be nested within it. The following types are currently available:

Type Used in … Description
container listblock, table, quote, hr containers are complex modes that can contain many other modes – hr breaks the principle but they shouldn't be used in tables / lists so they are put here
baseonly header some modes are allowed inside the base mode only
formatting strong, emphasis, underline, monospace, subscript, superscript, deleted, footnote modes for styling text – footnote behaves similar to styling
substition5) 'acronym', 'smiley', 'wordblock', 'entity', 'camelcaselink', 'internallink', 'media', 'externallink', 'linebreak', 'emaillink', 'windowssharelink', 'filelink', 'notoc', 'nocache', 'multiplyentity', 'quotes', 'rss' modes where the token is simply replaced – they can not contain any other modes
protected 'preformatted', 'code', 'file', 'php', 'html'modes which have a start and end token but inside which no other modes should be applied
disabled unformatted inside this mode no wiki markup should be applied but lineendings and whitespace isn't preserved
paragraphs eol used to mark paragraph boundaries

For a description what each type means and which other formatting classes are registered in them read the comments in inc/parser/parser.php.

Tutorial: Syntax Plugins Explained

The goal of this tutorial is to explain the concepts involved in a DokuWiki syntax plugin and to go through the steps involved in writing your own plugin.

For those who are really impatient to get started, grab a copy of the syntax plugin skeleton. It's a bare bones plugin which outputs ”Hello World!” when it encounters ”<TEST>” on a wiki page.

Quick Summary

modes

handle

render

:!: There is no guarantee the render() method will be called at the same time as the handle() method. The instructions generated by the handler are cached and can be used by the renderer at a future time. The only sure way to pass data from handle() to render() is using the array it returns - which is passed to render() as the $data parameter.

Key Concepts

modes

Modes (or more properly syntax modes) are the foundation on which the DokuWiki parser is based. Every different bit of DokuWiki markup has its own syntax mode. E.g. there is a strong mode for handling strong, a superscript mode for handling superscript, a table mode for processing tables and many more.

When the parser encounters some markup it enters the syntax mode for that markup. The properties and methods of that particular syntax mode govern how the parser behaves while it is within that mode, including:

Your plugin will add its own syntax mode to the parser - that is automatically handled by DokuWiki when the plugin is first loaded, the name assigned is plugin_+ the name of the plugin's directory (which must also be the plugin's class name without the prefix ”syntax_”). Then, when the parser encounters the markup used for your plugin, the parser will enter into that syntax mode. While it is in that mode your plugin controls what the parser can do.

mode types

To simplify things, syntax modes which behave in a similar manner have been grouped together into several mode types - a complete list can be found on the syntax plugin page.

Each mode type corresponds to a key in the $PARSER_MODES array. The entry for each mode type is itself an array which holds all the syntax modes which belong to that type. e.g. In vanilla DokuWiki with no plugins installed, $PARSER_MODES['formatting'] holds an array containing: 'strong', 'emphasis', 'underline', 'superscript', 'subscript', 'monospace', 'deleted' & 'footnote'.

When each plugin is loaded into the parser it is queried, via getType(), to discover which mode type it will belong to. The syntax mode associated with the plugin is then added to the appropriate $PARSER_MODES array.

:!: The mode type your plugin reports governs where in a DokuWiki page the parser will recognise your plugin's markup. Other DokuWiki (and plugin) syntax modes won't know about your plugin, but they do know about the different mode types. If they allow a particular mode type, they will allow all the modes which belong to that type, including any plugins that have returned that mode type.

Select the mode type for your plugin by comparing the behaviour of your plugin to that of the standard DokuWiki syntax modes. Choose the type that the most similar modes belong to.

allowed modes

These are the other modes that can occur nested within the current mode's own markup.

Each syntax mode has its own array of allowed modes which tells the parser what other syntax modes will be recognised whilst it is processing the mode. That is, if you want your plugin to be able to occur nested within ”**strong**” markup, then the strong mode must include your plugin's mode in its allowedModes array. And if you want to allow strong markup nested within your plugin's markup then your plugin must have 'strong' in its allowModes array.

:!: Your plugin gets in the allowedModes array of other syntax modes through the mode type it reports using the getType() method.

:!: Your plugin tells the parser which other syntax modes it permits by reporting the mode types it allows via the getAllowedTypes() method.

PType

PType governs how the parser handles html <p> elements when dealing with your syntax mode.

Generally, when the parser encounters some markup, there will be a currently open HTML paragraph tag. The parser needs to know if it should close that tag before entering your syntax mode and then open another paragraph when exiting, that is PType='block' and PType='stack', or whether it should leave the paragraphs alone, PType='normal'.

The PType also decides how and if paragraphs are created inside the syntax mode. With PType='normal' no paragraphs are created at all. PType='stack' opens a paragraph when inside the syntax mode (and closes it later, parsing paragraphs like usual). And PType='block' starts with no paragraph, but creates them as usual as soon as there are more than two newlines.

For those that know CSS, returning PType='block' and PType='stack' means the html generated by your plugin will be similar to display:block and returning PType='normal'means the HTML generated will be similar to display:inline.

Example

Suppose we have a fairly standard syntax plugin with the ENTRY ⇒ UNMATCHED ⇒ EXIT pattern. Depending on the PType setting, <p> and </p> will be inserted by the renderer automatically at various points outside, or even interspersed, with the plugin text. That means your plugin doesn't need to take care of those tags.

wikisyntax PType=normal PType=block PType=stack
foo
<plugin>text</plugin>

bar
<p>foo
ENTRY("<plugin>")
UNMATCHED("text")
EXIT("</plugin>")
</p>
<p>bar</p>
<p>foo</p>
ENTRY("<plugin>")
UNMATCHED("text")
EXIT("</plugin>")
<p>bar</p>
<p>foo</p>
ENTRY("<plugin>")
<p>
UNMATCHED("text")
</p>
EXIT("</plugin>")
<p>bar</p>

Sort Number

This number is used by the lexer6) to control the order it tests the syntax mode patterns against raw wiki data. It is only important if the patterns belonging two or more modes match the same raw data - where the pattern belonging to the mode with the lowest sort number will win out.

You can make use of this behaviour to write a plugin which will replace or extend a native DokuWiki handler for the same syntax. An example is the code plugin.

Details of existing sort numbers are available for both the parser (sort list).

Patterns

The parser uses PHP's preg7) compatible functions. A detailed explanation of regular expressions and their syntax is beyond the scope of this tutorial. There are many good sources on the web.

The complete preg syntax is not available for use in constructing syntax plugin patterns. Below is a list of the known differences:


The parser provides four functions for a plugin to register the patterns it needs. Each function corresponds to a pattern with a different meaning.


One plugin may add several patterns to the parser, including more than one pattern of the same type.

Tips

handle() method

This is the part of your plugin which should do all the work. Before DokuWiki renders the wiki page it creates a list of instructions for the renderer. The plugin's handle() method generates the render instructions for the plugin's own syntax mode. At some later time, these will be interpreted by the plugin's render() method. The instruction list is cached and can be used many times, making it sensible to maximize the work done once by this function and minimize the work done many times by render().

$match parameter — The text matched by the patterns, or in the case of DOKU_LEXER_UNMATCHED the contiguous piece of ordinary text which didn't match any pattern.

$state parameter — The lexer state for the match, representing the type of pattern which triggered this call to handle():

$pos parameter — The character position of the matched text.

&$handler parameter — Object Reference to the Doku_Handler object.

render() method

The part of the plugin that provides the output for the final web page - or whatever other output format is supported. It is here that the plugin adds its output to that already generated by other parts of the renderer - by concatenating its output to the renderer's doc property. e.g.

$renderer->doc .= "some plugin output...";

:!: Any raw wiki data that passes through render() should have all special characters converted to HTML entities. You can use the PHP functions, htmlspecialchars(), htmlentities() or the renderer's own xmlEntities() method. e.g.

$renderer->doc .= $renderer->_xmlEntities($text);

$mode parameter — Name for the format mode of the final output produced by the renderer. At present DokuWiki only supports one output format - XHTML 8). New modes can be introduced by renderer plugins. The plugin should only produce output for those formats which it supports - which means this function should be structured …

if ($mode == 'xhtml') {  // supported mode
  // code to generate XHTML output from instruction $data
}

$data parameter — An array containing the instructions previously prepared by the plugin's own handle() method. This function must interpret the instruction and generate the appropriate output.

Metadata renderer

The metadata rendering mode extracts metadata from the page. This is particularly important if you manually handle certain kinds of links. If you don't register these, they will not show up as backlinks on the pages that they refer to. Here is an example of how to register these backlinks:

function render($mode, &$renderer, $data) 
{
	if($mode == 'xhtml') {
        //this is where you put all the rendering that will be displayed in the web browser
	return true;
	}
	if($mode == 'metadata') {
		$renderer->internallink($data[0]);
                //I am assuming that when processing in handle(), you have stored the link destination in $data[0]
		return true;
	}
	return false;
}

Safety & Security

Raw wiki page data which reaches your plugin has not been processed at all. No further processing is done on the output after it leaves your plugin. At an absolute minimum the plugin should ensure any raw data output has all HTML special characters converted to HTML entities. Also any wiki data extracted and used internally should be treated with suspicion. See also security.

Localization

FIXME

For now refer to localisation & plugin file structure

Configuration

Please refer to configuration.

Using Styles and JavaScript

FIXME

For now refer to plugin file structure

Adding a Toolbar Button

To make it easy on the users of wikis which install your plugin, you should add a button for its syntax to the editor toolbar.

See the Action plugin page, sample_action_plugin_2. Also refer to toolbar.

Writing Your Own Plugin

Ok, so you have decided you want to extend DokuWiki's syntax with your own plugin. You have worked out what that syntax will be and how it should be rendered on the user's browser. Now you need to write the plugin.

  1. Decide on a name for the plugin. You may want to check the list of available plugins to make sure you aren't choosing a name that is already in use.
  2. In your own DokuWiki installation, create a new sub directory in the lib/plugins/ directory. That directory will have the same name as your plugin.
  3. Create the file syntax.php in the new directory. As a starting point, use a copy of the skeleton plugin.
  4. Edit that file to make it yours.
    • change the class name to be syntax_plugin_<your plugin name>9).
    • change the getType() method to report the mode type your plugin will belong to.
    • add a getAllowedTypes() method to report any mode types your plugin will allow to be nested within its own syntax. If your plugin won't allow any other mode then this can be left out.
    • change the getPType() method to report the PType that will apply for your plugin. If its 'normal' you can remove this method.
    • change the getSort() method to report a unique number after checking the list of plugins
    • alter the connectTo() method to register the pattern to match your syntax.
    • add a postConnect() method if your syntax has an second pattern to say when the parser is leaving your syntax mode.
  5. That's the easy part done, you now have a plugin that will say “Hello World!” when it encounters your syntax pattern. Time to test it and make sure the pattern works as expected - visit your wiki and make up a page with the syntax for your plugin, save it and make sure “Hello World!” shows up.
  6. Write your handle() & render() methods.
    • if you have entry and exit patterns remember to handle the unmatched data.
    • treat raw wiki data with suspicion and remember to ensure all special characters go to an entity converter.
  7. Add a plugin.info.txt file in your plugin directory (see for example, the sample plugin below)
  8. Test and post your completed plugin on the DokuWiki plugin page.

Sample Plugin 1 - Now

When its syntax, [NOW], is encountered in a wiki page the current date and time will be inserted in RFC2822 format.

And that's our plugin finished.

syntax.php
<?php
/**
 * Plugin Now: Inserts a timestamp.
 * 
 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
 * @author     Christopher Smith <chris@jalakai.co.uk>
 */
 
// must be run within DokuWiki
if(!defined('DOKU_INC')) die();
 
if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
require_once DOKU_PLUGIN.'syntax.php';
 
/**
 * All DokuWiki plugins to extend the parser/rendering mechanism
 * need to inherit from this class
 */
class syntax_plugin_now extends DokuWiki_Syntax_Plugin {
 
    function getInfo() {
        return array('author' => 'me',
                     'email'  => 'me@someplace.com',
                     'date'   => '2005-07-28',
                     'name'   => 'Now Plugin',
                     'desc'   => 'Include the current date and time',
                     'url'    => 'http://www.dokuwiki.org/plugin:tutorial');
    }
 
    function getType() { return 'substition'; }
    function getSort() { return 32; }
 
    function connectTo($mode) {
        $this->Lexer->addSpecialPattern('\[NOW\]',$mode,'plugin_now');
    }
 
    function handle($match, $state, $pos, &$handler) {
        return array($match, $state, $pos);
    }
 
    function render($mode, &$renderer, $data) {
        if($mode == 'xhtml'){
            $renderer->doc .= date('r');
            return true;
        }
        return false;
    }
}

You also need the plugin.info.txt file:

plugin.info.txt
base now
author me
email me@someplace.com
date 2005-07-28
name Now Plugin
desc Include the current date and time
url http://www.dokuwiki.org/plugin:tutorial

Note: due to the way DokuWiki caches pages this plugin will report the date/time at which the cached version was created. You would need to add ~~NOCACHE~~ to the page to ensure the date was current every time the page was requested.

Sample Plugin 2 - Color

When its syntax, <color somecolour/somebackgroundcolour>, is encountered in a wiki page the text colour will be changed to somecolour, the background will be changed to somebackgroundcolour and both will remain that way until </color> is encountered.

Again, all fairly straightforward - and here it is.

(the getInfo() method was needed with previous version of Dokuwiki. It is now no longer needed, and a plugin.info.txt file should be used instead. See plugin_info for more information).

syntax.php
<?php
/**
 * Plugin Color: Sets new colors for text and background.
 * 
 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
 * @author     Christopher Smith <chris@jalakai.co.uk>
 */
 
// must be run within Dokuwiki
if(!defined('DOKU_INC')) die();
 
if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
require_once(DOKU_PLUGIN.'syntax.php');
 
/**
 * All DokuWiki plugins to extend the parser/rendering mechanism
 * need to inherit from this class
 */
class syntax_plugin_color extends DokuWiki_Syntax_Plugin {
 
    /**
     * return some info
     */
    function getInfo(){
        return array(
            'author' => 'Christopher Smith',
            'email'  => 'chris@jalakai.co.uk',
            'date'   => '2008-02-06',
            'name'   => 'Color Plugin',
            'desc'   => 'Changes text colour and background',
            'url'    => 'http://www.dokuwiki.org/plugin:tutorial',
        );
    }
 
    function getType(){ return 'formatting'; }
    function getAllowedTypes() { return array('formatting', 'substition', 'disabled'); }   
    function getSort(){ return 158; }
    function connectTo($mode) { $this->Lexer->addEntryPattern('<color.*?>(?=.*?</color>)',$mode,'plugin_color'); }
    function postConnect() { $this->Lexer->addExitPattern('</color>','plugin_color'); }
 
 
    /**
     * Handle the match
     */
    function handle($match, $state, $pos, &$handler){
        switch ($state) {
          case DOKU_LEXER_ENTER :
                list($color, $background) = preg_split("/\//u", substr($match, 6, -1), 2);
                if ($color = $this->_isValid($color)) $color = "color:$color;";
                if ($background = $this->_isValid($background)) $background = "background-color:$background;";
                return array($state, array($color, $background));
 
          case DOKU_LEXER_UNMATCHED :  return array($state, $match);
          case DOKU_LEXER_EXIT :       return array($state, '');
        }
        return array();
    }
 
    /**
     * Create output
     */
    function render($mode, &$renderer, $data) {
        if($mode == 'xhtml'){
            list($state,$match) = $data;
            switch ($state) {
              case DOKU_LEXER_ENTER :      
                list($color, $background) = $match;
                $renderer->doc .= "<span style='$color $background'>"; 
                break;
 
              case DOKU_LEXER_UNMATCHED :  $renderer->doc .= $renderer->_xmlEntities($match); break;
              case DOKU_LEXER_EXIT :       $renderer->doc .= "</span>"; break;
            }
            return true;
        }
        return false;
    }
 
    // validate color value $c
    // this is cut price validation - only to ensure the basic format is correct and there is nothing harmful
    // three basic formats  "colorname", "#fff[fff]", "rgb(255[%],255[%],255[%])"
    function _isValid($c) {
        $c = trim($c);
 
        $pattern = "/^\s*(
            ([a-zA-z]+)|                                #colorname - not verified
            (\#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}))|        #colorvalue
            (rgb\(([0-9]{1,3}%?,){2}[0-9]{1,3}%?\))     #rgb triplet
            )\s*$/x";
 
        if (preg_match($pattern, $c)) return trim($c);
 
        return "";
    }
}
?>

Note: No checking is done to ensure colour names are valid or RGB values are within correct ranges.

How To Develop Syntax Plugin Step by Step

http://rkpisanu.altervista.org/doku.php?id=dokuwiki:devel:plugins:howto

1) defined in lib/plugins/syntax.php
2) , 4) defined in inc/parser/parser.php
3) See Doku_Handler_Block
5) Yes this is spelled wrong, but we won't change it to avoid breaking existing plugins. Sometimes a typo becomes a standard - see the HTTP “referer” header for an example
6) the part of the parser which analyses the raw wiki page
7) perl compatible regular expressions
ref: www.php.net/manual/en/ref.pcre.php
8) There is also the special mode metadata that doesn't output anything but collects metadata for the page. Use it to insert values into the metadata array. See the translation plugin for an example.
9) The name may not contain underscores and needs to match your class name