1<?php 2if (! class_exists('syntax_plugin_diff')) { 3 if (! defined('DOKU_PLUGIN')) { 4 if (! defined('DOKU_INC')) { 5 define('DOKU_INC', realpath(dirname(__FILE__) . '/../../') . '/'); 6 } // if 7 define('DOKU_PLUGIN', DOKU_INC . 'lib/plugins/'); 8 } // if 9 // Include parent class: 10 require_once(DOKU_PLUGIN . 'syntax.php'); 11 12/** 13 * <tt>syntax_plugin_diff.php </tt>- A PHP4 class that implements a 14 * plugin for highlighting <tt>diff</tt> output in <tt>DokuWiki</tt> 15 * pages. 16 * 17 * <p> 18 * The purpose of this plugin is to provide a facility for inserting 19 * a <tt>diff</tt> file into a Wiki page. While this could be done by 20 * using the <tt>code</tt> tag this plugin additionally provides some 21 * visual feedback (so-called "syntax highlighting") by emphasizing 22 * added/deleted lines using CSS rules. 23 * </p> 24 * <p> 25 * Three types of <tt>diff</tt> output formats are supported: 26 * </p> 27 * <dl> 28 * <dt><tt>unified</tt></dt> 29 * <dd>The output of the <tt>diff</tt> program with the <tt>-u</tt> 30 * commandline format option.</dd> 31 * <dt><tt>context</tt></dt> 32 * <dd>The output of the <tt>diff</tt> program with the <tt>-c</tt> 33 * commandline format option.</dd> 34 * <dt><tt>context</tt></dt> 35 * <dd>The output of the <tt>diff</tt> program with the <tt>-n</tt> 36 * commandline format option.</dd> 37 * <dt><tt>simple</tt></dt> 38 * <dd>The output of the <tt>diff</tt> program without any commandline 39 * format option.</dd> 40 * </dl><pre> 41 * Copyright (C) 2005, 2010 M.Watermann, D-10247 Berlin, FRG 42 * All rights reserved 43 * EMail : <support@mwat.de> 44 * </pre> 45 * <div class="disclaimer"> 46 * This program is free software; you can redistribute it and/or modify 47 * it under the terms of the GNU General Public License as published by 48 * the Free Software Foundation; either 49 * <a href="http://www.gnu.org/licenses/gpl.html">version 3</a> of the 50 * License, or (at your option) any later version.<br> 51 * This software is distributed in the hope that it will be useful, but 52 * WITHOUT ANY WARRANTY; without even the implied warranty of 53 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 54 * General Public License for more details. 55 * </div> 56 * @author <a href="mailto:support@mwat.de">Matthias Watermann</a> 57 * @version <tt>$Id: syntax_plugin_diff.php,v 1.11 2010/02/22 10:49:59 matthias Exp $</tt> 58 * @since created 14-Aug-2005 59 */ 60class syntax_plugin_diff extends DokuWiki_Syntax_Plugin { 61 62 /** 63 * @privatesection 64 */ 65 //@{ 66 67 /** 68 * Prepare the markup to render the DIFF text. 69 * 70 * @param $aText String The DIFF text to markup. 71 * @param $aFormat String The DIFF format used ('u', 'c', 'n|r', 's'). 72 * @param $aDoc String Reference to the current renderer's 73 * <tt>doc</tt> property. 74 * @return Boolean <tt>TRUE</tt>. 75 * @private 76 * @see render() 77 */ 78 function _addDiff(&$aText, &$aFormat, &$aDoc) { 79 // Since we're inside a PRE block we need the leading LFs: 80 $ADD = "\n" . '<span class="diff-addedline">'; 81 $DEL = "\n" . '<span class="diff-deletedline">'; 82 $HEAD = "\n" . '<span class="diff-blockheader">'; 83 $CLOSE = '</span>'; 84 // Common headers for all formats; 85 // the RegEx needs at least ')#' appended! 86 $DiffHead = '#\n((?:diff\s[^\n]*)|(?:Index:\s[^\n]*)|(?:={60,})' 87 . '|(?:RCS file:\s[^\n]*)|(?:retrieving revision [0-9][^\n]*)'; 88 switch ($aFormat) { 89 case 'u': // unified output 90 $aDoc .= preg_replace( 91 array($DiffHead . '|(?:@@[^\n]*))#', 92 '|\n(\+[^\n]*)|', 93 '|\n(\-[^\n]*)|'), 94 array($HEAD . '\1' . $CLOSE, 95 $ADD . '\1' . $CLOSE, 96 $DEL . '\1' . $CLOSE), 97 $aText); 98 return TRUE; 99 case 'c': // context output 100 $sections = preg_split('|(\n\*{5,})|', 101 preg_replace($DiffHead . ')#', 102 $HEAD . '\1' . $CLOSE, 103 $aText), 104 -1, PREG_SPLIT_DELIM_CAPTURE); 105 $sections[0] = preg_replace( 106 array('|\n(\-{3}[^\n]*)|', 107 '|\n(\*{3}[^\n]*)|'), 108 array($ADD . '\1' . $CLOSE, 109 $DEL . '\1' . $CLOSE), 110 $sections[0]); 111 $c = count($sections); 112 for ($i = 1; $c > $i; ++$i) { 113 $hits = array(); 114 if (preg_match('|^\n(\*{5,})|', 115 $sections[$i], $hits)) { 116 unset($hits[0]); 117 $sections[$i] = $HEAD . $hits[1] . $CLOSE; 118 } else if (preg_match('|^\n(\x2A{3}\s[^\n]*)(.*)|s', 119 $sections[$i], $hits)) { 120 unset($hits[0]); // free mem 121 $parts = preg_split('|\n(\-{3}\s[^\n]*)|', 122 $hits[2], -1, PREG_SPLIT_DELIM_CAPTURE); 123 // $parts[0] == OLD code 124 $parts[0] = preg_replace('|\n([!\-][^\n]*)|', 125 $DEL . '\1' . $CLOSE, $parts[0]); 126 // $parts[1] == head of NEW code 127 $parts[1] = $ADD . $parts[1] . $CLOSE; 128 // $parts[2] == NEW code 129 $parts[2] = preg_replace( 130 array('|\n([!\x2B][^\n]*)|', 131 '|\n(\x2A{3}[^\n]*)|'), 132 array($ADD . '\1' . $CLOSE, 133 $DEL . '\1' . $CLOSE), 134 $parts[2]); 135 if (isset($parts[3])) { 136 // TRUE when handling multi-file patches 137 $parts[3] = preg_replace('|^(\x2D{3}[^\n]*)|', 138 $ADD . '\1' . $CLOSE, $parts[3]); 139 } // if 140 $sections[$i] = $DEL . $hits[1] . $CLOSE 141 . implode('', $parts); 142 } // if 143 // ELSE: leave $sections[$i] as is 144 } // for 145 $aDoc .= implode('', $sections); 146 return TRUE; 147 case 'n': // RCS output 148 // Only added lines are there so we highlight just the 149 // diff indicators while leaving the text alone. 150 $aDoc .= preg_replace( 151 array($DiffHead . ')#', 152 '|\n(d[0-9]+\s+[0-9]+)|', 153 '|\n(a[0-9]+\s+[0-9]+)|'), 154 array($HEAD . '\1' . $CLOSE, 155 $DEL . '\1' . $CLOSE, 156 $ADD . '\1' . $CLOSE), 157 $aText); 158 return TRUE; 159 case 's': // simple output 160 $aDoc .= preg_replace( 161 array($DiffHead . '|((?:[0-9a-z]+(?:,[0-9a-z]+)*)(?:[^\n]*)))#', 162 '|\n(\x26#60;[^\n]*)|', 163 '|\n(\x26#62;[^\n]*)|'), 164 array($HEAD . '\1' . $CLOSE, 165 $DEL . '\1' . $CLOSE, 166 $ADD . '\1' . $CLOSE), 167 $aText); 168 return TRUE; 169 default: // unknown diff format 170 $aDoc .= $aText; // just append any unrecognized text 171 return TRUE; 172 } // switch 173 } // _addDiff() 174 175 //@} 176 /** 177 * @publicsection 178 */ 179 //@{ 180 181 /** 182 * Tell the parser whether the plugin accepts syntax mode 183 * <tt>$aMode</tt> within its own markup. 184 * 185 * <p> 186 * This method returns <tt>FALSE</tt> since no other (DokuWiki) 187 * types are allowed within a <tt>diff</tt> section. 188 * </p> 189 * @param $aMode String The requested syntaxmode. 190 * @return Boolean <tt>FALSE</tt> (always). 191 * @public 192 */ 193 function accepts($aMode) { 194 return FALSE; 195 } // accepts() 196 197 /** 198 * Connect lookup pattern to lexer. 199 * 200 * @param $aMode String The desired rendermode. 201 * @public 202 * @see render() 203 */ 204 function connectTo($aMode) { 205 $this->Lexer->addEntryPattern( 206 '\x3Cdiff(?=[^\n\r]*?\x3E.*?\n\x3C\x2Fdiff\x3E)', 207 $aMode, 'plugin_diff'); 208 } // connectTo() 209 210 /** 211 * Get an associative array with plugin info. 212 * 213 * <p> 214 * The returned array holds the following fields: 215 * <dl> 216 * <dt>author</dt><dd>Author of the plugin</dd> 217 * <dt>email</dt><dd>Email address to contact the author</dd> 218 * <dt>date</dt><dd>Last modified date of the plugin in 219 * <tt>YYYY-MM-DD</tt> format</dd> 220 * <dt>name</dt><dd>Name of the plugin</dd> 221 * <dt>desc</dt><dd>Short description of the plugin (Text only)</dd> 222 * <dt>url</dt><dd>Website with more information on the plugin 223 * (eg. syntax description)</dd> 224 * </dl> 225 * @return Array Information about this plugin class. 226 * @public 227 * @static 228 */ 229 function getInfo() { 230 return array( 231 'author' => 'Matthias Watermann', 232 'email' => 'support@mwat.de', 233 'date' => '2010-02-22', 234 'name' => 'diff Syntax Plugin', 235 'desc' => 'Add diff Style [<diff> ... </diff>]', 236 'url' => 'http://www.dokuwiki.org/plugin:diff'); 237 } // getInfo() 238 239 /** 240 * Define how this plugin is handled regarding paragraphs. 241 * 242 * <p> 243 * This method is important for correct XHTML nesting. It returns 244 * one of the following values: 245 * </p> 246 * <dl> 247 * <dt>normal</dt><dd>The plugin can be used inside paragraphs.</dd> 248 * <dt>block</dt><dd>Open paragraphs need to be closed before 249 * plugin output.</dd> 250 * <dt>stack</dt><dd>Special case. Plugin wraps other paragraphs.</dd> 251 * </dl> 252 * @return String <tt>'block'</tt> . 253 * @public 254 * @static 255 */ 256 function getPType() { 257 return 'block'; 258 } // getPType() 259 260 /** 261 * Where to sort in? 262 * 263 * <p> 264 * This method returns <tt>174</tt> an arbitrary value between 265 * <tt>Doku_Parser_Mode_unformatted</tt> and 266 * <tt>Doku_Parser_Mode_php</tt> (180). 267 * </p> 268 * @return Integer <tt>174</tt>. 269 * @public 270 * @static 271 */ 272 function getSort() { 273 return 174; 274 } // getSort() 275 276 /** 277 * Get the type of syntax this plugin defines. 278 * 279 * @return String <tt>'protected'</tt>. 280 * @public 281 * @static 282 */ 283 function getType() { 284 return 'protected'; 285 } // getType() 286 287 /** 288 * Handler to prepare matched data for the rendering process. 289 * 290 * <p> 291 * The <tt>$aState</tt> parameter gives the type of pattern which 292 * triggered the call to this method: 293 * </p><dl> 294 * <dt>DOKU_LEXER_ENTER</dt> 295 * <dd>a pattern set by <tt>addEntryPattern()</tt></dd> 296 * <dt>DOKU_LEXER_EXIT</dt> 297 * <dd> a pattern set by <tt>addExitPattern()</tt></dd> 298 * <dt>DOKU_LEXER_UNMATCHED</dt> 299 * <dd>ordinary text encountered within the plugin's syntax mode 300 * which doesn't match any pattern.</dd> 301 * </dl> 302 * @param $aMatch String The text matched by the patterns. 303 * @param $aState Integer The lexer state for the match. 304 * @param $aPos Integer The character position of the matched text. 305 * @param $aHandler Object Reference to the Doku_Handler object. 306 * @return Array Index <tt>[0]</tt> holds the current 307 * <tt>$aState</tt>, index <tt>[1]</tt> the diff type (i.e. either 308 * <tt>'c'</tt> for 'context' format, <tt>'u'</tt> for 'unified' 309 * format or <tt>'s'</tt> for the 'simple' format) and 310 * index <tt>[2]</tt> holding the diff's patch text. 311 * @public 312 * @see render() 313 * @static 314 */ 315 function handle($aMatch, $aState, $aPos, &$aHandler) { 316 if (DOKU_LEXER_UNMATCHED == $aState) { 317 $aMatch = explode('>', $aMatch, 2); 318 if ("\n" != $aMatch[1]{0}) { 319 // A leading LF is needed to recognize and handle 320 // the very first line with all the REs used. 321 $aMatch[1] = "\n" . $aMatch[1]; 322 } // if 323 } else { 324 return array($aState); 325 } // if 326 $aMatch[0] = strtolower(trim($aMatch[0])) . '?'; 327 switch ($aMatch[0] = $aMatch[0]{0}) { 328 case 'u': // DIFF cmdline switch for 'unified' 329 case 'c': // DIFF cmdline switch for 'context' 330 case 'n': // DIFF cmdline switch for 'RCS' 331 case 's': 332 // We believe the format hint ... 333 // (or should we be more suspicious?) 334 break; 335 case 'r': // Mnemonic for 'RCS' 336 $aMatch[0] = 'n'; 337 break; 338 default: // try to figure out the diff format actually used 339 if (preg_match( 340 '|\n(?:\x2A{5,}\n\x2A{3}\s[1-9]+.*?\x2A{4}\n.+?)+|s', 341 $aMatch[1])) { 342 $aMatch[0] = 'c'; 343 } else if (preg_match( 344 '|\n@@\s\-[0-9]+,[0-9]+[ \+,0-9]+?@@\n.+\n|s', 345 $aMatch[1])) { 346 $aMatch[0] = 'u'; 347 } else if (preg_match( 348 '|\n[ad][0-9]+\s+[0-9]+\r?\n|', $aMatch[1])) { 349 // We've to check this _before_ 'simple' since the REs 350 // are similar (this one is slightly more specific) 351 $aMatch[0] = 'n'; 352 } else if (preg_match( 353 '|\n(?:[0-9a-z]+(?:,[0-9a-z]+)*)(?:[^\n]*\n.*?)+|', 354 $aMatch[1])) { 355 $aMatch[0] = 's'; 356 } else { 357 $aMatch[0] = '?'; 358 } // if 359 } // switch 360 return array($aState, $aMatch[0], str_replace( 361 array('&', '<', '>', "\t"), 362 array('&', '<', '>', ' '), 363 $aMatch[1])); 364 } // handle() 365 366 /** 367 * Add exit pattern to lexer. 368 * 369 * @public 370 */ 371 function postConnect() { 372 $this->Lexer->addExitPattern('(?<=\n)\x3C\x2Fdiff\x3E', 'plugin_diff'); 373 } // postConnect() 374 375 /** 376 * Handle the actual output creation. 377 * 378 * <p> 379 * The method checks for the given <tt>$aMode</tt> and returns 380 * <tt>FALSE</tt> when a mode isn't supported. <tt>$aRenderer</tt> 381 * contains a reference to the renderer object which is currently 382 * handling the rendering. The contents of <tt>$aData</tt> is the 383 * return value of the <tt>handle()</tt> method. 384 * </p> 385 * @param $aFormat String The output format to being tendered. 386 * @param $aRenderer Object A reference to the renderer object. 387 * @param $aData Array The data created by the <tt>handle()</tt> 388 * method. 389 * @return Boolean <tt>TRUE</tt> if rendered correctly, or 390 * <tt>FALSE</tt> otherwise. 391 * @public 392 * @see handle() 393 */ 394 function render($aFormat, &$aRenderer, &$aData) { 395 if ('xhtml' != $aFormat) { 396 return FALSE; 397 } // if 398 switch ($aData[0]) { 399 case DOKU_LEXER_UNMATCHED: 400 return $this->_addDiff($aData[2], $aData[1], $aRenderer->doc); 401 case DOKU_LEXER_ENTER: 402 $aRenderer->doc .= '<pre class="code diff">'; 403 return TRUE; 404 case DOKU_LEXER_EXIT: 405 $aRenderer->doc .= '</pre>'; 406 default: 407 return TRUE; 408 } // switch 409 } // render() 410 411 //@} 412} // class syntax_plugin_diff 413} // if 414?> 415