syntax_plugin_diff.php - A PHP4 class that implements a
* plugin for highlighting diff output in DokuWiki
* pages.
*
*
* The purpose of this plugin is to provide a facility for inserting
* a diff file into a Wiki page. While this could be done by
* using the code tag this plugin additionally provides some
* visual feedback (so-called "syntax highlighting") by emphasizing
* added/deleted lines using CSS rules.
*
*
* Three types of diff output formats are supported:
*
*
* - unified
* - The output of the diff program with the -u
* commandline format option.
* - context
* - The output of the diff program with the -c
* commandline format option.
* - context
* - The output of the diff program with the -n
* commandline format option.
* - simple
* - The output of the diff program without any commandline
* format option.
*
* Copyright (C) 2005, 2010 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_diff.php,v 1.11 2010/02/22 10:49:59 matthias Exp $
* @since created 14-Aug-2005
*/
class syntax_plugin_diff extends DokuWiki_Syntax_Plugin {
/**
* @privatesection
*/
//@{
/**
* 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
* doc property.
* @return Boolean TRUE.
* @private
* @see render()
*/
function _addDiff(&$aText, &$aFormat, &$aDoc) {
// Since we're inside a PRE block we need the leading LFs:
$ADD = "\n" . '';
$DEL = "\n" . '';
$HEAD = "\n" . '';
// 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()
//@}
/**
* @publicsection
*/
//@{
/**
* Tell the parser whether the plugin accepts syntax mode
* $aMode within its own markup.
*
*
* This method returns FALSE since no other (DokuWiki)
* types are allowed within a diff section.
*
* @param $aMode String The requested syntaxmode.
* @return Boolean FALSE (always).
* @public
*/
function accepts($aMode) {
return FALSE;
} // accepts()
/**
* Connect lookup pattern to lexer.
*
* @param $aMode String The desired rendermode.
* @public
* @see render()
*/
function connectTo($aMode) {
$this->Lexer->addEntryPattern(
'\x3Cdiff(?=[^\n\r]*?\x3E.*?\n\x3C\x2Fdiff\x3E)',
$aMode, 'plugin_diff');
} // 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' => '2010-02-22',
'name' => 'diff Syntax Plugin',
'desc' => 'Add diff Style [ ... ]',
'url' => 'http://www.dokuwiki.org/plugin:diff');
} // 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 'block' .
* @public
* @static
*/
function getPType() {
return 'block';
} // getPType()
/**
* Where to sort in?
*
*
* This method returns 174 an arbitrary value between
* Doku_Parser_Mode_unformatted and
* Doku_Parser_Mode_php (180).
*
* @return Integer 174.
* @public
* @static
*/
function getSort() {
return 174;
} // getSort()
/**
* Get the type of syntax this plugin defines.
*
* @return String 'protected'.
* @public
* @static
*/
function getType() {
return 'protected';
} // 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_EXIT
* - a pattern set by addExitPattern()
* - 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 diff type (i.e. either
* 'c' for 'context' format, 'u' for 'unified'
* format or 's' for the 'simple' format) and
* index [2] holding the diff's patch text.
* @public
* @see render()
* @static
*/
function handle($aMatch, $aState, $aPos, &$aHandler) {
if (DOKU_LEXER_UNMATCHED == $aState) {
$aMatch = explode('>', $aMatch, 2);
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
} else {
return array($aState);
} // if
$aMatch[0] = strtolower(trim($aMatch[0])) . '?';
switch ($aMatch[0] = $aMatch[0]{0}) {
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'
$aMatch[0] = 'n';
break;
default: // try to figure out the diff format actually used
if (preg_match(
'|\n(?:\x2A{5,}\n\x2A{3}\s[1-9]+.*?\x2A{4}\n.+?)+|s',
$aMatch[1])) {
$aMatch[0] = 'c';
} else if (preg_match(
'|\n@@\s\-[0-9]+,[0-9]+[ \+,0-9]+?@@\n.+\n|s',
$aMatch[1])) {
$aMatch[0] = '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 similar (this one is slightly more specific)
$aMatch[0] = 'n';
} else if (preg_match(
'|\n(?:[0-9a-z]+(?:,[0-9a-z]+)*)(?:[^\n]*\n.*?)+|',
$aMatch[1])) {
$aMatch[0] = 's';
} else {
$aMatch[0] = '?';
} // if
} // switch
return array($aState, $aMatch[0], str_replace(
array('&', '<', '>', "\t"),
array('&', '<', '>', ' '),
$aMatch[1]));
} // handle()
/**
* Add exit pattern to lexer.
*
* @public
*/
function postConnect() {
$this->Lexer->addExitPattern('(?<=\n)\x3C\x2Fdiff\x3E', 'plugin_diff');
} // postConnect()
/**
* Handle the actual output creation.
*
*
* The method checks for the given $aMode and returns
* FALSE when a mode 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 being tendered.
* @param $aRenderer Object A reference to the renderer object.
* @param $aData Array The data created by the handle()
* method.
* @return Boolean TRUE if rendered correctly, or
* FALSE otherwise.
* @public
* @see handle()
*/
function render($aFormat, &$aRenderer, &$aData) {
if ('xhtml' != $aFormat) {
return FALSE;
} // if
switch ($aData[0]) {
case DOKU_LEXER_UNMATCHED:
return $this->_addDiff($aData[2], $aData[1], $aRenderer->doc);
case DOKU_LEXER_ENTER:
$aRenderer->doc .= '';
return TRUE;
case DOKU_LEXER_EXIT:
$aRenderer->doc .= '
';
default:
return TRUE;
} // switch
} // render()
//@}
} // class syntax_plugin_diff
} // if
?>