syntax_plugin_lists.php - A PHP4 class that implements
* a DokuWiki plugin for un/ordered lists block
* elements.
*
*
* Usage:
* * unordered item <
* - ordered item <
*
*
* Copyright (C) 2005, 2007 DFG/M.Watermann, D-10247 Berlin, FRG
* All rights reserved
* EMail : <support@mwat.de>
*
*
* 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
*
version 3 of the
* License, or (at your option) any later version.
* 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.
*
* @author Matthias Watermann
* @version $Id: syntax_plugin_lists.php,v 1.4 2007/08/15 12:36:19 matthias Exp $
* @since created 29-Aug-2005
*/
class syntax_plugin_lists extends DokuWiki_Syntax_Plugin {
/**
* @publicsection
*/
//@{
/**
* Tell the parser whether the plugin accepts syntax mode
* $aMode within its own markup.
*
* @param $aMode String The requested syntaxmode.
* @return Boolean TRUE unless $aMode is
* PLUGIN_LISTS (which would result in a
* FALSE method result).
* @public
* @see getAllowedTypes()
*/
function accepts($aMode) {
return (PLUGIN_LISTS != $aMode);
} // accepts()
/**
* Connect lookup pattern to lexer.
*
* @param $aMode String The desired rendermode.
* @public
* @see render()
*/
function connectTo($aMode) {
if (PLUGIN_LISTS == $aMode) {
return;
} // if
$this->Lexer->addEntryPattern(
'\n\x20{2,}[\x2A\x2D]\s*(?=(?s).*?[^\x5C]\x3C\n\n)',
$aMode, PLUGIN_LISTS);
$this->Lexer->addPattern(
'\n\x20{2,}[\x2A\x2D]\s*(?=(?s).*?[^\x5C]\x3C\n)', PLUGIN_LISTS);
$this->Lexer->addEntryPattern(
'\n\t+\s*[\x2A\x2D]\s*(?=(?s).*?[^\x5C]\x3C\n\n)',
$aMode, PLUGIN_LISTS);
$this->Lexer->addPattern(
'\n\t+\s*[\x2A\x2D]\s*(?=(?s).*?[^\x5C]\x3C\n)', PLUGIN_LISTS);
} // connectTo()
/**
* Get an associative array with plugin info.
*
*
* The returned array holds the following fields:
*
* - author
- Author of the plugin
* - email
- Email address to contact the author
* - date
- Last modified date of the plugin in
* YYYY-MM-DD format
* - name
- Name of the plugin
* - desc
- Short description of the plugin (Text only)
* - url
- Website with more information on the plugin
* (eg. syntax description)
*
* @return Array Information about this plugin class.
* @public
* @static
*/
function getInfo() {
return array(
'author' => 'Matthias Watermann',
'email' => 'support@mwat.de',
'date' => '2007-08-15',
'name' => 'List Syntax Plugin',
'desc' => 'Add HTML Style Un/Ordered Lists',
'url' => 'http://wiki.splitbrain.org/plugin:lists');
} // getInfo()
/**
* Define how this plugin is handled regarding paragraphs.
*
*
* This method is important for correct XHTML nesting. It returns
* one of the following values:
*
*
* - normal
- The plugin can be used inside paragraphs.
* - block
- Open paragraphs need to be closed before
* plugin output.
* - stack
- Special case: Plugin wraps other paragraphs.
*
* @return String 'normal' .
* @public
* @static
*/
function getPType() {
return 'normal';
} // getPType()
/**
* Where to sort in?
*
* @return Integer 8, an arbitrary value smaller
* Doku_Parser_Mode_listblock (10).
* @public
* @static
*/
function getSort() {
// class 'Doku_Parser_Mode_preformated' returns 20
// class 'Doku_Parser_Mode_listblock' returns 10
return 8;
} // getSort()
/**
* Get the type of syntax this plugin defines.
*
* @return String 'container'.
* @public
* @static
*/
function getType() {
return 'container';
} // getType()
/**
* Handler to prepare matched data for the rendering process.
*
*
* The $aState parameter gives the type of pattern
* which triggered the call to this method:
*
*
* - DOKU_LEXER_ENTER
* - a pattern set by addEntryPattern()
* - DOKU_LEXER_MATCHED
* - a pattern set by addPattern()
* - DOKU_LEXER_EXIT
* - a pattern set by addExitPattern()
* - DOKU_LEXER_SPECIAL
* - a pattern set by addSpecialPattern()
* - DOKU_LEXER_UNMATCHED
* - ordinary text encountered within the plugin's syntax mode
* which doesn't match any pattern.
*
* @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 [0] holds the current
* $aState, index [1] the match prepared for
* the render() method.
* @public
* @see render()
* @static
*/
function handle($aMatch, $aState, $aPos, &$aHandler) {
static $CHARS; static $ENTS;
if (! is_array($CHARS)) {
$CHARS = array('&','<', '>');
} // if
if (! is_array($ENTS)) {
$ENTS = array('&', '<', '>');
} // if
switch ($aState) {
case DOKU_LEXER_ENTER:
// fall through
case DOKU_LEXER_MATCHED:
$hits = array();
if (preg_match('|\n*((\s*)(.))|', $aMatch, $hits)) {
return array($aState, $hits[3],
strlen(str_replace(' ', "\t", $hits[2])));
} // if
return array($aState, $aMatch);
case DOKU_LEXER_UNMATCHED:
$hits = array();
if (preg_match('|^\s*\x3C$|', $aMatch, $hits)) {
return array(DOKU_LEXER_UNMATCHED, '', +1);
} // if
if (preg_match('|(.*?)\s+\x3C$|s', $aMatch, $hits)) {
return array(DOKU_LEXER_UNMATCHED,
str_replace($CHARS, $ENTS,
str_replace('\<', '<', $hits[1])), +1);
} // if
if (preg_match('|(.*[^\x5C])\x3C$|s', $aMatch, $hits)) {
return array(DOKU_LEXER_UNMATCHED,
str_replace($CHARS, $ENTS,
str_replace('\<', '<', $hits[1])), +1);
} // if
return array(DOKU_LEXER_UNMATCHED,
str_replace($CHARS, $ENTS,
str_replace('\<', '<', $aMatch)), -1);
case DOKU_LEXER_EXIT:
// end of list
default:
return array($aState);
} // switch
} // handle()
/**
* Add exit pattern to lexer.
*
* @public
*/
function postConnect() {
// make sure the RegEx 'eats' only _one_ LF:
$this->Lexer->addExitPattern('(?<=\x3C)\n(?=\n)', PLUGIN_LISTS);
} // postConnect()
/**
* Handle the actual output creation.
*
*
* The method checks for the given $aFormat and returns
* FALSE when a format isn't supported. $aRenderer
* contains a reference to the renderer object which is currently
* handling the rendering. The contents of $aData is the
* return value of the handle() method.
*
* @param $aFormat String The output format to generate.
* @param $aRenderer Object A reference to the renderer object.
* @param $aData Array The data created by the handle()
* method.
* @return Boolean TRUE if rendered successfully, or
* FALSE otherwise.
* @public
* @see handle()
*/
function render($aFormat, &$aRenderer, &$aData) {
if ('xhtml' != $aFormat) {
return FALSE;
} // if
static $LISTS = array('*' => 'ul', '-' => 'ol');
static $LEVEL = 1; // initial nesting level
static $INLI = array(); // INLI[LEVEL] :: 0==open LI, 1==open LI/P
static $CURRENT = array(); // CURRENT[LEVEL] :: * | -
switch ($aData[0]) {
case DOKU_LEXER_ENTER:
$CURRENT[$LEVEL] = $aData[1];
$hits = array();
if (preg_match('|\s*\s*$|i', $aRenderer->doc, $hits)) {
$hits = -strlen($hits[0]);
$aRenderer->doc = substr($aRenderer->doc, 0, $hits)
. '<' . $LISTS[$aData[1]] . '>';
} else {
$aRenderer->doc .= '
<' . $LISTS[$aData[1]] . '>';
} // if
// fall through to handle first item
case DOKU_LEXER_MATCHED:
// $aData[0] :: match state
// $aData[1] :: * | -
// $aData[2] :: nesting level
$diff = $aData[2] - $LEVEL;
if (0 < $diff) { // going up one level
$CURRENT[++$LEVEL] = $aData[1];
$hits = array();
if (preg_match('|\s*$|', $aRenderer->doc)) {
// need to open a new LI
$aRenderer->doc .= '<' . $LISTS[$CURRENT[$LEVEL]] . '>';
$INLI[$LEVEL - 1] = 0; // no closing P needed
} else if (preg_match('|\s*]*>\s*\s*$|',
$aRenderer->doc, $hits)) {
// replace rudimentary LI
$hits = -strlen($hits[0]);
$aRenderer->doc = substr($aRenderer->doc, 0, $hits)
. '
<' . $LISTS[$CURRENT[$LEVEL]] . '>';
$INLI[$LEVEL - 1] = 0; // no closing P needed
} else { // possibly open LI
if (isset($INLI[$LEVEL - 1])) {
if (0 < $INLI[$LEVEL - 1]) { // open LI P
$aRenderer->doc .= '<'
. $LISTS[$aData[1]] . '>';
$INLI[$LEVEL - 1] = 0;
} else { // open LI
$aRenderer->doc .= '<'
. $LISTS[$aData[1]] . '>';
} // if
} else { // no open LI
$aRenderer->doc .= '<'
. $LISTS[$aData[1]] . '>';
$INLI[$LEVEL - 1] = 0; // no closing P needed
} // if
} // if
} else if (0 > $diff) { // going back some levels
do {
--$LEVEL;
$aRenderer->doc .= ''
. $LISTS[$CURRENT[$LEVEL + 1]] . '>';
if (isset($INLI[$LEVEL])) {
$aRenderer->doc .= (0 < $INLI[$LEVEL])
? ''
: '';
} // if
} while (0 > ++$diff);
} else if ($aData[1] != $CURRENT[$LEVEL]) {
// list type changed
if (isset($INLI[$LEVEL])) {
$aRenderer->doc .= (0 < $INLI[$LEVEL])
? ''
: '';
} // if
$aRenderer->doc .= '' . $LISTS[$CURRENT[$LEVEL]]
. '><' . $LISTS[$aData[1]] . '>';
$CURRENT[$LEVEL] = $aData[1];
} // if
$aRenderer->doc .= '';
$INLI[$LEVEL] = 1; // closing P needed
return TRUE;
case DOKU_LEXER_UNMATCHED:
// $aData[0] :: match state
// $aData[1] :: text
// $aData[2] :: +1(EoT), -1(start/inbetween)
if (0 < $aData[2]) {
// last part of item's text
if (strlen($aData[1])) {
if (isset($INLI[$LEVEL])) {
$aRenderer->doc .= (0 < $INLI[$LEVEL]) // LI P
? $aData[1] . '
'
: '' . $aData[1] . '
';
} else { // no LI
if (1 < $LEVEL) { // assume a trailing LI text
--$LEVEL;
$aRenderer->doc .= ''
. $LISTS[$CURRENT[$LEVEL + 1]] . '>'
. $aData[1] . '
';
} else {
//XXX: There must be no data w/o context; the markup is broken. Whatever we
// could do it would be WRONG (and break XHMTL validity); hence comment:
$aRenderer->doc .= '';
} // if
} // if
} else { // empty data
$hits = array();
if (preg_match('|\s*]*>\s*\s*$|',
$aRenderer->doc, $hits)) {
$hits = -strlen($hits[0]);
// remove empty list item
$aRenderer->doc = substr($aRenderer->doc, 0, $hits);
} else if (preg_match('|\s*
\s*$|',
$aRenderer->doc, $hits)) {
$hits = -strlen($hits[0]);
$aRenderer->doc =
substr($aRenderer->doc, 0, $hits) . '
';
} else if (isset($INLI[$LEVEL])) {
$aRenderer->doc .= (0 < $INLI[$LEVEL])
? ''
: '';
} // if
} // if
unset($INLI[$LEVEL]);
} else {
// item part between substitutions or nested blocks
if (isset($INLI[$LEVEL])) {
if (0 < $INLI[$LEVEL]) { // LI P
$aRenderer->doc .= $aData[1];
$INLI[$LEVEL] = 1;
} else { // LI
$aRenderer->doc .= '' . $aData[1];
} // if
} else { // data w/o context
if (1 < $LEVEL) { // assume a trailing LI text
--$LEVEL;
$aRenderer->doc .= ''
. $LISTS[$CURRENT[$LEVEL + 1]] . '>
'
. $aData[1];
$INLI[$LEVEL] = 1;
} else {
$aRenderer->doc .= $aData[1];
} // if
} // if
} // if
return TRUE;
case DOKU_LEXER_EXIT:
while (1 < $LEVEL) {
--$LEVEL;
$aRenderer->doc .= ''. $LISTS[$CURRENT[$LEVEL + 1]] .'>';
if (isset($INLI[$LEVEL])) {
$aRenderer->doc .= (0 < $INLI[$LEVEL])
? '
'
: '';
} // if
} // while
// Since we have to use PType 'normal' we must open
// a new paragraph for the following text
$aRenderer->doc = preg_replace('|\s*\s*
\s*|', '',
$aRenderer->doc) . ''. $LISTS[$CURRENT[$LEVEL]] .'>';
$CURRENT = $INLI = array();
$LEVEL = 1;
default:
return TRUE;
} // switch
} // render()
//@}
} // class syntax_plugin_lists
} // if
?>