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" . ''; $CLOSE = ''; // 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 ?>