DokuWiki

It's better when it's simple

User Tools

Site Tools


devel:syntax_plugins

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revision Previous revision
Next revision
Previous revision
devel:syntax_plugins [2009-11-25 20:05]
204.58.246.49 replaced text intended to display an image with FIXME tag
devel:syntax_plugins [2017-07-25 12:10] (current)
109.218.95.146 [Sort Number] belonging two => belonging to two
Line 1: Line 1:
-====== Syntax Plugins ====== +====== 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.+
  
 +[[plugintype>​1#​extension__table|Syntax Plugins]] are [[:​devel:​plugins]] to extend DokuWiki'​s [[wiki:​syntax]]. To be able to understand what is needed to register new Syntax within DokuWiki you should read how the [[Parser]] works.
 ===== Synopsis ===== ===== Synopsis =====
  
-A Syntax Plugin //example// needs to define a class named ''​syntax_plugin_example''​ which extends ​''​DokuWiki_Syntax_Plugin''​((defined in ''​lib/​plugins/​syntax.php''​)). ​The class needs to be stored in a file called ​''​lib/​plugins/​example/​syntax.php''​. For full details of plugins and their files refer to [[plugin file structure]].+A Syntax Plugin //Example// needs
 +  * class name  ​''​syntax_plugin_example''​ 
 +  * which extends ​[[xref>DokuWiki_Syntax_Plugin]]((defined in ''​lib/​plugins/​syntax.php''​)). ​ 
 +  * to be stored in a file ''​lib/​plugins/​example/​syntax.php''​
 +Moreover, a [[plugin_info|plugin.info.txt]] file is needed. For full details of plugins and their files and how to create more syntax components ​refer to [[plugin file structure]].
  
 The class needs to implement at least the following functions: ​ The class needs to implement at least the following functions: ​
  
-  * **''​getInfo()''​** Return a Hash with plugin info [author, email, date, name, desc, url] 
   * **''​getType()''​** Should return the type of syntax this plugin defines ([[#​syntax_types|see below]])   * **''​getType()''​** Should return the type of syntax this plugin defines ([[#​syntax_types|see below]])
   * **''​getSort()''​** Returns a number used to determine in which order modes are added, also see [[parser#​order_of_adding_modes_important|parser,​ order of adding modes]] and [[devel:​parser:​getSort list]].   * **''​getSort()''​** Returns a number used to determine in which order modes are added, also see [[parser#​order_of_adding_modes_important|parser,​ order of adding modes]] and [[devel:​parser:​getSort list]].
   * **''​connectTo($mode)''​** This function is inherited from Doku_Parser_Mode ((defined in ''​inc/​parser/​parser.php''​)). Here is the place to register the regular expressions needed to match your syntax.   * **''​connectTo($mode)''​** This function is inherited from Doku_Parser_Mode ((defined in ''​inc/​parser/​parser.php''​)). Here is the place to register the regular expressions needed to match your syntax.
-  * **''​handle($match,​ $state, $pos, &$handler)''​** to prepare the matched syntax for use in the renderer +  * **''​handle($match,​ $state, $pos, Doku_Handler ​$handler)''​** to prepare the matched syntax for use in the renderer 
-  * **''​render($mode, ​&$renderer, $data)''​** to render the content+  * **''​render($mode, ​Doku_Renderer ​$renderer, $data)''​** to render the content
 \\  \\ 
  
Line 22: Line 22:
  
   * **''​getPType()''​** Defines how this syntax is handled regarding paragraphs((See ''​Doku_Handler_Block''​)). Return:   * **''​getPType()''​** Defines how this syntax is handled regarding paragraphs((See ''​Doku_Handler_Block''​)). Return:
-    *  ''​normal''​ --- (default value, will be used if the method is not overridden) The plugin ​can be used inside ​paragraphs+    * ''​normal''​ --- (default value, will be used if the method is not overridden) The plugin ​output will be inside ​a paragraph (or another block element)no paragraphs will be inside 
-    * ''​block''​ --- Open paragraphs ​need to be closed before plugin output ​or +    * ''​block''​ --- Open paragraphs ​will be closed before plugin output, the plugin output will not start with a paragraph 
-    * ''​stack''​ --- Special case. Plugin ​wraps other paragraphs+    * ''​stack''​ --- Open paragraphs will be closed before plugin output, the plugin output ​wraps other paragraphs
   * **''​getAllowedTypes()''​** (default value: ''​array()''​) Should return an array of [[#​syntax_types|mode types]] that may be nested within the plugin'​s own markup.   * **''​getAllowedTypes()''​** (default value: ''​array()''​) Should return an array of [[#​syntax_types|mode types]] that may be nested within the plugin'​s own markup.
-  * **''​accepts($mode)''​** This function is used to tell the parser if the plugin accepts syntax mode $mode within its own markup. ​ The default behaviour is to test $mode against the array of modes held by the inherited property ''​allowedModes''​.+  * **''​accepts($mode)''​** This function is used to tell the parser if the plugin accepts syntax mode $mode within its own markup. The default behaviour is to test $mode against the array of modes held by the inherited property ''​allowedModes''​. This array is also filled with modes from the mode types given in ''​getAllowedTypes()''​.
  
 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. 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.
Line 34: Line 34:
  
   * **''​allowedModes''​** --- initial value, an empty array, inherited from Doku_Parser_Mode ((defined in ''​inc/​parser/​parser.php''​)). Contains a list of other syntax modes which are allowed to occur within the plugin'​s own syntax mode (ie. the modes which belong to any other DokuWiki markup that can be nested inside the plugin'​s own markup). ​ Normally, it is automatically populated by the ''​accepts()''​ function using the results of ''​getAllowedTypes()''​.   * **''​allowedModes''​** --- initial value, an empty array, inherited from Doku_Parser_Mode ((defined in ''​inc/​parser/​parser.php''​)). Contains a list of other syntax modes which are allowed to occur within the plugin'​s own syntax mode (ie. the modes which belong to any other DokuWiki markup that can be nested inside the plugin'​s own markup). ​ Normally, it is automatically populated by the ''​accepts()''​ function using the results of ''​getAllowedTypes()''​.
 +
 +Inherited Functions
 +
 +  * See [[common plugin functions]] for inherited functions available to all plugins. e.g. localisation,​ configuration and introspection.
  
 ===== Syntax Types ===== ===== Syntax Types =====
Line 39: Line 43:
 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: 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 ^ +Modetype ​                                                                                                                                                                               ​^ Used in mode...                                                                                                                                                                                                        ^ 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 | +| 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 | +| 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 | +| formatting ​                                                                                                                                                                             | strong, emphasis, underline, monospace, subscript, superscript,​ deleted, footnote ​                                                                                                                                     | modes for styling text -- footnote behaves similar to styling ​                                                                                               
-| substition((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))| '​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 |  +| substition((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)) ​ | '​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 | +| 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 | +| 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 |+| paragraphs ​                                                                                                                                                                             | eol((This is actually a class, it does not mean "end of life", but "end of line"​.)) ​                                                                                                                                   ​| 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''​.+For a description what each type means and which other formatting classes are registered in them read the comments in ''​[[xref>inc/​parser/​parser.php]]''​.
  
 ====== Tutorial: Syntax Plugins Explained ====== ====== Tutorial: Syntax Plugins Explained ======
  
-The goal of this tutorial is to explain the concepts involved in a [[:DokuWiki]] [[devel:​Syntax Plugins|syntax plugin]] and to go through the steps involved in writing your own plugin.+The goal of this tutorial is to explain the concepts involved in a DokuWiki [[devel:​Syntax Plugins|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 [[devel:​syntax_plugin_skeleton|syntax plugin skeleton]]. ​ It's a bare bones plugin which outputs "//​Hello World!//"​ when it encounters "''​%%<​TEST>​%%''"​ on a wiki page. For those who are really impatient to get started, grab a copy of the [[devel:​syntax_plugin_skeleton|syntax plugin skeleton]]. ​ It's a bare bones plugin which outputs "//​Hello World!//"​ when it encounters "''​%%<​TEST>​%%''"​ on a wiki page.
Line 73: Line 77:
   * The ''​[[#​render_method|render()]]''​ method processes the renderer instructions that apply to the plugin'​s syntax mode - and which were created by the plugin'​s ''​[[#​handle_method|handle()]]''​ method.   * The ''​[[#​render_method|render()]]''​ method processes the renderer instructions that apply to the plugin'​s syntax mode - and which were created by the plugin'​s ''​[[#​handle_method|handle()]]''​ method.
   * add content to the output document with ''​%%$renderer->​doc .= '​content';​%%''​   * add content to the output document with ''​%%$renderer->​doc .= '​content';​%%''​
 +  * access the return value of handle() using the //$data// parameter of render($mode,​ Doku_Renderer $renderer, $data).
   * ensure any content output by the plugin is **safe** - run raw wiki data through an entity conversion function. ​   * ensure any content output by the plugin is **safe** - run raw wiki data through an entity conversion function. ​
   * do the minimum possible processing and decision making here, it should all have been done in the ''​[[#​handle_method|handle()]]''​ method.   * do the minimum possible processing and decision making here, it should all have been done in the ''​[[#​handle_method|handle()]]''​ method.
Line 105: Line 110:
 These are the other modes that can occur nested within the current mode's own markup. 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 ​its 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.+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 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. +:!: Your plugin tells the parser which other syntax modes it permits by reporting the mode types it allows via the ''​getAllowedTypes()''​ method. ​
 ==== PType ==== ==== PType ====
  
 PType governs how the parser handles html %%<​p>​%% elements when dealing with your syntax mode. 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'​%%''​, or whether it should leave the paragraphs alone, ​''​%%PType='​normal'​%%''​.  There is a third option, ''​%%PType='​stack'​%%''​, which I don't fully understand so I'll leave that for now (FIXME).+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'​%%''​.
  
-For those that know CSS, returning ​''​%%PType='​block'​%%'' ​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''​.+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.
  
-<​del>​There is one gotcha with ''​%%PType='​block'​%%''​. If your plugin allows other syntax modes, the parser will generate ​%%</​p>​%% & %%<p>%% tags when entering ​and exiting any nested syntax modes. ​ If that causes problems, choose ​''​%%PType='​normal'​%%'' ​and start the HTML your render method generates with a ''<​/p>''​ and finish it with a ''<​p>''​.</​del--- [corrected in DW2006-11-06 version ​(and preceding release candidates)]+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 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 textThat means your plugin doesn'​t need to take care of those tags. 
 + 
 +^wikisyntax ^PType=normal ^PType=block ^PType=stack ^ 
 +|<​code>​ 
 +foo 
 +<​plugin>​text</plugin>​ 
 + 
 +bar</​code>​ |<code html> 
 +<p>foo 
 +ENTRY("<​plugin>"​) 
 +UNMATCHED("​text"​) 
 +EXIT("</​plugin>"​) 
 +</​p>​ 
 +<​p>​bar</​p>​ 
 +</​code>​ | <code html> 
 +<​p>​foo</​p>​ 
 +ENTRY("<​plugin>"​) 
 +UNMATCHED("​text"​) 
 +EXIT("</​plugin>"​) 
 +<​p>​bar</​p></​code>​ | <code html> 
 +<​p>​foo</​p>​ 
 +ENTRY("<​plugin>"​) 
 +<p> 
 +UNMATCHED("​text"​) 
 +</​p>​ 
 +EXIT("</​plugin>"​) 
 +<​p>​bar</​p>​ 
 +</​code>​ |
  
 ==== Sort Number ==== ==== Sort Number ====
  
-This number is used by the lexer((the part of the parser which analyses the raw wiki page)) 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.+This number is used by the lexer((the part of the parser which analyses the raw wiki page)) to control the order it tests the syntax mode patterns against raw wiki data.  It is only important if the patterns belonging ​to 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 [[plugin:​code]] plugin. 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 [[plugin:​code]] plugin.
Line 139: Line 174:
   * be very wary of look behind assertions. The parser only attempts to match patterns on the next piece of "not yet matched"​ data.  If you need to look behind to characters that have been involved in a previous pattern match, those characters will never be there.   * be very wary of look behind assertions. The parser only attempts to match patterns on the next piece of "not yet matched"​ data.  If you need to look behind to characters that have been involved in a previous pattern match, those characters will never be there.
   * option flags can only be included as inline options, e.g. ''​(?​i)'',​ ''​(?​-i)''​   * option flags can only be included as inline options, e.g. ''​(?​i)'',​ ''​(?​-i)''​
 +  * back references do not work, e.g. "''​(\w)\1\w+''"​ (finding a word with a doubled first characters),​ due to the way the lexer functions internally.
 +
 \\ \\
  
Line 155: Line 192:
   * use non-greedy quantifiers,​ e.g. ''​+?''​ or ''​*?''​ instead of ''​+''​ or ''​*''​.   * use non-greedy quantifiers,​ e.g. ''​+?''​ or ''​*?''​ instead of ''​+''​ or ''​*''​.
   * be wary of using multiple exit patterns. The first exit pattern encountered will most likely trigger the parser to exit your syntax mode - even if that wasn't the pattern the entry pattern looked ahead for. Needing multiple exit patterns probably indicates a need for multiple plugins.   * be wary of using multiple exit patterns. The first exit pattern encountered will most likely trigger the parser to exit your syntax mode - even if that wasn't the pattern the entry pattern looked ahead for. Needing multiple exit patterns probably indicates a need for multiple plugins.
 +  * In the [[devel:​plugin_survey:​compatibility#​syntax|Plugin Survey]] of 2011 it looks like a majority of special patterns are either ''​%%{{…}}%%''​ (160 cases) or ''​%%~~…~~%%''​ (80 cases). A very common entry/exit pattern (231 plugins) is something like an XML tag even if some use upper case letters.
   * early versions of the DokuWiki lexer had a bug which prevented use of "<"​ or ">"​ in look ahead patterns. ​ This bug has been fixed and angle brackets can now be used.  Some plugins will still contain the hex codes for angle brackets ("​\x3C",​ "​\x3E"​) which was the workaround to overcome the effects of this bug.   * early versions of the DokuWiki lexer had a bug which prevented use of "<"​ or ">"​ in look ahead patterns. ​ This bug has been fixed and angle brackets can now be used.  Some plugins will still contain the hex codes for angle brackets ("​\x3C",​ "​\x3E"​) which was the workaround to overcome the effects of this bug.
 +  * Use this for a example of correct regular expression: [[devel:​plugin_programming_tips#​use_correct_regular_expressions|Use correct regular expressions]]
 ==== handle() method ==== ==== 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()''​. 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()''​.
 +
 +The complete signature is: ''​public handle($match,​ $state, $pos, Doku_Handler $handler)''​ with the arguments:
  
 **$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. **$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.
Line 171: Line 212:
 **$pos** parameter --- The character position of the matched text. **$pos** parameter --- The character position of the matched text.
  
-**&$handler** parameter --- Object Reference to the [[devel:​parser#​handler|Doku_Handler]] object.+**$handler** parameter --- Object Reference to the [[devel:​parser#​handler|Doku_Handler]] object
 + 
 +**return** --- The instructions for the ''​render()''​ method. These instructions are cached. The return value can be everything you require for your needs. Often, it is an array in which the different values are collected that are founded or determined in handle() and which are useful in ''​render()''​.
  
 ==== render() method ==== ==== 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.+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 - e.g. by concatenating its output to the renderer'​s ''​doc''​ property.
  
-<​code>​+<​code ​php>
 $renderer->​doc .= "some plugin output...";​ $renderer->​doc .= "some plugin output...";​
 </​code>​ </​code>​
  
-:!: Any raw wiki data that passes through ''​render()''​ should have all special characters converted to HTML entities. You can use the PHP functions, ''​[[http://​uk.php.net/​manual/​en/​function.htmlspecialchars.php|htmlspecialchars()]]'',​ ''​[[http://​uk.php.net/​manual/​en/​function.htmlentities.php|htmlentities()]]''​ or the renderer'​s own ''​xmlEntities()''​ method. e.g.<​code>​$renderer->​doc .= $renderer->​_xmlEntities($text);</​code>  ​+:!: Any raw wiki data that passes through ''​render()''​ should have all special characters converted to HTML entities. You can use the PHP functions, ''​[[http://​uk.php.net/​manual/​en/​function.htmlspecialchars.php|htmlspecialchars()]]'',​ ''​[[http://​uk.php.net/​manual/​en/​function.htmlentities.php|htmlentities()]]''​ or the renderer'​s own ''​xmlEntities()''​ method. e.g.<​code ​php>​$renderer->​doc .= $renderer->​_xmlEntities($text);</​code>  ​
  
-**$mode** parameter --- Name for the format mode of the final output produced by the renderer. ​ At present DokuWiki only supports one output format ​''​XHTML''​ ((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.)). New modes can be introduced by [[devel:​renderer plugins]]. ​ The plugin should only produce output for those formats which it supports - which means this function should be structured ... +The complete signature is: ''​public render($mode,​ Doku_Renderer $renderer, $data)''​ with the arguments:​ 
-<​code>​+ 
 +**$mode** parameter --- Name for the format mode of the final output produced by the renderer. ​ At present DokuWiki only supports one output format ''​XHTML'' ​and a special ​(internal) format ''​metadata'' ​((The special mode ''​metadata'' ​does not output anything but collects metadata for the page. Use it to insert values into the metadata array. See the translation plugin for an example.)). New modes can be introduced by [[devel:​renderer plugins]]. ​ The plugin should only produce output for those formats which it supports - which means this function should be structured ... 
 +<​code ​php>
 if ($mode == '​xhtml'​) {  // supported mode if ($mode == '​xhtml'​) {  // supported mode
   // code to generate XHTML output from instruction $data   // code to generate XHTML output from instruction $data
Line 190: Line 235:
 </​code>​ </​code>​
  
-**$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.+**$renderer** parameter --- Give access to the object [[xref>​Doku_Renderer]],​ which contains useful functions and values. Above you saw already ​the usage of ''​$renderer%%->​%%doc'' ​for storing ​the render ​output.
  
-==== Safety & Security ====+**$data** parameter --- An array containing the instructions previously prepared and returned by the plugin'​s own ''​handle()''​ method. ​ The ''​render()''​ must interpret the instruction and generate the appropriate output.
  
-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 entitiesAlso any wiki data extracted and used internally should be treated with suspicionSee also [[devel:​security]].+===XHTML renderer === 
 +When your plugin ​needs to extend ​the content of a wiki page, you need the output ​format mode ''​xhtml''​. Because ''​render()''​ is called for all the format modes, you need to filter by the desired modes. 
 +<code php> 
 +if ($mode == '​xhtml'​) {  // when the format mode is xhtml 
 +    /** @var Doku_Renderer_xhtml $renderer */    
 +    // code to generate XHTML output from instruction $data 
 +    $renderer->​doc ​.= '<​div>​Adds your div</​div>'; ​  
 +
 +</​code>​ 
 +Detail: the variable ''​$renderer''​ is now the [[xref>​Doku_Renderer_xhtml]] object.
  
-==== Localization ====+=== Metadata renderer ​=== 
 +A special render mode ''​metadata''​ is for rendering metadata. [[Metadata]] are the extra properties kept for your wiki page, which you can also extend or modify in your plugin.
  
-FIXME+In the metadata rendering mode you 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:
  
-For now refer to [[devel:​common plugin functions#​localisation]] [[devel:​plugin file structure]]+<code php> 
 +public function render($mode,​ Doku_Renderer $renderer, $data) { 
 +    if($mode == '​xhtml'​) { 
 +        /** @var Doku_Renderer_xhtml $renderer */ 
 +        // this is where you put all the rendering that will be displayed in the  
 +        // web browser 
 + return true; 
 +    } 
 +    if($mode == '​metadata'​) { 
 +        /** @var Doku_Renderer_metadata $renderer */ 
 +        $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; 
 +
 +</​code>​ 
 +This example uses the [[xref>​internallink]] function from ''​inc/​parser/​metadata.php''​. You can also access the metadata directly in the renderer with ''​%%$renderer->​meta%%''​ and ''​%%$renderer->​persistent%%'',​ because ''​$renderer''​ is now the [[xref>​Doku_Renderer_metadata]] object. Here is a snippet from the tag plugin: 
 +<code php> 
 +public function render($mode,​ Doku_Renderer $renderer, $data) { 
 +    if ($data === false) return false;
  
-==== Configuration ===+    // XHTML output 
-Please refer to [[configuration]].+    if ($mode ​== '​xhtml'​) { 
 +        /** @var Doku_Renderer_xhtml $renderer */ 
 +        ... 
 +     
 +    // for metadata renderer 
 +    } elseif ($mode ​== '​metadata'​) { 
 +        /** @var Doku_Renderer_metadata $renderer */ 
 +        // erase tags on persistent metadata no more used 
 +        if (isset($renderer->​persistent['​subject'​])) { 
 +            unset($renderer->​persistent['​subject'​]);​ 
 +            $renderer->​meta['​subject'​] ​array(); 
 +        } 
 +         
 +        // merge with previous tags and make the values unique 
 +        if (!isset($renderer->​meta['​subject'​])) $renderer->​meta['​subject'​= array(); 
 +        $renderer->​meta['​subject'​= array_unique(array_merge($renderer->​meta['​subject'​],​ $data)); 
 +         
 +        // create raw text summary for the page abstract 
 +        if ($renderer->​capture) $renderer->​doc .= DOKU_LF.implode('​ ', $data).DOKU_LF;
  
 +        ...
 +        return true;
 +    }
 +    return false;
 +}
 +</​code>​
 +First it handles old persistent metadata no longer used by this plugin. This persistent metadata is always kept, thus when you change your mind and use current metadata instead, you need to remove it explicitly. ​
  
-==== Using Styles and JavaScript ====+When handling persistent data in the metadata renderer, take care you update also the current metadata, when you update persistent metadata. ​
  
-FIXME+The tag plugin stores here '​subject'​ data by ''​$renderer%%->​%%meta['​subject'​] = ...''​. Be aware that when you use ''​p_set_metadata''​ to set current metadata somewhere, that the next time the metadata is rendered it will overwrite this data. Using ''​p_get_metadata($ID,​ $key)''​ gives access to stored metadata. For details see [[devel:​metadata]].
  
-For now refer to [[devel:​plugin ​file structure]]+When some raw text from your syntax should be included in the abstract you can append it to ''​$renderer%%->​%%doc''​. When the abstract is long enough, ''​$renderer%%->​%%capture''​ becomes false. 
 + 
 +The xhtml mode is called when DokuWiki is in need of a new xhtml version of the wikipage. The metadata is a bit different. In general, the metadata of the page is rendered on demand when ''​p_get_metadata''​ is called somewhere.  
 + 
 +When someone edit a page and use the preview function, the metadata renderer is not called. So the metadata is not yet updated! This is done when the page is saved. 
 + 
 +===== 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 [[devel:security]]. 
 + 
 +===== Common ​plugin ​functions ===== 
 +Some function are shared between the plugins, refer to next sections for info: 
 +  * [[devel:​common_plugin_functions#​Configuration|Plugin configuration settings]]  
 +  * [[devel:​common_plugin_functions#​localisation|Localisation]] 
 +  * [[devel:​common_plugin_functions#​styles and javascript|Using styles and javascript]] 
  
  
Line 217: Line 332:
 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. 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, [[devel:action_plugins#sample_action_plugin_2]]. Also refer to [[devel:toolbar]].+  * The [[devel:toolbar#extending_the_toolbar|toolbar]] page explains how you can extend by PHP or javascript. 
 +  * Another ​[[devel:action_plugins#​Sample Action plugin 2|example]] is available at the Action Plugin page
  
 ===== Writing Your Own Plugin ===== ===== Writing Your Own Plugin =====
Line 228: Line 344:
   - Edit that file to make it yours.   - Edit that file to make it yours.
     * change the class name to be ''​syntax_plugin_<​your plugin name>''​((The name may not contain underscores and needs to match your class name)).     * change the class name to be ''​syntax_plugin_<​your plugin name>''​((The name may not contain underscores and needs to match your class name)).
-    * change the ''​getInfo()''​ to report information about your plugin. 
     * change the ''​getType()''​ method to report the mode type your plugin will belong to.     * 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.     * 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 ''​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]]+    * change the ''​getSort()''​ method to report a unique number after checking the [[:devel:​parser:​getsort_list|getsorted list]] and 
     * alter the ''​connectTo()''​ method to register the pattern to match your syntax.     * 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.     * add a ''​postConnect()''​ method if your syntax has an second pattern to say when the parser is leaving your syntax mode.
Line 239: Line 354:
     * if you have entry and exit patterns remember to handle the unmatched data.     * 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.     * treat raw wiki data with suspicion and remember to ensure all special characters go to an entity converter.
 +  - Add a [[plugin_info|plugin.info.txt]] file in your plugin directory (see for example, the sample plugin below)
   - Test and post your completed plugin on the DokuWiki [[:​plugins|plugin page]].   - Test and post your completed plugin on the DokuWiki [[:​plugins|plugin page]].
 +
 +===== Read also=====
 +
 +  * The [[http://​pluginwizard.dokuwiki.org/​|Plugin Wizard]] can create a basic skeleton.
 +  * [[Plugin file structure]]
 +  * [[Common plugin functions]]
 +  * [[Plugin programming tips]]
 +  * [[plugins|Plugin Development]]
  
 ===== Sample Plugin 1 - Now ===== ===== Sample Plugin 1 - Now =====
Line 265: Line 389:
 // must be run within DokuWiki // must be run within DokuWiki
 if(!defined('​DOKU_INC'​)) die(); if(!defined('​DOKU_INC'​)) die();
- 
-if(!defined('​DOKU_PLUGIN'​)) define('​DOKU_PLUGIN',​DOKU_INC.'​lib/​plugins/'​);​ 
-require_once DOKU_PLUGIN.'​syntax.php';​ 
  
 /** /**
Line 275: Line 396:
 class syntax_plugin_now extends DokuWiki_Syntax_Plugin { class syntax_plugin_now extends DokuWiki_Syntax_Plugin {
  
-    function ​getInfo() { +    ​public ​function ​getType() { return 'substition'; } 
-        ​return ​array('author' ​=> '​me',​ +    ​public function getSort({ return 32; }
-                     '​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';​ } +    ​public ​function connectTo($mode) {
-    function getSort() { return 32; } +
- +
-    ​function connectTo($mode) {+
         $this->​Lexer->​addSpecialPattern('​\[NOW\]',​$mode,'​plugin_now'​);​         $this->​Lexer->​addSpecialPattern('​\[NOW\]',​$mode,'​plugin_now'​);​
     }     }
  
-    function handle($match,​ $state, $pos, &$handler) {+    ​public ​function handle($match,​ $state, $pos, Doku_Handler ​$handler) {
         return array($match,​ $state, $pos);         return array($match,​ $state, $pos);
     }     }
  
-    function render($mode, ​&$renderer, $data) {+    ​public ​function render($mode, ​Doku_Renderer ​$renderer, $data) { 
 +    // $data is what the function handle return'​ed.
         if($mode == '​xhtml'​){         if($mode == '​xhtml'​){
 +            /** @var Doku_Renderer_xhtml $renderer */
             $renderer->​doc .= date('​r'​);​             $renderer->​doc .= date('​r'​);​
             return true;             return true;
Line 302: Line 416:
         return false;         return false;
     }     }
-}</​code>​+} 
 +</​code>​ 
 + 
 +You also need the plugin.info.txt file: 
 +<code txt 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/​devel:​syntax_plugins 
 +</​code>​
  
 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. 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.
Line 323: Line 449:
     * ''​DOKU_LEXER_EXIT'',​ close the span     * ''​DOKU_LEXER_EXIT'',​ close the span
  
-Againall fairly straightforward - and here it is. +Put the file syntax.php from below into a folder named "​color"​ directly below your plugins foldere.g. /​srv/​www/​htdocs/​dokuwiki/​lib/​plugins. If you do not name this folder "​color",​ the plugin will not work: 
- +<​code ​php syntax.php>
-<code php>+
 <?php <?php
 /** /**
Line 336: Line 461:
 // must be run within Dokuwiki // must be run within Dokuwiki
 if(!defined('​DOKU_INC'​)) die(); if(!defined('​DOKU_INC'​)) die();
- 
-if(!defined('​DOKU_PLUGIN'​)) define('​DOKU_PLUGIN',​DOKU_INC.'​lib/​plugins/'​);​ 
-require_once(DOKU_PLUGIN.'​syntax.php'​);​ 
  
 /** /**
Line 346: Line 468:
 class syntax_plugin_color extends DokuWiki_Syntax_Plugin { class syntax_plugin_color extends DokuWiki_Syntax_Plugin {
  
-    ​/** +    ​public ​function getType(){ return '​formatting';​ } 
-     * return some info +    ​public ​function getAllowedTypes() { return array('​formatting',​ '​substition',​ '​disabled'​);​ }    
-     */ +    ​public ​function getSort(){ return 158; } 
-    function getInfo(){ +    ​public ​function connectTo($mode) { $this->​Lexer->​addEntryPattern('<​color.*?>​(?​=.*?</​color>​)',​$mode,'​plugin_color'​);​ } 
-        return array( +    ​public ​function postConnect() { $this->​Lexer->​addExitPattern('</​color>','​plugin_color'​);​ }
-            '​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'​);​ }+
  
  
Line 370: Line 478:
      * Handle the match      * Handle the match
      */      */
-    function handle($match,​ $state, $pos, &$handler){+    ​public ​function handle($match,​ $state, $pos, Doku_Handler ​$handler){
         switch ($state) {         switch ($state) {
           case DOKU_LEXER_ENTER :           case DOKU_LEXER_ENTER :
Line 387: Line 495:
      * Create output      * Create output
      */      */
-    function render($mode, ​&$renderer, $data) {+    ​public ​function render($mode, ​Doku_Renderer ​$renderer, $data) { 
 +        // $data is what the function handle() return'​ed.
         if($mode == '​xhtml'​){         if($mode == '​xhtml'​){
 +            /** @var Doku_Renderer_xhtml $renderer */
             list($state,​$match) = $data;             list($state,​$match) = $data;
             switch ($state) {             switch ($state) {
-              ​case DOKU_LEXER_ENTER :       +                ​case DOKU_LEXER_ENTER :       
-                list($color,​ $background) = $match; +                    list($color,​ $background) = $match; 
-                $renderer->​doc .= "<​span style='​$color $background'>";​  +                    $renderer->​doc .= "<​span style='​$color $background'>";​  
-                break;+                    break;
                 ​                 ​
-              ​case DOKU_LEXER_UNMATCHED :  $renderer->​doc .= $renderer->​_xmlEntities($match);​ break; +                ​case DOKU_LEXER_UNMATCHED :  ​ 
-              case DOKU_LEXER_EXIT :       ​$renderer->​doc .= "</​span>";​ break;+                    ​$renderer->​doc .= $renderer->​_xmlEntities($match); ​ 
 +                    ​break; 
 +                case DOKU_LEXER_EXIT :        
 +                    ​$renderer->​doc .= "</​span>"; ​ 
 +                    ​break;
             }             }
             return true;             return true;
Line 404: Line 518:
     }     }
     ​     ​
-    // validate ​color value $c +    /** 
-    // ​this is cut price validation - only to ensure the basic format is correct and there is nothing harmful +     * Validate ​color value $c 
-    // ​three basic formats ​ "​colorname",​ "#​fff[fff]",​ "​rgb(255[%],​255[%],​255[%])"​ +     * this is cut price validation - only to ensure the basic format is correct and there is nothing harmful 
-    function _isValid($c) {+     * three basic formats ​ "​colorname",​ "#​fff[fff]",​ "​rgb(255[%],​255[%],​255[%])"​ 
 +     */ 
 +    ​private ​function _isValid($c) {
         $c = trim($c);         $c = trim($c);
         ​         ​
Line 421: Line 537:
     }     }
 } }
-?> 
 </​code>​ </​code>​
  
 Note: No checking is done to ensure colour names are valid or RGB values are within correct ranges. Note: No checking is done to ensure colour names are valid or RGB values are within correct ranges.
 +
 +===== Unit Testing Syntax Plugins =====
 +
 +For a general introduction about Unit Testing in DokuWiki please see [[devel:​unittesting]]. For a syntax plugin a common test goal will be to ensure that a certain wiki code produces the expected XHTML code or other destination language code.
 +
 +The following example function shows a simple way to do this:
 +
 +<code php>
 +    public function test_superscript() {
 +        $info = array();
 +        $expected = "​\n<​p>​\nThis is <​sup>​superscripted</​sup>​ text.<br />​\n</​p>​\n";​
 +
 +        $instructions = p_get_instructions('​This is ^^superscripted^^ text.'​);​
 +        $xhtml = p_render('​xhtml',​ $instructions,​ $info);
 +
 +        $this->​assertEquals($expected,​ $xhtml);
 +    }
 +</​code>​
 +
 +Here we strongly benefit from DokuWiki'​s good design. The two function calls to ''​p_get_instructions()''​ and ''​p_render()''​ are enough to render the example code //'​This is ^^superscripted^^ text.'//​ and store the result in the variable **$xhtml**. Finally we only need a simple assert to check if the result is what we **$expected**.
 +
devel/syntax_plugins.1259175914.txt.gz · Last modified: 2009-11-25 20:05 by 204.58.246.49