DokuWiki

It's better when it's simple

User Tools

Site Tools


plugin:code2

Code Syntax PlugIn

Compatible with DokuWiki

2005-07-13+

plugin Syntax highlighting with optional line numbers

Last updated on
2008-07-22
Provides
Syntax, Render
Conflicts with
jquery-syntax

While writing several pages to document some source code I noticed that it would be helpful if there were line numbers added automatically when using the <code> tag to highlight the respective code fragments. Investigating the underlying GeSHi system1) showed that its line numbering feature was not available for use in DokuWiki2). — Hence I wrote this plugin, which replaces and enhances DokuWiki's builtin<code>” feature.

While I was at it I integrated the algorithms of my older Diff plugin as well3) – this way obsoleting the latter4). — And after some enlightening5) I implemented the option to specify an optional header/footer for a code section. — Some time later the Console mode, the inclusion of external resources and ODT support followed.

So the abstract markup looks like this:

<code lang 123 |[fh] text |[sh]>
{code to highlight}
</code>
  • lang” (if present) specifies the given code's (programming-) language;
  • 123” (if present) specifies the first number to use when numbering the given code's lines;
  • the first ”|” (if present) delimits the first argument(s) from the remaining ones which are used to produce a header or footer above/below the highlighted code;
  • a ”h” (for “header”)6) following immediately the pipe character causes the remaining ”text” to get placed above the code, a ”f” (for “footer”)7) places the ”text” below the code (note that this flag is optional; if omitted it defaults to ”f”);
  • the ”text” is used as-is with the additional benefit of getting wrapped by an anchor tag which allows for addressing the code from other pages.
  • the second ”|” (if present) delimits the header/footer text;
  • a ”s8) or ”h9) character (if present) controls the code block's initial folding state; for more details see the Behaviour section below.
  • the ”{code to highlight}” is either the plain text to process or a pointer to an external source.

Usage

Backward compatible with DokuWiki's builtin ”<code>markup this plugin provides additional (optional) arguments which can be used to both turning ON the new line numbering feature and specify the first number to use actually when numbering the lines.

As for ”diff” the optional second argument can be used to give a hint about the diff (unified, context, RCS or simple) format.

To use syntax highlighting you'll use the ”<code>” tag as you're used to do. Without giving a new argument the output will be just the same as with DokuWiki's built-in10).

  • <code>
    some text
    </code>
    (basic)
    just marks up ”some text” as preformatted text (w/o any special highlighting).
  • <code 7>
    some text
    </code>
    (extended)
    marks up ”some text” as preformatted text (w/o any special highlighting) with line numbers starting at number 7.
  • <code |A description>
    some text
    </code>
    (extended)
    marks up ”some text” as preformatted text (w/o any special highlighting) with a footer line of ”A description”.
  • <code html>
    some text
    </code>
    (basic)
    turns on HTML highlighting for ”some text”.
  • <code html 1>
    some text
    </code>
    (extended)
    turns on HTML highlighting for ”some text” with added line numbers starting with number 1.
  • <code html |h A description:>
    some text
    </code>
    (extended)
    turns on HTML highlighting for ”some text” and a header line of ”A description:”.
  • <code html 66 |A description>
    some text
    </code>
    (extended)
    turns on HTML highlighting for ”some text” with added line numbers starting with number 66 and a footer line of ”A description”.
  • <code diff>
    some text
    </code>
    (extended)
    turns on diff highlighting (autodetecting the patch format).
  • <code diff u>
    some text
    </code>
    (extended)
    turns on diff highlighting (assuming an unified patch format).
  • <code diff c |A description>
    some text
    </code>
    (extended)
    turns on diff highlighting (assuming a context patch format) and a footer line of ”A description”.
  • <code console 1|The shell output>
    some console commands capture
    </code>
    (extended)
    turns on console mode with a footer line of ”The shell output”.
  • <code html 1|t The example page:>
    extern> http://some.where.else.tld/page.html
    </code>
    (extended)
    turns on HTML highlighting for the externalhttp://some.where.else.tld/page.html” page's source with added line numbers starting with number 1 and a header line of ”The example page:”.

The entries marked (basic) are available with DokuWiki's builtin <code> markup as well, while those marked (extended) are features added by this plugin. So if you neither need nor want any of the latter you won't have to install this plugin but instead get yourself a cup of tea and watch the sunset.

And just for the records: The optional line number argument specifies the first number to use when numbering the lines but not the first code line to show.

Examples

Please note that you will not see any highlighting here: This section just shows some use cases without actually triggering the activation of the plugin – even if it was installed …

<code>
some text
and more
</code>

This just renders the given preformatted text without any special highlighting. However,

<code 1>
some text
and more
</code>

will add line numbers in front of each line starting (in this case) with 1 (one).

<code JavaScript 12|Listing 2>
var de = function() {
	return (typeof(window.de) == 'object') ? window.de : {};
}();
</code>

This markup will turn on both the JavaScript syntax highlighting and the line numbering starting in this case with number 12 and place the text ”Listing 2” below the code block – producing HTML like:

<div class="code">
  <pre class="code javascript">
  <span class="lno">12:</span>  <span class="kw2">var</span> de = <span class="kw2">function</span><span class="br0">&#40;</span><span class="br0">&#41;</span> <span class="br0">&#123;</span>
  <span class="lno">13:</span>      <span class="kw1">return</span> <span class="br0">&#40;</span><span class="kw1">typeof</span><span class="br0">&#40;</span>window.<span class="me1">de</span><span class="br0">&#41;</span> == <span class="st0">'object'</span><span class="br0">&#41;</span> ? window.<span class="me1">de</span> : <span class="br0">&#123;</span><span class="br0">&#125;</span>;
  <span class="lno">14:</span>  <span class="br0">&#125;</span><span class="br0">&#40;</span><span class="br0">&#41;</span>;
  </pre>
  <p class="codefoot"><a name="listing_2">Listing 2</a></p>
</div>

If you'd rather the text above the code you'd just insert a ”h” right after the pipe character i.e. ”<code javascript 12|h Listing 2>”.

Of course, instead of JavaScript you may use any other highlighting mode supported by GeSHi like ”html” for example or ”php”. In case a language is given which is not supported by GeSHi there won't be any syntax highlighting, of course, but the line numbers (if requested) and header/footer lines (if requested) will appear nevertheless. As mentioned above besides the line numbering feature this plugin provides an improved highlighting mode for ”diff” files (aka ”patches”).

For ”diff” the second argument can be either u (for ”unified”), c (for ”context”), n (or r for RCS) or s (for ”simple”) format11). Although appreciated this argument is optional as well. Omitting it will cause the plugin to perform some additional tests to figure out the ”diff” format actually used. For more information see the Examples section of the Diff plugin's doc.

To summarize:

  • Without the new arguments nothing changes from what you're used to know (and get) – but see the Notes section below.
  • Giving a numeric argument only results in preformatted text without highlighting but line numbering turned on.
  • Giving just a language argument turns on syntax highlighting w/o line numbers.
  • Giving a language and a numeric argument turns on syntax highlighting and line numbering starting with the given number.
  • Adding a ”|”, optionally followed by ”h” or ”f” and some text will result in a header or footer line attached to the code block (see Behaviour below).

Installation

It's quite easy to integrate this plugin with your DokuWiki:

  1. Download the source archive (~25KB) and unpack it in your DokuWiki plugin directory {dokuwiki}/lib/plugins (make sure, included subdirectories are unpacked correctly); this will create the directory {dokuwiki}/lib/plugins/code.
  2. Make sure both the new directory and the files therein are readable by the web-server e.g.
$> chown apache:apache dokuwiki/lib/plugins/* -Rc

You might as well use the plugin manager for installing or updating this plugin.

Plugin Source

Here comes the GPLed PHP source12) for those who'd like to scan before actually installing it:

<?php
if (! class_exists('syntax_plugin_code')) {
  if (! defined('DOKU_PLUGIN')) {
    if (! defined('DOKU_INC')) {
      define('DOKU_INC',
        realpath(dirname(__FILE__) . '/../../') . '/');
    } // if
    define('DOKU_PLUGIN', DOKU_INC . 'lib/plugins/');
  } // if
  // Include parent class:
  require_once(DOKU_PLUGIN . 'syntax.php');
  // We're dealing with "GeSHi" here, hence include it:
  require_once(DOKU_INC . 'inc/geshi.php');
 
/**
 * <tt>syntax_plugin_code.php </tt>- A PHP4 class that implements the
 * <tt>DokuWiki</tt> plugin for <tt>highlighting</tt> code fragments.
 *
 * <p>
 * Usage:<br>
 * <tt>&#60;code [language startno |[fh] text |[hs]]&#62;...&#60;/code&#62;</tt>
 
 * </p><pre>
 *  Copyright (C) 2006, 2008  M.Watermann, D-10247 Berlin, FRG
 *      All rights reserved
 *    EMail : &lt;support@mwat.de&gt;
 * </pre><div class="disclaimer">
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either
 * <a href="http://www.gnu.org/licenses/gpl.html">version 3</a> of the
 * License, or (at your option) any later version.<br>
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License for more details.
 * </div>
 * @author <a href="mailto:support@mwat.de">Matthias Watermann</a>
 * @version <tt>$Id: syntax_plugin_code.php,v 1.29 2008/07/22 09:22:47 matthias Exp $</tt>
 * @since created 24-Dec-2006
 */
class syntax_plugin_code extends DokuWiki_Syntax_Plugin {
 
  /**
   * @privatesection
   */
  //@{
 
  /**
   * Additional markup used with older DokuWiki installations.
   *
   * @private
   * @see _fixJS()
   */
  var $_JSmarkup = FALSE;
 
  /**
   * Indention "text" used by <tt>_addLines()</tt>.
   *
   * <p>
   * Note that we're using raw <em>UTF-8 NonBreakable Spaces</em> here.
   * </p>
   * @private
   * @see _addLines()
   */
  var $_lead = array('', ' ', '  ', '   ', '    ',
    '     ', '      ', '       ');
 
 
  /**
   * Section counter for ODT export
   *
   * @private
   * @see render()
   * @since created 08-Jun-2008
   */
  var $_odtSect = 0;
 
  /**
   * Prepare the markup to render the DIFF text.
   *
   * @param $aText String The DIFF text to markup.
   * @param $aFormat String The DIFF format used ("u", "c", "n|r", "s").
   * @param $aDoc String Reference to the current renderer's
   * <tt>doc</tt> property.
   * @return Boolean <tt>TRUE</tt>.
   * @private
   * @see render()
   */
  function _addDiff(&$aText, &$aFormat, &$aDoc) {
    // Since we're inside a PRE block we need the leading LFs:
    $ADD = "\n" . '<span class="diff_addedline">';
    $DEL = "\n" . '<span class="diff_deletedline">';
    $HEAD = "\n" . '<span class="diff_blockheader">';
    $CLOSE = '</span>';
    // Common headers for all formats;
    // the RegEx needs at least ")#" appended!
    $DiffHead = '#\n((?:diff\s[^\n]*)|(?:Index:\s[^\n]*)|(?:={60,})'
      . '|(?:RCS file:\s[^\n]*)|(?:retrieving revision [0-9][^\n]*)';
    switch ($aFormat) {
      case 'u':  // unified output
        $aDoc .= preg_replace(
          array($DiffHead . '|(?:@@[^\n]*))#',
            '|\n(\+[^\n]*)|',
            '|\n(\-[^\n]*)|'),
          array($HEAD . '\1' . $CLOSE,
            $ADD . '\1' . $CLOSE,
            $DEL . '\1' . $CLOSE),
          $aText);
        return TRUE;
      case 'c':  // context output
        $sections = preg_split('|(\n\*{5,})|',
          preg_replace($DiffHead . ')#',
            $HEAD . '\1' . $CLOSE,
            $aText),
          -1, PREG_SPLIT_DELIM_CAPTURE);
        $sections[0] = preg_replace(
          array('|\n(\-{3}[^\n]*)|',
            '|\n(\*{3}[^\n]*)|'),
          array($ADD . '\1' . $CLOSE,
            $DEL . '\1' . $CLOSE),
          $sections[0]);
        $c = count($sections);
        for ($i = 1; $c > $i; ++$i) {
          $hits = array();
          if (preg_match('|^\n(\*{5,})|',
            $sections[$i], $hits)) {
            unset($hits[0]);
            $sections[$i] = $HEAD . $hits[1] . $CLOSE;
          } else if (preg_match('|^\n(\x2A{3}\s[^\n]*)(.*)|s',
            $sections[$i], $hits)) {
            unset($hits[0]);  // free mem
            $parts = preg_split('|\n(\-{3}\s[^\n]*)|',
              $hits[2], -1, PREG_SPLIT_DELIM_CAPTURE);
            // $parts[0] == OLD code
            $parts[0] = preg_replace('|\n([!\-][^\n]*)|',
              $DEL . '\1' . $CLOSE, $parts[0]);
            // $parts[1] == head of NEW code
            $parts[1] = $ADD . $parts[1] . $CLOSE;
            // $parts[2] == NEW code
            $parts[2] = preg_replace(
              array('|\n([!\x2B][^\n]*)|',
                '|\n(\x2A{3}[^\n]*)|'),
              array($ADD . '\1' . $CLOSE,
                $DEL . '\1' . $CLOSE),
              $parts[2]);
            if (isset($parts[3])) {
              // TRUE when handling multi-file patches
              $parts[3] = preg_replace('|^(\x2D{3}[^\n]*)|',
                $ADD . '\1' . $CLOSE, $parts[3]);
            } // if
            $sections[$i] = $DEL . $hits[1] . $CLOSE
              . implode('', $parts);
          } // if
          // ELSE: leave $sections[$i] as is
        } // for
        $aDoc .= implode('', $sections);
        return TRUE;
      case 'n':  // RCS output
        // Only added lines are there so we highlight just the
        // diff indicators while leaving the text alone.
        $aDoc .= preg_replace(
          array($DiffHead . ')#',
            '|\n(d[0-9]+\s+[0-9]+)|',
            '|\n(a[0-9]+\s+[0-9]+)|'),
          array($HEAD . '\1' . $CLOSE,
            $DEL . '\1' . $CLOSE,
            $ADD . '\1' . $CLOSE),
          $aText);
        return TRUE;
      case 's':  // simple output
        $aDoc .= preg_replace(
          array($DiffHead
            . '|((?:[0-9a-z]+(?:,[0-9a-z]+)*)(?:[^\n]*)))#',
            '|\n(\x26#60;[^\n]*)|',
            '|\n(\x26#62;[^\n]*)|'),
          array($HEAD . '\1' . $CLOSE,
            $DEL . '\1' . $CLOSE,
            $ADD . '\1' . $CLOSE),
          $aText);
        return TRUE;
      default:  // unknown diff format
        $aDoc .= $aText;  // just append any unrecognized text
        return TRUE;
    } // switch
  } // _addDiff()
 
  /**
   * Add the lines of the given <tt>$aList</tt> to the specified
   * <tt>$aDoc</tt> beginning with the given <tt>$aStart</tt> linenumber.
   *
   * @param $aList Array [IN] the list of lines as prepared by
   * <tt>render()</tt>, [OUT] <tt>FALSE</tt>.
   * @param $aStart Integer The first linenumber to use.
   * @param $aDoc String Reference to the current renderer's
   * <tt>doc</tt> property.
   * @private
   * @see render()
   */
  function _addLines(&$aList, $aStart, &$aDoc) {
    // Since we're dealing with monospaced fonts here the width of each
    // character (space, NBSP, digit) is the same. Hence the length of
    // a digits string gives us its width i.e. the number of digits.
    $i = $aStart + count($aList);  // greatest line number
    $g = strlen("$i");    // width of greatest number
    while (list($i, $l) = each($aList)) {
      unset($aList[$i]);  // free mem
      $aDoc .= '<span class="lno">'
        . $this->_lead[$g - strlen("$aStart")]
        . "$aStart:</span>" . ((($l) && ('&nbsp;' != $l))
          ? " $l\n"
          : "\n");
      ++$aStart;  // increment line number
    } // while
    $aList = FALSE;  // release memory
  } // _addLines()
 
  /**
   * Internal convenience method to replace HTML special characters.
   *
   * @param $aString String [IN] The text to handle;
   * [OUT] the modified text (i.e. the method's result).
   * @return String The string with HTML special chars replaced.
   * @private
   * @since created 05-Feb-2007
   */
  function &_entities(&$aString) {
    $aString = str_replace(array('&', '<', '>'),
      array('&#38;', '&#60;', '&#62;'), $aString);
    return $aString;
  } // _entities()
 
  /**
   * Try to fix some markup error of the GeSHi SHELL highlighting.
   *
   * <p>
   * The GeShi highlighting for type "sh" (i.e. "bash") is, well,
   * seriously flawed (at least up to version 1.0.7.20 i.e. 2007-07-01).
   * Especially handling of comments and embedded string as well as
   * keyword is plain wrong.
   * </p><p>
   * This internal helper method tries to solve some minor problems by
   * removing highlight markup embedded in comment markup.
   * This is, however, by no means a final resolution: GeSHi obviously
   * keeps a kind of internal state resulting in highlighting markup
   * spawing (i.e. repeated on) several lines.
   * Which - if that state is wrong - causes great demage not by
   * corrupting the data but by confusing the reader with wrong markup.
   * The easiest way to trigger such a line spawning confusion is to use
   * solitary doublequotes or singlequotes (apostrophe) in a comment
   * line ...
   * </p>
   * @param $aMarkup String [IN] The highlight markup as returned by GeSHi;
   * [OUT] <tt>FALSE</tt>.
   * @param $aDoc String Reference to the current renderer's
   * <tt>doc</tt> property.
   * @private
   * @since created 04-Aug-2007
   * @see render()
   */
  function _fixGeSHi_Bash(&$aMarkup, &$aDoc) {
    $hits = array();
    if (defined('GESHI_VERSION')
    && preg_match('|(\d+)\.(\d+)\.(\d+)\.(\d+)|', GESHI_VERSION, $hits)
    && ($hits = sprintf('%02u%02u%02u%03u',
      $hits[1] * 1, $hits[2] * 1, $hits[3] * 1, $hits[4] * 1))
    && ('010007020' < $hits)) {
      // GeSHi v1.0.7.21 has the comments bug fixed
      $aDoc .= $aMarkup;
      $aMarkup = FALSE;  // release memory
      return;
    } // if
    $lines = explode("\n", $aMarkup);
    $aMarkup = FALSE;  // release memory
    while (list($i, $l) = each($lines)) {
      $hits = array();
      // GeSHi "bash" module marks up comments with CSS class "re3":
      if (preg_match('|^((.*)<span class="re3">)(.*)$|i', $l, $hits)) {
        if ('#!/bin/' == substr($hits[3], 0, 7)) {
          $lines[$i] = $hits[2] . strip_tags($hits[3]);
        } else {
          $lines[$i] = $hits[1] . strip_tags($hits[3]) . '</span>';
        } // if
      } else if (! preg_match('|^\s*<span|i', $l)) {
        // If a line doesn't start with a highlighted keyword
        // all tags are removed since they're most probably
        // "leftovers" from the GeSHI string/comment bug.
        $lines[$i] = strip_tags($l);
      } // if
    } // while
    $aDoc .= implode("\n", $lines);
  } // _fixGeSHi_Bash()
 
  /**
   * Add markup to load JavaScript file with older DokuWiki versions.
   *
   * @param $aRenderer Object The renderer used.
   * @private
   * @since created 19-Feb-2007
   * @see render()
   */
  function _fixJS(&$aRenderer) {
    //XXX This test will break if the DokuWiki file gets renamed:
    if (@file_exists(DOKU_INC . 'lib/exe/js.php')) {
      // Assuming a fairly recent DokuWiki installation
      // handling the plugin files on its own there's
      // nothing to do here ...
      return;
    } // if
    if ($this->_JSmarkup) {
      // Markup already added (or not needed)
      return;
    } // if
    $localdir = realpath(dirname(__FILE__)) . '/';
    $webdir = DOKU_BASE . 'lib/plugins/code/';
    $css = '';
    if (file_exists($localdir . 'style.css')) {
      ob_start();
      @include($localdir . 'style.css');
      // Remove whitespace from CSS and expand IMG paths:
      if ($css = preg_replace(
        array('|\s*/\x2A.*?\x2A/\s*|s', '|\s*([:;\{\},+!])\s*|',
          '|(?:url\x28\s*)([^/])|', '|^\s*|', '|\s*$|'),
        array(' ', '\1', 'url(' . $webdir . '\1'),
        ob_get_contents())) {
        $css = '<style type="text/css">' . $css . '</style>';
      } // if
      ob_end_clean();
    } // if
    $js = (file_exists($localdir . 'script.js'))
      ? '<script type="text/javascript" src="'
        . $webdir . 'script.js"></script>'
      : '';
    if ($this->_JSmarkup = $css . $js) {
      $aRenderer->doc = $this->_JSmarkup
        . preg_replace('|\s*<p>\s*</p>\s*|', '', $aRenderer->doc);
    //ELSE: Neither CSS nor JS files found.
    } // if
    // Set member field to skip tests with next call:
    $this->_JSmarkup = TRUE;
  } // _fixJS()
 
  /**
   * RegEx callback to markup spaces in ODT mode.
   *
   * @param $aList Array A list of RegEx matches.
   * @private
   * @static
   * @since created 07-Jun-2008
   * @see render()
   */
  function _preserveSpaces($aList) {
    return ($len = strlen($aList[1]))
      ? '<text:s text:c="' . $len . '"/>'
      : ' ';
  } // _preserveSpaces()
 
  /**
   * Add the lines of the given <tt>$aText</tt> to the specified
   * <tt>$aDoc</tt> beginning with the given <tt>$aStart</tt> linenumber.
   *
   * @param $aText String [IN] the text lines as prepared by
   * <tt>handle()</tt>, [OUT] <tt>FALSE</tt>.
   * @param $aStart Integer The first linenumber to use;
   * if <tt>0</tt> (zero) no linenumbers are used.
   * @param $aDoc String Reference to the current renderer's
   * <tt>doc</tt> property.
   * @param $aClass String The CSS class name for the <tt>PRE</tt> tag.
   * @param $addTags Boolean Used in "ODT" mode to suppress tagging
   * the line numbers.
   * @private
   * @since created 03-Feb-2007
   * @see render()
   */
  function _rawMarkup(&$aText, $aStart, &$aDoc, $aClass, $addTags = TRUE) {
    if ($addTags) {
      $aDoc .= '<pre class="' . $aClass . '">' . "\n";
    } // if
    if ($aStart) {
      // Split the prepared data into a list of lines:
      $aText = explode("\n", $aText);
      // Add the numbered lines to the document:
      $this->_addLines($aText, $aStart, $aDoc);
    } else {
      $aDoc .= $aText;
    } // if
    if ($addTags) {
      $aDoc .= '</pre>';
    } // if
    $aText = FALSE;  // release memory
  } // _rawMarkup()
 
  /**
   * RegEx callback to replace SPAN tags in ODT mode.
   *
   * @param $aList Array A list of RegEx matches.
   * @private
   * @static
   * @since created 07-Jun-2008
   * @see render()
   */
  function _replaceSpan($aList) {
    return ($aList[3])
      ? '<text:span text:style-name="Code_5f_'
        . str_replace('_', '_5f_', $aList[3]) . '">'
      : '<text:span>';
  } // _replaceSpan()
 
  //@}
  /**
   * @publicsection
   */
  //@{
 
  /**
   * Tell the parser whether the plugin accepts syntax mode
   * <tt>$aMode</tt> within its own markup.
   *
   * @param $aMode String The requested syntaxmode.
   * @return Boolean <tt>FALSE</tt> (no nested markup allowed).
   * @public
   * @see getAllowedTypes()
   */
  function accepts($aMode) {
    return FALSE;
  } // accepts()
 
  /**
   * Connect lookup pattern to lexer.
   *
   * @param $aMode String The desired rendermode.
   * @public
   * @see render()
   */
  function connectTo($aMode) {
    // look-ahead to minimize the chance of false matches:
    $this->Lexer->addEntryPattern(
      '\x3Ccode(?=[^>]*\x3E\r?\n.*\n\x3C\x2Fcode\x3E)',
      $aMode, 'plugin_code');
  } // connectTo()
 
  /**
   * Get an array of mode types that may be nested within the
   * plugin's own markup.
   *
   * @return Array Allowed nested types (none).
   * @public
   * @see accepts()
   * @static
   */
  function getAllowedTypes() {
    return array();
  } // getAllowedTypes()
 
  /**
   * Get an associative array with plugin info.
   *
   * <p>
   * The returned array holds the following fields:
   * <dl>
   * <dt>author</dt><dd>Author of the plugin</dd>
   * <dt>email</dt><dd>Email address to contact the author</dd>
   * <dt>date</dt><dd>Last modified date of the plugin in
   * <tt>YYYY-MM-DD</tt> format</dd>
   * <dt>name</dt><dd>Name of the plugin</dd>
   * <dt>desc</dt><dd>Short description of the plugin (Text only)</dd>
   * <dt>url</dt><dd>Website with more information on the plugin
   * (eg. syntax description)</dd>
   * </dl>
   * @return Array Information about this plugin class.
   * @public
   * @static
   */
  function getInfo() {
    $c = 'code';  // hack to hide "desc" field from GeShi
    return array(
      'author' =>  'Matthias Watermann',
      'email' =>  'support@mwat.de',
      'date' =>  '2008-07-22',
      'name' =>  'Code Syntax Plugin',
      'desc' =>  'Syntax highlighting with line numbering <'
        . $c . ' lang 1 |[fh] text |[hs]> ... </' . $c . '>',
      'url' =>  'http://wiki.splitbrain.org/plugin:code2');
  } // getInfo()
 
  /**
   * Define how this plugin is handled regarding paragraphs.
   *
   * <p>
   * This method is important for correct XHTML nesting.
   * It returns one of the following values:
   * </p><dl>
   * <dt>normal</dt><dd>The plugin can be used inside paragraphs.</dd>
   * <dt>block</dt><dd>Open paragraphs need to be closed before
   * plugin output.</dd>
   * <dt>stack</dt><dd>Special case: Plugin wraps other paragraphs.</dd>
   * </dl>
   * @return String <tt>"block"</tt>.
   * @public
   * @static
   */
  function getPType() {
    return 'block';
  } // getPType()
 
  /**
   * Where to sort in?
   *
   * @return Integer <tt>194</tt> (below "Doku_Parser_Mode_code").
   * @public
   * @static
   */
  function getSort() {
    // class "Doku_Parser_Mode_code" returns 200
    return 194;
  } // getSort()
 
  /**
   * Get the type of syntax this plugin defines.
   *
   * @return String <tt>"protected"</tt>.
   * @public
   * @static
   */
  function getType() {
    return 'protected';
  } // getType()
 
  /**
   * Handler to prepare matched data for the rendering process.
   *
   * <p>
   * The <tt>$aState</tt> parameter gives the type of pattern
   * which triggered the call to this method:
   * </p><dl>
   * <dt>DOKU_LEXER_UNMATCHED</dt>
   * <dd>ordinary text encountered within the plugin's syntax mode
   * which doesn't match any pattern.</dd>
   * </dl>
   * @param $aMatch String The text matched by the patterns.
   * @param $aState Integer The lexer state for the match.
   * @param $aPos Integer The character position of the matched text.
   * @param $aHandler Object Reference to the Doku_Handler object.
   * @return Array Index <tt>[0]</tt> holds the current <tt>$aState</tt>,
   * index <tt>[1]</tt> the embedded text to highlight,
   * index <tt>[2]</tt> the language/dialect (or <tt>FALSE</tt>),
   * index <tt>[3]</tt> the first line number (or <tt>0</tt>),
   * index <tt>[4]</tt> the top title (or <tt>FALSE</tt>),
   * index <tt>[5]</tt> the bottom title (or <tt>FALSE</tt>),
   * index <tt>[6]</tt> hidding CSS flag (or <tt>""</tt>).
   * @public
   * @see render()
   * @static
   */
  function handle($aMatch, $aState, $aPos, &$aHandler) {
    if (DOKU_LEXER_UNMATCHED != $aState) {
      return array($aState);  // nothing to do for "render()"
    } // if
    $aMatch = explode('>', $aMatch, 2);
    // $aMatch[0] : lang etc.
    // $aMatch[1] : text to highlight
    $n = explode('>', trim($aMatch[1]));
    $l = 'extern';    // external resource requested?
    // Check whether there's an external file to fetch:
    if ($l == $n[0]) {
      if ($n[1] = trim($n[1])) {
        if (is_array($n[0] = @parse_url($n[1]))
        && ($n[0] = $n[0]['scheme'])) {
          // Don't accept unsecure schemes like
          // "file", "javascript", "mailto" etc.
          switch ($n[0]) {
            case 'ftp':
            case 'http':
            case 'https':
              //XXX This might fail due to global PHP setup:
              if ($handle = @fopen($n[1], 'rb')) {
                $aMatch[1] = '';
                while (! @feof($handle)) {
                  //XXX This might fail due to
                  // memory constraints:
                  $aMatch[1] .= @fread($handle, 0x8000);
                } // while
                @fclose($handle);
              } else {
                $aMatch = array($l,
                  'Failed to retrieve: ' . $n[1]);
              } // if
              break;
            default:
              $aMatch = array($l,
                'Unsupported URL scheme: ' . $n[0]);
              break;
          } // switch
        } else {
          $aMatch = array($l, 'Invalid URL: ' . $n[1]);
        } // if
      } else {
        $aMatch = array($l, 'Missing URL: ' . $aMatch[1]);
      } // if
    } // if
    // Strip leading/trailing/EoL whitespace,
    // replace TABs by four spaces, "&#160;" by NBSP:
    $aMatch[1] = preg_replace(
      array('#(?>\r\n)|\r#', '|^\n\n*|',
        '|[\t ]+\n|', '|\s*\n$|'),
      array("\n", '', "\n", ''),
      str_replace('&#160;', '&nbsp;',
        str_replace("\t", '    ', $aMatch[1])));
 
    $css = '';    // default: no initial CSS content hidding
    $l = FALSE;    // default: no language
    $n = 0;      // default: no line numbers
    $ht = $ft = FALSE;  // default: no (head/foot) title
    $hits = array();  // RegEx matches from the tag attributes
    /*
      The free form of the RegEx to parse the arguments here is:
    /^
      # "eat" leading whitespace:
      \s*
      (?=\S)  # Look ahead: do not match empty lines. This is
          # needed since all other expressions are optional.
      # Make sure, nothing is given away once it matched:
      (?>
        # We need a separate branch for "diff" because it may be
        # followed by a _letter_ (not digit) indicating the format.
        (?>
          (diff)
          #  match 1
          (?>\s+([cnrsu]?))?
          #  match 2
        )
      |
        # Branch for standard language highlighting
        (?>
          # extract language:
          ([a-z][^\x7C\s]*)
          #  match 3
          (?>
            # extract starting line number:
            \s+(\d\d*)
            #  match 4
          )?
        )
      |
        # Branch for line numbering only
        (\d\d*)
        #  match 5
      |
        \s*    # dummy needed to match "title only" markup (below)
      )
      # "eat" anything else up to the text delimiter:
      [^\x7C]*
      (?>
        \x7C
        # extract the position flag:
        ([bfht])?\s*
        #  match 6
        # extract the header,footer line:
        ([^\x7C]+)
        #  match 7
        (?>
          # see whether there is a class flag:
          \x7C\s*
          (h|s)?.*
          #  match 8
        )?
      )?
    # Anchored to make sure everything gets matched:
    $/xiu
 
      Since compiling and applying a free form RegEx slows down the
      overall matching process I've folded it all to a standard RegEx.
      Benchmarking during development gave me
      free form:  20480 loops, 552960 hits, 102400 fails, 12.994689 secs
      standard:  20480 loops, 552960 hits, 102400 fails, 8.357169 secs
    */
    if (preg_match('/^\s*(?=\S)(?>(?>(diff)(?>\s+([cnrsu]?))?)|'
      . '(?>([a-z][^\x7C\s]*)(?>\s+(\d\d*))?)|(\d\d*)|\s*)[^\x7C]*'
      . '(?>\x7C([bfht])?\s*([^\x7C]+)(?>\x7C\s*(h|s)?.*)?)?$/iu',
    $aMatch[0], $hits)) {
      unset($hits[0]);  // free mem
      // $hits[1] = "diff"
      // $hits[2] = type  (of [1])
      // $hits[3] = LANG
      // $hits[4] = NUM  (of [3])
      // $hits[5] = NUM  (alone)
      // $hits[6] = Top/Bottom flag  (of [7])
      // $hits[7] = TITLE
      // $hits[8] = s/h CSS flag
      if (isset($hits[3]) && ($hits[3])) {
        $l = strtolower($hits[3]);
        if (isset($hits[4]) && ($hits[4])) {
          $n = (int)$hits[4];
        } // if
        $hits[3] = $hits[4] = FALSE;
      } else if (isset($hits[1]) && ($hits[1])) {
        $l = strtolower($hits[1]);
        $hits[2] = (isset($hits[2]))
          ? strtolower($hits[2]) . '?'
          : '?';
        $n = $hits[2]{0};
        $hits[1] = $hits[2] = FALSE;
      } else if (isset($hits[5]) && ($hits[5])) {
        $n = (int)$hits[5];
      } // if
      if (isset($hits[7]) && ($hits[7])) {
        $hits[6] = (isset($hits[6]))
          ? strtolower($hits[6]) . 'f'
          : 'f';
        switch ($hits[6]{0}) {
          case 'h':
          case 't':
            $ht = trim($hits[7]);
            break;
          default:
            $ft = trim($hits[7]);
            break;
        } // switch
        if (isset($hits[8])) {
          $hits[8] = strtolower($hits[8]) . 's';
          if ('h' == $hits[8]{0}) {
            // This class is handled by JavaScript (there
            // _must_not_ be any CSS rules for this):
            $css = ' HideOnInit';
          } // if
        } // if
        $hits[6] = $hits[7] = $hits[8] = FALSE;
      } // if
    // ELSE: no arguments given to CODE tag
    } // if
    switch ($l) {
      case 'console':
        // nothing additional to setup here
        break;
      case 'diff':
        if ("\n" != $aMatch[1]{0}) {
          // A leading LF is needed to recognize and handle
          // the very first line with all the REs used.
          $aMatch[1] = "\n" . $aMatch[1];
        } // if
        switch ($n) {
          case 'u':  // DIFF cmdline switch for "unified"
          case 'c':  // DIFF cmdline switch for "context"
          case 'n':  // DIFF cmdline switch for "RCS"
          case 's':
            // We believe the format hint ...
            // (or should we be more suspicious?)
            break;
          case 'r':  // Mnemonic for "RCS"
            $n = 'n';
            break;
          default:  // try to figure out the format actually used
            if (preg_match(
              '|\n(?:\x2A{5,}\n\x2A{3}\s[1-9]+.*?\x2A{4}\n.+?)+|s',
              $aMatch[1])) {
              $n = 'c';
            } else if (preg_match(
              '|\n@@\s\-[0-9]+,[0-9]+[ \+,0-9]+?@@\n.+\n|s',
              $aMatch[1])) {
              $n = 'u';
            } else if (preg_match(
              '|\n[ad][0-9]+\s+[0-9]+\r?\n|', $aMatch[1])) {
              // We've to check this _before_ "simple" since
              // the REs are quite similar (but this one is
              // slightly more specific).
              $n = 'n';
            } else if (preg_match(
              '|\n(?:[0-9a-z]+(?:,[0-9a-z]+)*)(?:[^\n]*\n.*?)+|',
              $aMatch[1])) {
              $n = 's';
            } else {
              $n = '?';
            } // if
            break;
        } // switch
        break;
      case 'htm':    // convenience shortcut
      case 'html':  // dito
        $l = 'html4strict';
        break;
      case 'js':    // shortcut
        $l = 'javascript';
        break;
      case 'sh':    // shortcut
        $l = 'bash';
        break;
      default:
        if (! $l) {
          // no language: simple PRE markup will get generated
          $l = FALSE;
        } // if
        break;
    } // switch
    return array(DOKU_LEXER_UNMATCHED,
      $aMatch[1], $l, $n, $ht, $ft, $css);
  } // handle()
 
  /**
   * Add exit pattern to lexer.
   *
   * @public
   */
  function postConnect() {
    // look-before to minimize the chance of false matches:
    $this->Lexer->addExitPattern('(?<=\n)\x3C\x2Fcode\x3E',
      'plugin_code');
  } // postConnect()
 
  /**
   * Handle the actual output (markup) creation.
   *
   * <p>
   * The method checks the given <tt>$aFormat</tt> to decide how to
   * handle the specified <tt>$aData</tt>.
   * The standard case (i.e. <tt>"xhtml"</tt>) is handled completely
   * by this implementation, preparing linenumbers and/or head/foot
   * lines are requested.
   * For the <tt>"odt"</tt> format all plugin features (incl. linenumbers
   * and header/footer lines) are supported by generating the appropriate
   * ODT/XML markup.
   * All other formats are passed back to the given <tt>$aRenderer</tt>
   * instance for further handling.
   * </p><p>
   * <tt>$aRenderer</tt> contains a reference to the renderer object
   * which is currently in charge of the rendering.
   * The contents of the given <tt>$aData</tt> is the return value
   * of the <tt>handle()</tt> method.
   * </p>
   * @param $aFormat String The output format to generate.
   * @param $aRenderer Object A reference to the renderer object.
   * @param $aData Array The data created/returned by the
   * <tt>handle()</tt> method.
   * @return Boolean <tt>TRUE</tt>.
   * @public
   * @see handle()
   */
  function render($aFormat, &$aRenderer, &$aData) {
    if (DOKU_LEXER_UNMATCHED != $aData[0]) {
      return TRUE;
    } // if
    if ('xhtml' == $aFormat) {
      if ($tdiv = (($aData[4]) || ($aData[5]))) {
        $this->_fixJS($aRenderer);  // check for old DokuWiki versions
        $aRenderer->doc .= '<div class="code">';
        if ($aData[4]) {
          //XXX Note that "_headerToLink()" is supposed to be a
          // _private_ method of the renderer class; so this code
          // will fail once DokuWiki is rewritten in PHP5 which
          // implements encapsulation of private methods and
          // properties:
          $aRenderer->doc .= '<p class="codehead' . $aData[6]
            . '"><a name="' . $aRenderer->_headerToLink($aData[4])
            . '">' . $this->_entities($aData[4]) . '</a></p>';
          $aData[4] = $aData[6] = FALSE;  // free mem
        } // if
      } // if
      if ($aData[2]) {  // lang was given
        if ('console' == $aData[2]) {
          $this->_rawMarkup($this->_entities($aData[1]),
            $aData[3], $aRenderer->doc, $aData[2]);
        } else if ('diff' == $aData[2]) {
          $this->_entities($aData[1]);
          $aRenderer->doc .= '<pre class="code diff">';
          $this->_addDiff($aData[1], $aData[3], $aRenderer->doc);
          $aRenderer->doc .= '</pre>';
        } else {
          $isSH = ('bash' == $aData[2]);
          $geshi = new GeSHi($aData[1], $aData[2], GESHI_LANG_ROOT);
          if ($geshi->error()) {
            // Language not supported by "GeSHi"
            $geshi = NULL;  // release memory
            $this->_rawMarkup($this->_entities($aData[1]),
              $aData[3], $aRenderer->doc, 'code');
          } else {
            $aData[1] = FALSE;  // free mem
            $geshi->enable_classes();
            $geshi->set_encoding('utf-8');
            $geshi->set_header_type(GESHI_HEADER_PRE);
            $geshi->set_overall_class('code ' . $aData[2]);
            global $conf;
            if ($conf['target']['extern']) {
              $geshi->set_link_target($conf['target']['extern']);
            } // if
            if ($aData[3]) {    // line numbers requested
              // Separate PRE tag from parsed data:
              $aData[1] = explode('>', $geshi->parse_code(), 2);
              // [1][0] =  leading "<pre"
              // [1][1] =  remaining markup up to trailing "</pre"
              $geshi = NULL;  // release memory
 
              // Add the open tag to the document:
              $aRenderer->doc .= $aData[1][0] . '>';
 
              // Separate trailing PRE tag:
              $aData[1] = explode('</pre>', $aData[1][1], 2);
              // [1][0] =  GeSHi markup
              // [1][1] =  trailing "</pre"
 
              if ($isSH) {
                $aData[1][1] = '';
                $this->_fixGeSHi_Bash($aData[1][0],
                  $aData[1][1]);
              } else {
                // Set reference to fixed markup to sync with
                // the "bash" execution path (above):
                $aData[1][1] =& $aData[1][0];
              } // if
 
              // Split the parsed data into a list of lines:
              $aData[2] = explode("\n", $aData[1][1]);
              $aData[1] = FALSE; // free mem
 
              // Add the numbered lines to the document:
              $this->_addLines($aData[2], $aData[3],
                $aRenderer->doc);
 
              // Close the preformatted section markup:
              $aRenderer->doc .= '</pre>';
            } else {        // w/o line numbering
              if ($isSH) {
                // Separate trailing PRE tag which
                // sometimes is "forgotten" by GeSHi:
                $aData[2] = explode('</pre>',
                  $geshi->parse_code(), 2);
                // [1][0] =  GeSHi markup
                // [1][1] =  trailing "</pre" (if any)
                $this->_fixGeSHi_Bash($aData[2][0],
                  $aRenderer->doc);
                $aRenderer->doc .= '</pre>';
              } else {
                $aRenderer->doc .= $geshi->parse_code();
              } // if
              $geshi = NULL;  // release memory
            } // if
          } // if
        } // if
      } else {
        $this->_rawMarkup($this->_entities($aData[1]),
          $aData[3], $aRenderer->doc, 'code');
      } // if
      if ($tdiv) {
        if ($aData[5]) {
          //XXX See "_headerToLink()" note above.
          $aRenderer->doc .= '<p class="codefoot'
            . $aData[6] . '"><a name="'
            . $aRenderer->_headerToLink($aData[5]) . '">'
            . $this->_entities($aData[5]) . '</a></p>';
        } // if
        $aRenderer->doc .= '</div>';
      } // if
    } else if ('odt' == $aFormat) {
      $inLI = array();
      if (preg_match('|^<text:p text:style-name="[^"]+">\s*</text:p>\s*(.*)$|si',
        $aRenderer->doc, $inLI)) {
        // remove leading whitespace
        $aRenderer->doc = $inLI[1];
      } // if
      // The "renderer_plugin_odt" doesn't clean (close)
      // its own tags before calling this plugin.
      // To work around that bug we have to check some
      // private properties of the renderer instance.
      $inLI = FALSE;
      if (is_a($aRenderer, 'renderer_plugin_odt')) {
        if ($inLI = ($aRenderer->in_list_item)) {
          // If we're in a list item, we've to close the paragraph:
          $aRenderer->doc .= '</text:p>';
        } // if
        if ($aRenderer->in_paragraph) {
          $aRenderer->doc .= '</text:p>';
          $aRenderer->in_paragraph = FALSE;
        } // if
      } // if
 
      // Init (open) our text section:
      $aRenderer->doc .= "\n"
        . '<text:section text:style-name="Code_5f_Section" text:name="CodeSnippet'
        . ++$this->_odtSect . '">';
 
      if ($tdiv = (($aData[4]) || ($aData[5]))) {
        // Check whether we need a top caption ("header"):
        if ($aData[4]) {
          $aRenderer->doc .=
            '<text:p text:style-name="Code_5f_Title">'
            . "<text:line-break/>\n"
            . $aData[4] . "</text:p>\n";
          $aData[4] = $aData[6] = FALSE;  // free mem
        } // if
      } // if
      // The following code resembles the "xhtml" processing
      // above except that we're not using "pre" tags here
      // but ODT/XML markup.
      $aData[0] = '';    // tmp. container of processed data
      if ($aData[2]) {  // lang was given
        if ('console' == $aData[2]) {
          $this->_rawMarkup($this->_entities($aData[1]),
            $aData[3], $aData[0], $aData[2], FALSE);
        } else if ('diff' == $aData[2]) {
          $this->_addDiff($this->_entities($aData[1]),
            $aData[3], $aData[0]);
        } else {
          $isSH = ('bash' == $aData[2]);
          $geshi = new GeSHi($aData[1], $aData[2], GESHI_LANG_ROOT);
          if ($geshi->error()) {
            // Language not supported by "GeSHi"
            $geshi = NULL;  // release memory
            $this->_rawMarkup($this->_entities($aData[1]),
              $aData[3], $aData[0], '', FALSE);
          } else {
            $aData[1] = FALSE;  // free mem
            $geshi->enable_classes();
            $geshi->set_encoding('utf-8');
            $geshi->set_header_type(GESHI_HEADER_PRE);
            $geshi->set_overall_class('code ' . $aData[2]);
            global $conf;
            if ($conf['target']['extern']) {
              $geshi->set_link_target($conf['target']['extern']);
            } // if
            // Separate PRE tag from parsed data:
            $aData[1] = explode('>', $geshi->parse_code(), 2);
            // [1][0] =  leading "<pre"
            // [1][1] =  remaining markup up to trailing "</pre"
            $geshi = NULL;  // release memory
 
            // Separate trailing PRE tag:
            $aData[1] = explode('</pre>', $aData[1][1], 2);
            // [1][0] =  GeSHi markup
            // [1][1] =  trailing "</pre"
            $aData[1] = $aData[1][0];
 
            if ($isSH) {  // work around GeSHI bug
              $aData[2] = '';
              $this->_fixGeSHi_Bash($aData[1], $aData[2]);
            } else {
              $aData[2] = $aData[1];
            } // if
            $aData[1] = FALSE; // release memory
 
            if ($aData[3]) {    // line numbers requested
              // Split the parsed data into a list of lines:
              $aData[1] = explode("\n", $aData[2]);
              $aData[2] = FALSE; // release memory
 
              // Add the numbered lines to the document:
              $this->_addLines($aData[1], $aData[3], $aData[0]);
            } else {    // w/o line numbers
              $aData[0] = $aData[2];
              $aData[2] = FALSE; // release memory
            } // if
          } // if
        } // if
      } else {
        $this->_rawMarkup($this->_entities($aData[1]),
          $aData[3], $aData[0], '', FALSE);
      } // if
 
      if ('console' == $aData[2]) {
        $aRenderer->doc .=
          '<text:p text:style-name="Code_5f_Console">';
      } else {
        $aRenderer->doc .=
          '<text:p text:style-name="Code_5f_Standard">';
      } // if
      // Replace the HTML "span" tags (for highlighting) by
      // the appropriate ODT/XML markup.
      // For unknown reasons we need an additional space
      // in front of the very first line.
      $aData[0] = '<text:s/>'
        . preg_replace_callback('|(<span( class="([^"]*)"[^>]*)?>)|',
          array('syntax_plugin_code', '_replaceSpan'),
          // OOo (v2.3) crashes on "&nbsp;"
          str_replace('&nbsp;', chr(194) . chr(160),
            str_replace('</span>', '</text:span>',
              strip_tags($aData[0], '<span>'))));
      // Now append our markup to the renderer's document;
      // TABs, LFs and SPACEs are replaced by their respective
      // ODT/XML equivalents:
      $aRenderer->doc .= preg_replace_callback('|( {2,})|',
        array('syntax_plugin_code', '_preserveSpaces'),
        str_replace("\n", "<text:line-break/>\n", $aData[0]));
      $aData[0] =  FALSE;  // release memory
 
      // Check whether we need a bottom caption ("footer"):
      if ($tdiv && ($aData[5])) {
        $aRenderer->doc .=
          '</text:p><text:p text:style-name="Code_5f_Title">'
          . $aData[5];
      } // if
      // Close all our open tags:
      $aRenderer->doc .=  "</text:p></text:section>\n";
 
      if ($inLI) {
        // Workaround (see above): (re-)open a paragraph:
        $aRenderer->doc .= '<text:p>';
      } // if
    } else {    // unsupported output format
      $aData[0] = $aData[4] = $aData[5] = FALSE;  // avoid recursion
      // Pass anything else back to the renderer instance
      // (which will - hopefully - know how to handle it):
      $aRenderer->code($aData[1], $aData[2]);
    } // if
    $aData = array(FALSE);  // don't process this text again
    return TRUE;
  } // render()
 
  //@}
} // class syntax_plugin_code
} // if
//Setup VIM: ex: et ts=2 enc=utf-8 :
?>

Presentation

The accompanying CSS presentation rules (using web-safe colours only):

div.dokuwiki pre.code,pre.code,div.dokuwiki pre.console,pre.console{border:thin dotted #ccc;margin:1ex 0;line-height:1.33;overflow:auto;text-indent:0;max-height:40em;max-width:99%;}
div.dokuwiki pre.code,pre.code{background:#fcfdfe none;color:#000;padding:0.4ex;}
.code .diff_addedline{background:#cfc none;color:#000;}
.code .diff_blockheader{background:#ccf none;color:#000;}
.code .diff_deletedline{background:#fcc none;color:#000;}
.code .br0{background:inherit;color:#369;}
.code .co1,.code .co2,.code .coMULTI,.code .kw2,.code .lno{font-style:italic;}
.code .br0,.code .co2,.code .es0,.code .kw1,.code .kw2,.code .kw3,.code .lno{font-weight:600;}
.code .co1,.code .co2,.code .coMULTI{background:inherit;color:#666;}
.code .es0{background:inherit;color:#c09;}
.code .imp{background:inherit;color:#909;}
.code .kw1{background:inherit;color:#903;}
.code .kw2{background:inherit;color:#036;}
.code .kw3{background:inherit;color:#309;}
.code .kw4{background:inherit;color:#933;}
.code .kw5{background:inherit;color:#00f;}
.code .lno{background:inherit;color:#999;font-size:smaller;}
.code .me0{background:inherit;color:#060;}
.code .nu0{background:inherit;color:#939;}
.code .re0{background:inherit;color:#606;}
.code .re1{background:inherit;color:#660;}
.code .re2{background:inherit;color:#063;}
.code .re3{background:inherit;color:#963;font-style:italic;font-weight:400;}
.code .re4{background:inherit;color:#099;}
.code .sc0{background:inherit;color:#069;}
.code .sc1{background:inherit;color:#960;}
.code .sc2{background:inherit;color:#090;}
.code .st0{background:inherit;color:#900;}
.code .sy0{background:inherit;color:#6c6;}
pre.code a{border:none;}
div.dokuwiki pre.console,pre.console{background:#333 none;color:#fff;font-weight:900;padding:0.4ex 0.3ex 0.6ex 0.6ex;}
div.dokuwiki pre.console .lno,pre.console .lno{background:inherit;color:#cff;font-size:smaller;font-style:italic;}
div.code{margin:0.4ex 0;padding:0.4ex 0;}
div.code pre.code{margin:0;}
div.code p.codehead,div.code p.codefoot{color:#030;background:inherit;line-height:1.33;text-align:left;padding:0 0 0 1ex;}
div.code p.codehead{margin:0.6ex 0 0 0;text-decoration:underline;}
div.code p.codefoot{margin:0 0 0.6ex 0;text-decoration:underline;}
div.code p.codeHidden{background-image:url(img/plus-11x11.gif);}
div.code p.codeShown{background-image:url(img/minus-11x11.gif);}
div.code p.codeHidden,div.code p.codeShown{padding-left:13px;background-repeat:no-repeat;background-position:0 50%;cursor:pointer;}
div.code p.codeHidden:hover,div.code p.codeShown:hover{background-color:#ddffdd;color:#030;text-decoration:none;}
div.code pre.codeHidden{display:none;}
div.code pre.codeShown{display:block;}

Of course, you're free to modify this styles13) to suit your personal needs or aesthetics14).

Behaviour

Sometimes code blocks can be quite long. Long enough, anyway, to sort of overwhelm the other text of the respective page and forcing the user to scroll up/down a lot. The JavaScript file that comes with this plugin provides some additional functionality for the reader to address this issue: If you've added a header/footer line to the code block that very line becomes a clickable area allowing to fold (hide/show) the whole code block.

The client side JavaScript accompanying this plugin uses a totaly unobtrusive approach. That means there's no mixing of markup (HTML), presentation (CSS) and behaviour (JavaScript). And if JavaScript is not available for one reason or another the only consequence is that there's no folding feature i.e. the very same situation as the one without this plugin installed.

If, however, JavaScript is available (and activated) at the user's side15) clicking on a code block's header/footer line will toggle the visibility of the respective code block. Of course – since visibility, colour and alike do not belong to the behaviour domain of JavaScript but to presentation i.e. CSS – the script just assigns CSS classes to the respective page elements. That allows to configure the actual changes which should follow a user's click without having to learn and modify JavaScript source code: Just adjust the CSS and you're done.

If – for example – you do not want the code block to disappear completely “on click” you could change the two very last lines of the CSS file to something like this:

div.code pre.codeHidden {
	max-height:	3em;
}
div.code pre.codeShown {
	max-height:	50em;
}

With browsers implementing the CSS correctly16) the styling rules above will “shrink” the code block to at most three lines (instead of hidding them altogether) on click and expand the code block on the next click to at most 50 lines.

The script itself defines just one (meaningless) global variable17) while encapsulating all its functionality in private members and methods. So there's little chance of naming conflicts with other JavaScript.

Folding state

Without user intervention (i.e. clicking on header/footer lines) nothing changes from default. That means all code blocks are visible18) as one would expect. After all, it would be quite unthoughtful to hide parts of a page by default thus forcing the reader to click somewhere before he/she sees the hidden contents. But.

But there may be situations where the code block is not that important but more sort of illustration and the surrounding text should gain the user's focus first. In other words: You – and not some hardcoded browser logic – whish some block of highlighted code to be hidden initially (i.e. when the page is presented to the user) and become visible only on explicit user intervention (i.e. clicking). That's where the very last optional argument of the code tag comes into the play:
<code lang num |[fh] text |[sh] >

This adds some more use cases to the examples given above:

  • <code |A description |h>
    some text
    </code>

    marks up ”some text” as preformatted text (w/o any special highlighting) with a footer line of ”A description” and the actual code block hidden initially.
  • <code html |h A description: |h>
    some text
    </code>

    turns on HTML highlighting for ”some text” and a header line of ”A description:” with the actual code block hidden initially.
  • <code html 66 |A description |h>
    some text
    </code>

    turns on HTML highlighting for ”some text” with added line numbers starting with number 66 and a footer line of ”A description” with the actual code block hidden initially.

You've noticed, probably, that I didn't use the ”s” flag here. The simple reason: As mentioned above the default behaviour is to show the code block anyway. Hence the ”s” flag doesn't really change anything; however, I've implemented it for symmetry reasons and to allow for a more explicit markup.

When implementing this folding feature I've taken great care to not exclude users with JavaScript turned off. One consequence is that code blocks you marked up using the ”hide” flag will be not hidden in browsers w/o JavaScript19).

In case you neither like nor want this folding feature, simply remove (delete or rename) the script.js file in the {dokuwiki}/lib/plugins/code directory after installing this plugin. The non-/existence of the JavaScript file doesn't influence the PHP plugin in any way.

Changes

2008-07-22:
* CSS: added some selectors to work around DokuWiki's faulty default CSS;
* JS: minor internal changes (mostly adding comments);
* PHP: changed some misleading texts in 'handle()';

2008-06-09:
* updated CSS class names in '_addDiff()';
* modified '_addLines()' to use SPAN (instead of B) tags;
+ implemented a version test in '_fixGeSHi_Bash()';
+ implemented private '_preserveSpaces()' and '_replaceSpan()' methods for use by ODT export;
* added optional 'addTags' argument in '_rawMarkup()' to suppress PRE tags in ODT exports;
* changed whitespace handling in 'handle()' (for better OTD support);
+ completely rewrote ODT generation in 'render()' to fully support highlighting and header/footer lines;
* CSS: renamed DIFF related classes (for consistency with ODT styles);
+ added ODT stylesheet;

2008-06-02:
+ implemented basic support for ODT export;
* modified '_addLines()' to accept new 'addTags' argument;
# removed a superflous end tag in '_fixJS()';
* moved entityfication from 'handle()' to 'render()' method since the ODT plugin does this on its own;
* modified 'render()' method to pass unsupported data formats back to the active renderer instance;

2008-05-26:
+ extended 'handle()' method to retrieve external files;

2008-05-25:
* CSS: added a text-indent rule (for code snippets in footnotes etc.);

2008-04-05:
+ implemented new “console” pseudo language keyword;
* replaced former private “_makeID()” method by calls of the renderer's “_headerToLink()” method;

2007-08-31:
* modified “_fixGeSHi_Bash()” to omit LF if the very last line is marked up as comment;

2007-08-28:
* corrected some doc typos;

2007-08-25:
- removed TAB reduction in “handle()”;

2007-08-16:
* minor change in “_fixGeSHi_Bash()” to omit trailing LF;

2007-08-15:
* added GPL link and fixed some doc problems;

2007-08-05:
+ implemented another workaround in “render()” for GeSHi forgetting the trailing/closing “pre” tag in “bash” mode;

2007-08-04:
+ implemented private “_fixGeSHi_Bash()” method to try to fix some GeSHi errors and modified “render()” accordingly;
* decreased init timeout in JavaScript class;
+ added '.re3' selector in CSS file;

2007-02-23:
* several internal changes (mostly for better support of older browsers and DokuWiki releases);

2007-02-19:
+ implemented support for DokuWiki installations older 2006-03-05;

2007-02-11:
* modified overall RegEx to require LF (to minimize false matches);
* improved TAB handling in “handle()”;
+ implemented optional flag/option to hide code blocks initially;
+ added JavaScript to allow for folding code blocks;

2007-02-04:
* improved handling of leading/trailing/EoL whitespace to save memory & bandwidth;

2007-02-03:
* recrafted RegEx used in “handle()” to become more tolerant, faster and consuming less memory;
* implemented handling of GeSHi “error()”;

2007-02-02:
* made some REs nonpossessive in “_addDiff()”;

2007-01-31:
+ implemented header/footer option;
* minor changes in “handle()” to make “diff” REs unpossessive;

2007-01-16:
* rewrote private “_addLines()” to handle line numbers up to 999,999,999;

2007-01-12:
+ added 'r' option for “diff” highlighting;

2007-01-10:
+ implemented (recognition/highlighting of) “RCS” diff format;

2007-01-05:
* moved entity replacements for blocks w/o language from “render()” to “handle()” and added some more comments;

2006-12-25:
+ initial release;

Matthias Watermann 2008-07-22

Notes

Please note, that this plugin shares common problems with DokuWiki's builtin ”<code>” tag handler as far as the underlying “GeSHi” module is concerned.

Markup

If there's ”<code>” markup within the text to highlight the RegEx based parsers will stumble – with unwanted results20). — Now, considering that (a) the strings<code>” and ”</code>” occur almost exclusively in texts marked up by (X)HTML tags and that (b) the ”codetag is an inline tag, one might presume that in most cases21) those tags will be embedded within a line of text. Therefor this plugin uses slightly different patterns requiring a LF22) both immediately after the opening tag and before the closing tag – thus minimizing (but not removing) the chance of false matches. So DokuWiki markup such as

<code 1>bla bla
bla</code>

or

<code 1>
bla bla
bla</code>

or

<code 1>bla bla
bla
</code>

will not be matched & handled by this plugin. However, that doesn't stop DokuWiki's builtincode” handler from jumping in. And since those builtin DokuWiki parsers are not designed for extensibility you have to live with this problem.

While this might sound annoyingly comp-li-cated23), actually, the only point you should be aware of is to just make sure the LFs mentioned above are in place.

Console

The console mode is intended to reproduce text such as commandline commands and their respective output; for example to document the installation of an application or the invocation of a program etc. You'll have to capture the output of a console session by some means24) to be able to insert the text in a wiki page.

Using console does not really produce some kind of highlighting but just places the given content in a preformatted (X)HTML section with accompanying styling rules to sort of resemble a raw console screen's output.

You may use the optional linenumber and footer/header arguments just as with all the other languages to markup/highlight. However, in console mode the tag's content (i.e. the text between the opening and closing code tags) is just printed out as-is without analyzing or tokenizing it: plain and dump.

Diff

The problems and solution regarding ”diff” handling are already discussed in the usage and examples sections above. With this plugin installed everything works as expected.

External Files

Starting with the 2008-05-26 release this plugin allows for including highlighted data from external sources. The wiki markup would look like this:

<code css |t The DokuWiki Styling Rules>
extern> http://www.dokuwiki.org/lib/exe/css.php
</code>

So basically instead of inserting the complete text to highlight you just enter an URI with the ”extern>” keyword prepended. You may use any highlighting type you wish in the ”code” tag. e.g.

<code html 1|t The DokuWiki Start-Page>
extern> http://www.dokuwiki.org/
</code>

would produce a numbered and highlighted listing of the given page's markup and

<code javascript |t The DokuWiki Javascript>
extern> http://www.dokuwiki.org/lib/exe/js.php
</code>

gives you an impression of the script code delivered to DokuWiki readers …

This feature allows you to include a highlighted version of whatever a remote server happens to send back when serving your specified URI25). You should note, however, that there's no relation between the “filetype” of an URI (as indicated by a socalled filename extension) and the content-type of the data returned when serving the URI request.

The first example above appears to request a PHP file, but the returned data are of type ”text/css”, actually. The second example requests something (by an URI w/o any indication about a possible data type) and gets a ”text/html” data stream. The third example, finally, again seems to request a PHP file but, in fact, gets data of type ”text/javascript”. – So please don't confuse the URI with the data it points to. And, ah, you should know about the content-type of a certain URI's data when deciding which highlighting language to use: Although the examples above all point (directly or indirectly) to a PHP file none of them return a PHP source code26).

As noted above, when using this feature to include external resources you have to live with whatever the external server sends. Especially the formatting of the data may not suit your needs. For example, quite a few server send CSS, HTML, JavaScript etc. without any unnecessary whitespace27) i.e. there may be no linefeeds or indentions in the data stream you receive. While that is perfectly okay for browsers it doesn't look too well in a wiki page, even with syntax highlighting. So you should check the resource you'd like to include with a browser's Show Source view to decide whether it's suitable for inclusion in a wiki page.

Please be aware that lots of people consider it abusive to include their respective contents in foreign pages. Note as well, that including28) external resources could raise copyright problems. If in doubt, just ask the respective site operator/owner for permission before using this feature.

Limitations on external files

For security reasons the supported URI-schemes are limited to ”ftp”, ”http” and ”https29). Hence it's not possible to compromise your wiki by either e.g. malicious use of the ”javascript” or ”mailto” pseudo schemes or the exposure of local files by the ”file” scheme.

Please note as well that this feature depends on PHP's allow-url-fopen configuration setting. So it's not guaranteed to work on each and every system. Consult your PHP manual for more details about this subject.

In case someone sees other security problems with this feature please drop me a note.

Open Document Format

There's an experimental odt plugin aiming to support the Open Document Format. Unfortunately that plugin doesn't use the well defined XHTML markup (produced by all builtin and plugin renderers) to prepare a page for export but instead requires that each and every syntax plugin explicitely supports the ODT as well.

As of 2008-06-02 this plugin provides support for the ODT export. However, you should be aware that the overall result of an ODT export might not be what you expect. Not only whole parts of a wiki page may simply disappear30). But the exported ODT file can be corrupted as well since the odt plugin31) doesn't support the native DokuWiki syntax completely32). So before making ODT exports publically available for your wiki pages you should carefully check whether it works for you or not.

Please note, that this plugin handles only the code fragments marked up by ”<code …>
some text to highlight
</code>” but not the preformatted texts in your wiki page indented by spaces or TABs. The latter are handled by the odt plugin which doesn't work properly in all constellations. If you're experiencing problems in this area try to add the code tags and see whether it helps.

Anyway, as far as this plugin is concerned the text to highlight results in preformatted text, optionally with line numbers prepended (see Examples above). The header/footer lines are placed in the ODT export as well but, of course, there's no show/hide feature in the ODT file.

ODT highlighting

To make the syntax highlighting actually work the ODT reader (e.g. OpenOffice.org writer) needs a fitting stylesheet – just as the web-browser needs one – to render the text. Starting with the 2008-06-09 release the plugin's archive contains a file “syntax_plugin_code_styles.xml” which provides styling rules for all the classes used by this plugin. You'll have to copy it into the odt plugin's directory to let the latter use it. Because this task cannot automaticated you'll need to change to this plugin's directory and copy the stylesheet manually, e.g.

$> cd /to/my/dokuwiki/lib/plugins/code
$> cp syntax_plugin_code_styles.xml ../odt/styles.xml
$>

Once you've done that all your ODT exports will use a highlighting similar to the browser's Presentation. If you miss this step the resulting ODT file should still be usable but all the code fragments will look like standard text – probably not what you intend …

Shell

The GeSHi module handling shell scripts (”sh”/”bash”) is, well, seriously flawed (at least up to version 1.0.7.20 i.e. 2007-07-01). Especially the handling of comments with embedded strings as well as keywords is plain wrong.

This plugin tries to solve some minor problems by removing highlight markup embedded in comment markup. This is, however, by no means a final solution: GeSHi obviously keeps a kind of internal state resulting in highlighting markup spanning (i.e. repeated on) several lines. Which – if that state is wrong – causes great demage: not by corrupting the data but by confusing the reader with wrong markup. The easiest way to trigger such a line spanning confusion is to use solitary doublequotes or singlequotes (apostrophe) in a comment line as demonstrated below.

The following fragment will show up correctly with this plugin installed but wrong with DokuWiki's builtin code handler (using GeSHi up to v1.0.7.20):

#!/bin/sh
# This file is for test purposes only.
# There is no other reason -
# hence do not use it.
 
echo "This test is right!"
exit 0
#_EoF_

Although the words ”for”, ”test” and ”do” are just plain text in a comment line, GeSHi (up to v1.0.7.20) treats them as keywords. This cause of irritation gets removed by this plugin (as of 2007-08-04).

The next fragment, however, will result in confusing highlighting:

#!/bin/sh
# This file is for test purposes only.
# There's no other reason -
# hence do not use it.
 
echo "This test's not right!"
exit 0
#_EoF_

The problem here starts with the apostrophe in line 3. Besides the wrong keyword handling mentioned above the recognition of strings is broken as well: The text

's no other reason -
# hence do not use it.

echo "This test'

is handled as one string and the text

"
exit 0
#_EoF_

(i.e. up to the end of the fragment) is handled as a second string. Since this kind of errors may span several lines (and can very well mixup the whole highlighting as the following fragment shows) this plugin can't do anything about it33). The only 'workaround' is to manually replace the apostrophes (i.e. write it is instead of it's) and single doublequotes34).

#!/bin/sh
# It's not gonna work ...
echo "This test's not right as well for all I know."
# What's going on?
echo "I'd rather use something else ..."
# It's disturbing, isn't it?
#_EoF_

A general problem with the GeSHibash” module is that is treats a lot of words as keywords which in fact are notbash” keywords. It seems that someone just dumped his /bin/ directories saying: “All this tools and programs are shell keywords”35). Which, of course, is plain wrong.

Whoever is responsible for that module should just take a look at the bash man page to see which words and symbols are keywords actually. – Anyway, the consequence of all the bugs mentioned is that the GeSHibash” module is not usable for serious work. It causes more confusion than help.

Update: The GeSHi version 1.0.7.21 (2008-03-23) seems to have at least the comment bug fixed. You might want to try installing that version over the one that came with your DokuWiki if that was an older version.

UTF-8

Another problem is that the GeSHi engine has a bug which causes raw UTF-8 character sequences in the text to highlight to end up illegible in some cases36). As a workaround you could manually “entityfy” those characters in your page's text.

Backwards compatibility

Prior to the 2007-02-19 release of this plugin the behaviour didn't work with DokuWiki releases older than 2006-03-0537). But obviously there are still quite a few older versions in production use38). To support such systems I enhanced the plugin to include the CSS and JavaScript in such cases. I've tested it successfully with a 2005-07-30 version of DokuWiki, but please let me know if there are issues with other releases.

You should be aware that the resulting XHTML code with such DokuWiki releases does not validate39) because the current design of DokuWiki doesn't provide a way to add markup to a page's head section40). Hence the only way to transport the plugin's CSS to the user's browser is by embedding it in the page which, alas, renders the page formally invalid. The only workaround is to (a) append the plugin's CSS file to DokuWiki's main CSS file and (b) remove (delete) the plugin's CSS file e.g.:

$>  cat lib/plugins/code/style.css >> lib/tpl/default/design.css
$>  rm -f lib/plugins/code/style.css
$>  _

See also

There was another code plugin with different aims. Check its docs to see whether it will fit your needs.

Plugins by the same author

Discussion

Hints, comments, suggestions …
(Please add new issues at the document's end.)

Thank you for the last update ;-)

You're welcome! Hope you'll like the new folding feature as well.
Matthias Watermann 2007-02-12 12:25

Hello Matthias, how can I change the default behaviour of the header/footer line

  • a ”h” (for “header”)42) following immediately the pipe character causes the remaining ”text” to get placed above the code, a ”f” (for “foot”)43) places the ”text” below the code (note that this flag is optional; if omitted it defaults to ”f”);

I want to change the default to ”h” = “header”. Or better is it possible to make this configurable?
Thanks – Uwe Kirbach 2007-02-17

Well, I designed the placement based on the model of books where you'll usually find something like “Listing 1” or “Example 12.4” below a listing. That's what readers are used to and I think it reasonable to resemble it in web pages44). I can, however, imagine use cases where the text should appear above a listing and so I introduced the ”h” and ”f” flags to allow for controlling the text's placement. The default position (”f“oot) can be changed by one single keystroke (”h”). So, Uwe, I've to admit that I probably don't really understand your problem. Anyway, if you want to save that single keystroke you can patch the plugin's handle() method:
--- syntax_plugin_code.php.orig	2007-02-23 11:33:35.000000000 +0100
+++ syntax_plugin_code.php	2007-02-23 11:53:17.000000000 +0100
@@ -595,14 +595,14 @@
 				$n = (int)$hits[5];
 			} // if
 			if (isset($hits[7]) && ($hits[7])) {
-				$hits[6] = (isset($hits[6])) ? strtolower($hits[6]) . 'f' : 'f';
+				$hits[6] = (isset($hits[6])) ? strtolower($hits[6]) . 'h' : 'h';
 				switch ($hits[6]{0}) {
-					case 'h':
-					case 't':
-						$ht = trim($hits[7]);
+					case 'b':
+					case 'f':
+						$ft = trim($hits[7]);
 						break;
 					default:
-						$ft = trim($hits[7]);
+						$ht = trim($hits[7]);
 						break;
 				} // switch
 				if (isset($hits[8])) {

About your suggestion to make the default placement configurable: I don't think it's worth it to maintain45) external configuration files, implement the required program logic and sacrifice both memory and runtime just to save a single keystroke for some use cases while requiring another keystroke for the standard use case. Apart from that there is a general problem with configuration files & options: The writer preparing a page can not rely on a well defined result since someone else (i.e. the wiki admin) configures something this way or the other in files the page writer has no access to46). – So for the time being I'd suggest to think about it and in case you're sure apply the patch given above.
Matthias Watermann 2007-02-18 11:44

Thanks, you're right about performance and complexity of configurability. The patch is enough for me. – Uwe 2007-02-18

There seems to be a bug in your highlighting. When inserting code like this:

#!/bin/bash
#
# here's the highlighting error
#  ---> ...
 
exit 1;

Everything from the tick will be in red but since it is a comment, highlighting should not change. – Dagobert 2007-06-08

Yes, I can confirm that problem. Please contact the GeSHi developers to let them fix it. Thank you.
Matthias Watermann 2007-06-09 18:16
Even worse… I think the whole commentary thing does not work. On the example above a keyword for would also be highlighted! – Dagobert 2007-06-11
I just checked with the latest GeSHi v1.0.7.19 but still the same. Scanning the source, however, I found that the leading DokuWiki developer is the author of the bash highlighting script as well. So probably contacting Andreas Gohr directly might help.
Matthias Watermann 2007-06-12 10:58
FYI, I have just checked it with the latest GeSHi v1.0.7.20 but still the same.
Mischa The Evil 2007-07-23 01:52
I've implemented a workaround for some of the problems. See the shell section above.
Matthias Watermann 2007-08-05 16:56
The GeSHi version 1.0.7.21 (2008-03-23) seems to have at least the bash comment bug fixed. You might want to try installing that version over the one that came with your DokuWiki.
Matthias Watermann 2008-04-05 19:25

The plugin doesn't seem to work with the latest DokuWiki edition 2007-06-26b… Any ideas?
Tetsuo 2008-02-20 23:12

Well, if you could be a little more specific – e.g. elaborate what “doesn't seem to work” means – someone just might have an idea. For all I know, it works without problems on numerous 2007-06-26b installations.
Matthias Watermann 2008-03-05 21:26
I was having issues with this as well (2007-06-26b) but then I spotted something very subtle between my code and the examples. In my text I was using:
  <code sql 1>SELECT blah FROM BLAH

</code> which wouldn't highlight. However changing it to:

  <code sql 1>
    SELECT foo FROM bar
  

</code> did. HTH
Fred 2008-03-19 13:31

This is discussed in the Markup section above, Fred. Thanks for emphasizing it.
Matthias Watermann 2008/03/26 18:35

Hi, I really enjoy that plugin. I saw another plugin witch feet to my need I'm talking about repo. I would like to have both in one code2 cool features and repo capabilities to include remote code. I made quite an ugly patch for code2 to do the work, you just need to do:

<code   ....>
repo>http://my_remote_url
</code>

Macfly 2008/04/16 22:09

Hi Macfly, please see the External Files section above. I've incorporated that functionality with the plugin in a hopefully secure way. However, I changed the keyword to ”extern” which seems more intuitive to me than “repo”. I hope it helps nonetheless.
Matthias Watermann 2008/05/26 10:25

I made a basic patch to make code2 work with odt plugin: (obsolete patch removed)
Macfly 2008/04/16 23:47

Thanks, Macfly, for your suggestion. Please see the Open Document Format section above. I hope you're happy with the results.
Matthias Watermann 2008/06/02 10:59

:!: I just installed this plugin (and it's excellent). I seem to have two problems:

  1. the hide functionality doesn't seem to work (which really isn't much of a problem, unless I'm using large code blocks)
  2. oddly the extern> function seems to run php code (and markup the result)! This isn't intended, right?

Thanks for this great plugin! Juice 2008-06-03

Umh, Juice, could you explain what you mean by “doesn't seem to work” with your first point? The ”hide” feature relies on a combination of presentation (i.e. CSS) and behaviour (i.e. JavaScript), so if one of them is disabled (or doesn't work as supposed) the respective code fragment is shown. And that feature only works in combination with a header/footer text (without it there wouldn't be an element to use for toggling the visibility). Could you show (or send by email if it's confidential) a wiki markup fragment that doesn't work as you expected?
About your second point: The ”extern>” option highlights whatever the specified URI happens to resolve to (i.e. whatever the remote server sends back, see above). That means if you want to highlight a PHP file that is stored on a remote server you'd have to use an URI that lets the remote server return the source code instead of executing it. This plugin – or any browser, for that matter – has no way to force a remote server to send source code of server-side scripts (in whatever language) if that server does not willingly so. If you have access to the remote server in question you should consider preparing some URIs for inclusion with your DokuWiki system (by means of this plugin), for example you could prepare a symbolic link (e.g. “ln -s thescript.php thescript.php.txt”) and then use that (latter) name in the URI of the ”extern>” option.
Matthias Watermann 2008/06/05 11:26
okay, thanks. I've got it sorted. Thanks! Juice 2008-08-15

:!:Help me. I don't know how to install. I got download file “syntax_plugin_code.zip” and upload /lib/plugins/code. But Not Operation. What is my mistake?

It appears, anonymous, that you forgot to extract the archive's contents:
$
$ cd /path/to/dokuwiki/lib/plugins
$ unzip syntax_plugin_code.zip
Archive:  syntax_plugin_code.zip
  inflating: code/style.css
  inflating: code/syntax_plugin_code.css
  inflating: code/script.js
  inflating: code/syntax_plugin_code.js
  inflating: code/syntax_plugin_code_styles.xml
  inflating: code/syntax.php
   creating: code/img/
 extracting: code/img/minus-11×11.gif
 extracting: code/img/plus-11×11.gif
$

Those two steps should do the trick.
Matthias Watermann 2008/07/24 09:56
I solved this problem. I downloaded by Internet Explore 6.0. But downloaded file is crashed. Then I downloaded by Fire Fox 3.0. Then the zip file is not crashed. 8-O M$ is bad.

:?:Great plugin! I was only wondering if there would be a way to set the size of the box, and to turn on line wrapping.
For the first, what I mean is for example, sometimes you may want to see the full code block, without needing to move the bars.
About line wrapping, the geshi engine supports this, so that lines that exceed the width would be wrapped. This produces nice results combined with line numbering.
What do you think?

Well, Anonymous, obviously you want to change the presentation of your code. So just adjust its rules i.e. modify the CSS according to your needs. As for the code's width replace the “max-width: 99%;” with something like “width: 80ex;” in the plugin stylesheet's first selector line.
To let your browser wrap the code lines add the “white-space: normal;” presentation rule in the same line. You might also want to replace the “overflow: auto;” rule by “overflow: visible;” to get rid of the browser-provided scrollbars.
However, if you're thinking about another markup option just have a look at the Usage section above: There's already quite a lot of possible option arguments (and their respective combinations). Adding another one would not only make it more difficult to remember the correct syntax but as well would involve (plugin) code additions which, ironically, would have to boil down to using the exact CSS rules mentioned above.
Matthias Watermann 2008/08/17 12:40
Thank you Matthias for your response! Playing with the CSS I'm able to fit my needs. On the other hand, have you realized that there is a new version of geshi? I know that it is more related to the DokuWiki distribution, but as it has some interesting new features, I tried to put it into my installation.
It works seamlessly, but there is an interesting improvement that splits line numbers and code into two columns of a table thus allowing you to select only the code and not the line numbers. To use that, you have to change the header type: $geshi→set_header_type(GESHI_HEADER_PRE_TABLE). However this is not working, somewhere DokuWiki seems to strip out the table part of the geshi output. It would be very nice to have a syntax highlighting such as this: http://qbnz.com/highlighter/geshi-doc.html#basic-usage.
Thank you for your attention and your plugin!
Glad, that you got your problems solved. – As about GeSHi v1.0.8 I have to admit that I'm not impressed at all. Actually, I'm rather disappointed with it. Just two examples: The line numbering you mention, is implemented by generating an OL47) thus completely changing (breaking) the semantics of every page including GeSHi processed code fragments; and for “bash” again some fool dumped an arbitrary selection of binary programs (which are _not_ and never have been shell keywords) to the keywords list which renders that module unusable for serious work.
Matthias Watermann 2008/08/24 14:45
I just got that working, changing the code on syntax.php. However, I couldn't get the PRE_TABLE to display well (it shows disaligned) so I chose to use GESHI_HEADER_DIV and $geshi→enable_line_numbers(GESHI_FANCY_LINE_NUMBERS,2) in case of having line numbers. In this latter case, I commented out the code for line numbering, just leaving this:
	$geshi->enable_line_numbers(GESHI_FANCY_LINE_NUMBERS,2);
	$geshi->set_line_style('background: #fcfcfc;', 'background: #f0f0f0;'); // For this to work, I disabled classes
	$aRenderer->doc .= $geshi->parse_code();
	$geshi = NULL;


I know this is not very elegant, but it works for me™. I should investigate further, as this breaks folding feature (no classes, no folding).

If it works for you it's just fine with me. However, I will not incorporate the use of broken GeSHi “features” into this plugin. In fact, I guess I have to investigate further as well and implement some kind of workaround for at least some of the problems caused by GeSHi (such as the wrong “keywords” in bash markup mentioned above).
Matthias Watermann 2008/08/24 14:40

:!: I just realized that line numbering isn't working for me anymore..?
I switched to using monobook as my template a while ago (I'm not sure if this has anything to do with it). Code highlighting appears to work well, but if I add the line number option highlighting turns off… Any ideas? Juice 2008-08-15

Hmmm, “ideas” … I'd guess, Juice, it's a problem with the template's main CSS file (e.g. too specific selectors there). You could try to replace all ”.code .lno” occurrences in the plugin's stylesheet by “pre.code .lno” (which makes the respective selector a little more specific) and see whether that helps. In case that doesn't work out you should provide the URL of a page demonstrating the problem. You can do this by private email if it shouldn't go public.
Matthias Watermann 2008/08/17 12:45

Referencing a local file

This is a nice plugin, I particularly appreciate the possibility to include EXTERNAL code file specifying their URL. Is there any possibility to do the same thing with a code file that should be located in the media folder of the Wiki. I added the following code which is not very elegant:

	case 'wiki':
		$nom=substr ($n[1],5);
		if ($handle = @fopen($nom, 'rb')) {
			$aMatch[1] = '';
			while (! @feof($handle)) {
				//XXX This might fail due to
				// memory constraints:
				$aMatch[1] .= @fread($handle, 0x8000);
			} // while
			@fclose($handle);
		} else {
			$aMatch = array($l,
				'Failed to retrieve: ' . $nom);
		} // if
		break;

Didier - 2008/09/03 11:59

Add $aMatch[1] = utf8_encode ($aMatch[1]); after @fclose($handle); to display ANSI files. Joachim 05.11.2011

I fail to see your point, Didier, I'm afraid. As the DokuWiki media files are accessible by their respective URLs you could use the “extern” mechanism to refer to them (see my response to Juice as of 2008/06/05 above). So why would you need just another keyword? – Apart from that I tend to avoid foreseeable security problems (therefor the “file” URI scheme, for example, is not supported). To implement an unfiltered access to raw files in the server's filesystem doesn't seem to be a good idea to me. At least tests for (sym-)links and devices would be needed before using those files. Additionally it doesn't look good to place raw filesystem path/names in a publicly accessible resource like wiki pages.
Matthias Watermann 2008/10/18 10:25
I think the idea is to be able to use internal or relative links without giving the full http link path of your own server, it took me a while to figure this out but here's one approach.

instead of
extern> wiki:D:\Sites\dokuwiki\data\media\root\namespace1\namespace2\filename1.txt
with the below patch instead you can do
extern> wiki:root:namespace1:namespace2:filename1.txt
Or if the code file your referencing is in the same namespace as the page it's being viewed from
extern> wiki:.filename1.txt
--- D:\Sites\dokuwiki\lib\plugins\code\syntax.php.bak	2008-07-22 11:36:40.000000000 +0100
+++ D:\Sites\dokuwiki\lib\plugins\code\syntax.php	2010-12-14 16:02:35.526048400 +0000
@@ -525,6 +525,25 @@
 		return 'protected';
 	} // getType()
 
+
+	// Get the Absouloute Http path to a media file
+	function getFullHttpLink($dokuwikilink) {
+		$retval = null;
+		// Get the Relative link
+		$xhtml_renderer = p_get_renderer('xhtml');
+		$xhtml_renderer->internalmedia($dokuwikilink,null,null,null,null,null,'linkonly');
+		// Extract the href attribute from the relative link
+		$dom = new DOMDocument();
+		@$dom->loadHTML($xhtml_renderer->doc);
+		$xpath = new DOMXPath($dom);
+		$tags = $xpath->query('//a');
+
+		foreach ($tags as $tag) {
+			if ($tag->hasAttribute('href')) { $retval = $tag->getAttribute('href'); }
+		}
+		return 'http://' . getenv("HTTP_HOST") . $retval;
+	}
+
 	/**
 	 * Handler to prepare matched data for the rendering process.
 	 *
@@ -585,6 +604,24 @@
 									'Failed to retrieve: ' . $n[1]);
 							} // if
 							break;
+						
+						case 'wiki':
+								$nom=substr ($n[1],5);
+								$nom=$this->getFullHttpLink($nom);
+								if ($handle = @fopen($nom, 'rb')) {
+									$aMatch[1] = '';
+									while (! @feof($handle)) {
+										//XXX This might fail due to
+										// memory constraints:
+										$aMatch[1] .= @fread($handle, 0x8000);
+									} // while
+									@fclose($handle);
+								} else {
+									$aMatch = array($l,
+										'Failed to retrieve: ' . substr ($n[1],5) . ' | ' . $nom);
+								} // if
+								break;
+
 						default:
 							$aMatch = array($l,
 								'Unsupported URL scheme: ' . $n[0]);

That indeed is a neat fix, working well - but sadly not for non-public wikis. Is there any way to work around that? I would like to upload c-Files and use them in wiki articles as collapsible source blocks..


XML, markup

Hi Matthias, great plugin, really.

I have two points, which I have in my mind after long time I am using code2 plugin.

First, for XML files in the comments like below the line break is inserted. Original code:

<!-- verbose>true</verbose -->

what we get as the output:

<!-- verbose>
true</verbose -->

And the next point is: Is it possible to extend plugin in a way, that I can combine the features of colorer and add some markup on the top? For example:

<configuration>
	{{markup color=red,bold}}<url>http://local-server.net:8080/manager</url>{{/markup}}
	<path>/rdf-manager</path>
</configuration>

or maybe you can give another way of how to achieve the same result with.

Also I think that header position flag should be followed by a space (at least non-character), so the Regex should be fixed as the following:

--- lib/plugins/code/syntax.php   (revision 139)
+++ lib/plugins/code/syntax.php   (working copy)
@@ -652,7 +652,7 @@
                        (?>
                                \x7C
                                # extract the position flag:
-                               ([bfht])?\s*
+                               (?:([bfht])(?>\s))?\s*
                                #       match 6
                                # extract the header,footer line:
                                ([^\x7C]+)
@@ -675,7 +675,7 @@
                */
                if (preg_match('/^\s*(?=\S)(?>(?>(diff)(?>\s+([cnrsu]?))?)|'
                        . '(?>([a-z][^\x7C\s]*)(?>\s+(\d\d*))?)|(\d\d*)|\s*)[^\x7C]*'
-                       . '(?>\x7C([bfht])?\s*([^\x7C]+)(?>\x7C\s*(h|s)?.*)?)?$/iu',
+                       . '(?>\x7C(?:([bfht])(?>\s))?\s*([^\x7C]+)(?>\x7C\s*(h|s)?.*)?)?$/iu',
                $aMatch[0], $hits)) {
                        unset($hits[0]);        // free mem
                        // $hits[1] = "diff"

Dmitry Katsubo 2009-09-03 13:24

Differentiating input and output in console

Hi, I just quickly hack a consoleio mode. It is strongly based on console. However it lets the user add markup (<in></in>) to indicate what's to be input at the console, and differentiate from the program's output. Default console output is not bold anymore, but user input is.

Index: code/syntax_plugin_code.css
===================================================================
--- code.orig/syntax_plugin_code.css    2010-10-28 06:05:53.N +0200
+++ code/syntax_plugin_code.css 2010-10-28 07:21:01.N +0200
@@ -173,9 +173,12 @@
 pre.console {
        background:             #333 none;
        color:                  #fff;
-       font-weight:    900;
        padding:                0.4ex 0.3ex 0.6ex 0.6ex;
 }
+div.dokuwiki pre.console span.input,
+pre.console span.input {
+       font-weight:    900;
+}
 div.dokuwiki pre.console .lno,
 pre.console .lno {
        background:             inherit;
Index: code/syntax.php
===================================================================
--- code.orig/syntax.php        2010-10-28 06:05:53.N +0200
+++ code/syntax.php     2010-10-28 09:58:28.N +0200
@@ -390,6 +390,29 @@
        } // _rawMarkup()
 
        /**
+        * Add the lines of the given <tt>$aText</tt> to the specified
+        * <tt>$aDoc</tt> beginning with the given <tt>$aStart</tt> linenumber.
+        *
+        * @param $aText String the text lines as prepared by <tt>handle()</tt>
+        * @param $addTags Boolean Used in "ODT" mode to suppress tagging
+        * the line numbers.
+        * @param $aMarker String The marker identifying user input.
+        * @private
+        * @since created 28-Oct-2010
+        * @author <a href="mailto:shtrom-doku@ssji.net">Olivier Mehani</a>
+        * @see render()
+        */
+       function _ioTag($aText, $addTags = TRUE, $aMarker="in") {
+               $search = array("&#60;$aMarker&#62;", "&#60;/$aMarker&#62;");
+               if ($addTags) {
+                       $replace = array("<span class=\"input\">", "</span>");
+               } else {
+                       $replace = array("", "");
+               }
+               return str_replace($search, $replace, $aText);
+       } // _ioTag()
+
+       /**
         * RegEx callback to replace SPAN tags in ODT mode.
         *
         * @param $aList Array A list of RegEx matches.
@@ -729,6 +752,7 @@
                } // if
                switch ($l) {
                        case 'console':
+                       case 'consoleio':
                                // nothing additional to setup here
                                break;
                        case 'diff':
@@ -857,6 +881,9 @@
                                if ('console' == $aData[2]) {
                                        $this->_rawMarkup($this->_entities($aData[1]),
                                                $aData[3], $aRenderer->doc, $aData[2]);
+                               } else if ('consoleio' == $aData[2]) {
+                                       $this->_rawMarkup($this->_ioTag($this->_entities($aData[1])),
+                                               $aData[3], $aRenderer->doc, 'console');
                                } else if ('diff' == $aData[2]) {
                                        $this->_entities($aData[1]);
                                        $aRenderer->doc .= '<pre class="code diff">';
@@ -993,6 +1020,9 @@
                                if ('console' == $aData[2]) {
                                        $this->_rawMarkup($this->_entities($aData[1]),
                                                $aData[3], $aData[0], $aData[2], FALSE);
+                               } else if ('consoleio' == $aData[2]) {
+                                       $this->_rawMarkup($this->_ioTag($this->_entities($aData[1], FALSE)),
+                                               $aData[3], $aRenderer->doc, 'console');
                                } else if ('diff' == $aData[2]) {
                                        $this->_addDiff($this->_entities($aData[1]),
                                                $aData[3], $aData[0]);
@@ -1055,6 +1085,9 @@
                        if ('console' == $aData[2]) {
                                $aRenderer->doc .=
                                        '<text:p text:style-name="Code_5f_Console">';
+                       } else if ('consoleio' == $aData[2]) {
+                               $aRenderer->doc .=
+                                       '<text:p text:style-name="Code_5f_Console">';
                        } else {
                                $aRenderer->doc .=
                                        '<text:p text:style-name="Code_5f_Standard">';
Index: code/style.css
===================================================================
--- code.orig/style.css 2010-02-21 15:37:56.N +0100
+++ code/style.css      2010-10-28 06:46:36.N +0200
@@ -31,7 +31,8 @@
 .code .st0{background:inherit;color:#900;}
 .code .sy0{background:inherit;color:#6c6;}
 pre.code a{border:none;}
-div.dokuwiki pre.console,pre.console{background:#333 none;color:#fff;font-weight:900;padding:0.4ex 0.3ex 0.6ex 0.6ex;}
+div.dokuwiki pre.console,pre.console{background:#333 none;color:#fff;padding:0.4ex 0.3ex 0.6ex 0.6ex;}
+div.dokuwiki pre.console span.input,pre.console span.input{font-weight:900;}
 div.dokuwiki pre.console .lno,pre.console .lno{background:inherit;color:#cff;font-size:smaller;font-style:italic;}
 div.code{margin:0.4ex 0;padding:0.4ex 0;}
 div.code pre.code{margin:0;}

— shtrom 2010/10/28 10:10


Two small problems

I'm so happy I found this plugin this morning! It's absolutely indispensable for me.

However, I found two small problems:

  1. In documenting some bits of software, I used strings like function _fixJS() as (intended) footer text. What happened instead is that the starting 'f' was taken as the footer marker, and the text was displayed as unction _fixJS(). :-)
    Suggestion: add one space to string to look for (h or f ) to decide whether the text should be header or footer.
  2. When footers (or headers) appeared, they did not appear to be active to fold the code block. At first I thought it might be a conflict with the Folded plugin which I also have installed, but digging a bit deeper I found that script.js was not being included at all. When I commented out the first if statement in function _fixJS() to force the script to be included, it suddenly started working. So, it appears the plugin is not entirely compatible with the latest version of DokuWiki (which BTW uses jQuery, so script for this show/hide functionality could be a lot simpler).

So, maybe a small update is in order?

marjoleinMarjolein Katsma
marjolein

Amsterdam, NL, Terra
2013-06-17 19:46



1) which is used by DokuWiki internally for syntax highlighting
2) And for a good reason, I'd say: It produces a bunch of markup that does not even validate.
3) that one as well was written initially to solve problems with the in some respects rather limited GeSHi
4) although both plugins will work side by side without disturbing each other
5) by private mail, directing me to think about CSS styling …
6) , 42) or ”t” for “top”
7) , 43) or ”b” for “bottom”
8) for “show”, the default
9) for “hide”
10) After all, the GeSHi engine is used very much the same way.
11) Meaning, that line numbering is not available for ”diff”. It wouldn't make any sense anyway. Just think about it.
12) The comments within the source file are suitable for the OSS doxygen tool, a documentation system for C++, C, Java, Objective-C, Python, IDL and to some extent PHP, C#, and D. — Since I'm working with different programming languages it's a great ease to have one tool that handles the docs for all of them.
13) The source archive contains a commented and indented stylesheet for your information.
14) Just be careful when modifying a CSS file: both the order and the selector groupings are important for CSS to work as intended/expected.
15) always assuming a browser implementing the DOM standard methods
16) which cannot be presumed with all browsers since CSS is an open standard and only ten years old
17) which would not be necessary if DokuWiki's JavaScript wouldn't be the mess it is but use a proper namespace concept instead
18) unless you reverse the “meaning” of the two CSS selectors mentioned above by changing the settings therein
19) otherwise those users would have to disable CSS completely in their browsers to see the hidden contents
20) That's the reason why I changed the markup in my source doc comments from <code> to <tt> – a practise you could follow if you experience such a problem.
21) i.e. not always!
22) LineFeed, char #10, ”\n
23) “Nac Mac Feegle! The Wee Free Men! Nae king! Nae quin! Nae laird! Nae master! We willna be fooled again!
24) like using the session program, or redirecting the I/O from/to a file, or using the tee tool, or …
25) This includes error pages such as ”404 - file not found”!
26) unless, of course, the remote server is broken
27) thus saving lots af bandwidth and transfer time
28) as opposed to linking
29) ignoring e.g. ”gopher” because I think wiki operators neither know about, nor use it anyway
30) mostly due to other plugins not yet implementing ODT support
31) at least up to its 2008-05-07 release
32) For example, code fragments in list items or (block)quotes sometimes just disappear although this plugin produces the appropriate ODT/XML markup which can be found in the exported ODT's content.xml file.
33) apart from replacing the whole ”bash” handling to bypass GeSHi completely, that is
34) which cause the same desaster but are less likely to occur
35) GeSHi's 1.0.7.20 version therefor should be avoided – or at least clean the KEYWORDS[2] array completely (lines 64 - 118 inclusive of the ”bash.php”” file) because none of its entries holds any bash specific keyword
36) The obvious reason seems to be that GeSHi undiscriminatingly uses PHP's builtin htmlspecialchars() function which handles UTF-8 characters only in PHP 4.3 and greater thus making it unusable for e.g. widely used RH9 installations and hosted systems with PHP 4.2 installed.
37) The reason being that older DokuWiki versions didn't support plugin related CSS and JavaScript files.
38) As far as it was mentioned the reason to avoid updating is that newer DokuWiki releases are using PHP functions not available with the respective OS/PHP combination.
39) while working, actually, at least with Firefox, Opera and M$I€
40) This is not a problem specific to this plugin but a general one with DokuWiki. Unfortunately its software architecture is poorly designed and a rampant mix of global variables, functions and objects – a characteristic it shares with a lot of other CMSs out there in the wild.
41) obsoleted by incorporating its ability into the Code plugin
44) It's a quite important point – but, alas, obviously unknown to many so-called “web designers” – always to adapt the reader's expectations and habits but not contradict them.
45) by both you and the plugin
46) turning the writing of a page into an experience of surprises
47) i.e. ordered list
plugin/code2.txt · Last modified: 2014/02/05 22:40 by 95.91.56.224