raw
mp-wp_genesis           1 <?php
mp-wp_genesis 2 /**
mp-wp_genesis 3 * General API for generating and formatting diffs - the differences between
mp-wp_genesis 4 * two sequences of strings.
mp-wp_genesis 5 *
mp-wp_genesis 6 * The original PHP version of this code was written by Geoffrey T. Dairiki
mp-wp_genesis 7 * <dairiki@dairiki.org>, and is used/adapted with his permission.
mp-wp_genesis 8 *
mp-wp_genesis 9 * $Horde: framework/Text_Diff/Diff.php,v 1.26 2008/01/04 10:07:49 jan Exp $
mp-wp_genesis 10 *
mp-wp_genesis 11 * Copyright 2004 Geoffrey T. Dairiki <dairiki@dairiki.org>
mp-wp_genesis 12 * Copyright 2004-2008 The Horde Project (http://www.horde.org/)
mp-wp_genesis 13 *
mp-wp_genesis 14 * See the enclosed file COPYING for license information (LGPL). If you did
mp-wp_genesis 15 * not receive this file, see http://opensource.org/licenses/lgpl-license.php.
mp-wp_genesis 16 *
mp-wp_genesis 17 * @package Text_Diff
mp-wp_genesis 18 * @author Geoffrey T. Dairiki <dairiki@dairiki.org>
mp-wp_genesis 19 */
mp-wp_genesis 20 class Text_Diff {
mp-wp_genesis 21
mp-wp_genesis 22 /**
mp-wp_genesis 23 * Array of changes.
mp-wp_genesis 24 *
mp-wp_genesis 25 * @var array
mp-wp_genesis 26 */
mp-wp_genesis 27 var $_edits;
mp-wp_genesis 28
mp-wp_genesis 29 /**
mp-wp_genesis 30 * Computes diffs between sequences of strings.
mp-wp_genesis 31 *
mp-wp_genesis 32 * @param string $engine Name of the diffing engine to use. 'auto'
mp-wp_genesis 33 * will automatically select the best.
mp-wp_genesis 34 * @param array $params Parameters to pass to the diffing engine.
mp-wp_genesis 35 * Normally an array of two arrays, each
mp-wp_genesis 36 * containing the lines from a file.
mp-wp_genesis 37 */
mp-wp_genesis 38 function Text_Diff($engine, $params)
mp-wp_genesis 39 {
mp-wp_genesis 40 // Backward compatibility workaround.
mp-wp_genesis 41 if (!is_string($engine)) {
mp-wp_genesis 42 $params = array($engine, $params);
mp-wp_genesis 43 $engine = 'auto';
mp-wp_genesis 44 }
mp-wp_genesis 45
mp-wp_genesis 46 if ($engine == 'auto') {
mp-wp_genesis 47 $engine = extension_loaded('xdiff') ? 'xdiff' : 'native';
mp-wp_genesis 48 } else {
mp-wp_genesis 49 $engine = basename($engine);
mp-wp_genesis 50 }
mp-wp_genesis 51
mp-wp_genesis 52 // WP #7391
mp-wp_genesis 53 require_once dirname(__FILE__).'/Diff/Engine/' . $engine . '.php';
mp-wp_genesis 54 $class = 'Text_Diff_Engine_' . $engine;
mp-wp_genesis 55 $diff_engine = new $class();
mp-wp_genesis 56
mp-wp_genesis 57 $this->_edits = call_user_func_array(array($diff_engine, 'diff'), $params);
mp-wp_genesis 58 }
mp-wp_genesis 59
mp-wp_genesis 60 /**
mp-wp_genesis 61 * Returns the array of differences.
mp-wp_genesis 62 */
mp-wp_genesis 63 function getDiff()
mp-wp_genesis 64 {
mp-wp_genesis 65 return $this->_edits;
mp-wp_genesis 66 }
mp-wp_genesis 67
mp-wp_genesis 68 /**
mp-wp_genesis 69 * Computes a reversed diff.
mp-wp_genesis 70 *
mp-wp_genesis 71 * Example:
mp-wp_genesis 72 * <code>
mp-wp_genesis 73 * $diff = new Text_Diff($lines1, $lines2);
mp-wp_genesis 74 * $rev = $diff->reverse();
mp-wp_genesis 75 * </code>
mp-wp_genesis 76 *
mp-wp_genesis 77 * @return Text_Diff A Diff object representing the inverse of the
mp-wp_genesis 78 * original diff. Note that we purposely don't return a
mp-wp_genesis 79 * reference here, since this essentially is a clone()
mp-wp_genesis 80 * method.
mp-wp_genesis 81 */
mp-wp_genesis 82 function reverse()
mp-wp_genesis 83 {
mp-wp_genesis 84 if (version_compare(zend_version(), '2', '>')) {
mp-wp_genesis 85 $rev = clone($this);
mp-wp_genesis 86 } else {
mp-wp_genesis 87 $rev = $this;
mp-wp_genesis 88 }
mp-wp_genesis 89 $rev->_edits = array();
mp-wp_genesis 90 foreach ($this->_edits as $edit) {
mp-wp_genesis 91 $rev->_edits[] = $edit->reverse();
mp-wp_genesis 92 }
mp-wp_genesis 93 return $rev;
mp-wp_genesis 94 }
mp-wp_genesis 95
mp-wp_genesis 96 /**
mp-wp_genesis 97 * Checks for an empty diff.
mp-wp_genesis 98 *
mp-wp_genesis 99 * @return boolean True if two sequences were identical.
mp-wp_genesis 100 */
mp-wp_genesis 101 function isEmpty()
mp-wp_genesis 102 {
mp-wp_genesis 103 foreach ($this->_edits as $edit) {
mp-wp_genesis 104 if (!is_a($edit, 'Text_Diff_Op_copy')) {
mp-wp_genesis 105 return false;
mp-wp_genesis 106 }
mp-wp_genesis 107 }
mp-wp_genesis 108 return true;
mp-wp_genesis 109 }
mp-wp_genesis 110
mp-wp_genesis 111 /**
mp-wp_genesis 112 * Computes the length of the Longest Common Subsequence (LCS).
mp-wp_genesis 113 *
mp-wp_genesis 114 * This is mostly for diagnostic purposes.
mp-wp_genesis 115 *
mp-wp_genesis 116 * @return integer The length of the LCS.
mp-wp_genesis 117 */
mp-wp_genesis 118 function lcs()
mp-wp_genesis 119 {
mp-wp_genesis 120 $lcs = 0;
mp-wp_genesis 121 foreach ($this->_edits as $edit) {
mp-wp_genesis 122 if (is_a($edit, 'Text_Diff_Op_copy')) {
mp-wp_genesis 123 $lcs += count($edit->orig);
mp-wp_genesis 124 }
mp-wp_genesis 125 }
mp-wp_genesis 126 return $lcs;
mp-wp_genesis 127 }
mp-wp_genesis 128
mp-wp_genesis 129 /**
mp-wp_genesis 130 * Gets the original set of lines.
mp-wp_genesis 131 *
mp-wp_genesis 132 * This reconstructs the $from_lines parameter passed to the constructor.
mp-wp_genesis 133 *
mp-wp_genesis 134 * @return array The original sequence of strings.
mp-wp_genesis 135 */
mp-wp_genesis 136 function getOriginal()
mp-wp_genesis 137 {
mp-wp_genesis 138 $lines = array();
mp-wp_genesis 139 foreach ($this->_edits as $edit) {
mp-wp_genesis 140 if ($edit->orig) {
mp-wp_genesis 141 array_splice($lines, count($lines), 0, $edit->orig);
mp-wp_genesis 142 }
mp-wp_genesis 143 }
mp-wp_genesis 144 return $lines;
mp-wp_genesis 145 }
mp-wp_genesis 146
mp-wp_genesis 147 /**
mp-wp_genesis 148 * Gets the final set of lines.
mp-wp_genesis 149 *
mp-wp_genesis 150 * This reconstructs the $to_lines parameter passed to the constructor.
mp-wp_genesis 151 *
mp-wp_genesis 152 * @return array The sequence of strings.
mp-wp_genesis 153 */
mp-wp_genesis 154 function getFinal()
mp-wp_genesis 155 {
mp-wp_genesis 156 $lines = array();
mp-wp_genesis 157 foreach ($this->_edits as $edit) {
mp-wp_genesis 158 if ($edit->final) {
mp-wp_genesis 159 array_splice($lines, count($lines), 0, $edit->final);
mp-wp_genesis 160 }
mp-wp_genesis 161 }
mp-wp_genesis 162 return $lines;
mp-wp_genesis 163 }
mp-wp_genesis 164
mp-wp_genesis 165 /**
mp-wp_genesis 166 * Removes trailing newlines from a line of text. This is meant to be used
mp-wp_genesis 167 * with array_walk().
mp-wp_genesis 168 *
mp-wp_genesis 169 * @param string $line The line to trim.
mp-wp_genesis 170 * @param integer $key The index of the line in the array. Not used.
mp-wp_genesis 171 */
mp-wp_genesis 172 function trimNewlines(&$line, $key)
mp-wp_genesis 173 {
mp-wp_genesis 174 $line = str_replace(array("\n", "\r"), '', $line);
mp-wp_genesis 175 }
mp-wp_genesis 176
mp-wp_genesis 177 /**
mp-wp_genesis 178 * Determines the location of the system temporary directory.
mp-wp_genesis 179 *
mp-wp_genesis 180 * @static
mp-wp_genesis 181 *
mp-wp_genesis 182 * @access protected
mp-wp_genesis 183 *
mp-wp_genesis 184 * @return string A directory name which can be used for temp files.
mp-wp_genesis 185 * Returns false if one could not be found.
mp-wp_genesis 186 */
mp-wp_genesis 187 function _getTempDir()
mp-wp_genesis 188 {
mp-wp_genesis 189 $tmp_locations = array('/tmp', '/var/tmp', 'c:\WUTemp', 'c:\temp',
mp-wp_genesis 190 'c:\windows\temp', 'c:\winnt\temp');
mp-wp_genesis 191
mp-wp_genesis 192 /* Try PHP's upload_tmp_dir directive. */
mp-wp_genesis 193 $tmp = ini_get('upload_tmp_dir');
mp-wp_genesis 194
mp-wp_genesis 195 /* Otherwise, try to determine the TMPDIR environment variable. */
mp-wp_genesis 196 if (!strlen($tmp)) {
mp-wp_genesis 197 $tmp = getenv('TMPDIR');
mp-wp_genesis 198 }
mp-wp_genesis 199
mp-wp_genesis 200 /* If we still cannot determine a value, then cycle through a list of
mp-wp_genesis 201 * preset possibilities. */
mp-wp_genesis 202 while (!strlen($tmp) && count($tmp_locations)) {
mp-wp_genesis 203 $tmp_check = array_shift($tmp_locations);
mp-wp_genesis 204 if (@is_dir($tmp_check)) {
mp-wp_genesis 205 $tmp = $tmp_check;
mp-wp_genesis 206 }
mp-wp_genesis 207 }
mp-wp_genesis 208
mp-wp_genesis 209 /* If it is still empty, we have failed, so return false; otherwise
mp-wp_genesis 210 * return the directory determined. */
mp-wp_genesis 211 return strlen($tmp) ? $tmp : false;
mp-wp_genesis 212 }
mp-wp_genesis 213
mp-wp_genesis 214 /**
mp-wp_genesis 215 * Checks a diff for validity.
mp-wp_genesis 216 *
mp-wp_genesis 217 * This is here only for debugging purposes.
mp-wp_genesis 218 */
mp-wp_genesis 219 function _check($from_lines, $to_lines)
mp-wp_genesis 220 {
mp-wp_genesis 221 if (serialize($from_lines) != serialize($this->getOriginal())) {
mp-wp_genesis 222 trigger_error("Reconstructed original doesn't match", E_USER_ERROR);
mp-wp_genesis 223 }
mp-wp_genesis 224 if (serialize($to_lines) != serialize($this->getFinal())) {
mp-wp_genesis 225 trigger_error("Reconstructed final doesn't match", E_USER_ERROR);
mp-wp_genesis 226 }
mp-wp_genesis 227
mp-wp_genesis 228 $rev = $this->reverse();
mp-wp_genesis 229 if (serialize($to_lines) != serialize($rev->getOriginal())) {
mp-wp_genesis 230 trigger_error("Reversed original doesn't match", E_USER_ERROR);
mp-wp_genesis 231 }
mp-wp_genesis 232 if (serialize($from_lines) != serialize($rev->getFinal())) {
mp-wp_genesis 233 trigger_error("Reversed final doesn't match", E_USER_ERROR);
mp-wp_genesis 234 }
mp-wp_genesis 235
mp-wp_genesis 236 $prevtype = null;
mp-wp_genesis 237 foreach ($this->_edits as $edit) {
mp-wp_genesis 238 if ($prevtype == get_class($edit)) {
mp-wp_genesis 239 trigger_error("Edit sequence is non-optimal", E_USER_ERROR);
mp-wp_genesis 240 }
mp-wp_genesis 241 $prevtype = get_class($edit);
mp-wp_genesis 242 }
mp-wp_genesis 243
mp-wp_genesis 244 return true;
mp-wp_genesis 245 }
mp-wp_genesis 246
mp-wp_genesis 247 }
mp-wp_genesis 248
mp-wp_genesis 249 /**
mp-wp_genesis 250 * @package Text_Diff
mp-wp_genesis 251 * @author Geoffrey T. Dairiki <dairiki@dairiki.org>
mp-wp_genesis 252 */
mp-wp_genesis 253 class Text_MappedDiff extends Text_Diff {
mp-wp_genesis 254
mp-wp_genesis 255 /**
mp-wp_genesis 256 * Computes a diff between sequences of strings.
mp-wp_genesis 257 *
mp-wp_genesis 258 * This can be used to compute things like case-insensitve diffs, or diffs
mp-wp_genesis 259 * which ignore changes in white-space.
mp-wp_genesis 260 *
mp-wp_genesis 261 * @param array $from_lines An array of strings.
mp-wp_genesis 262 * @param array $to_lines An array of strings.
mp-wp_genesis 263 * @param array $mapped_from_lines This array should have the same size
mp-wp_genesis 264 * number of elements as $from_lines. The
mp-wp_genesis 265 * elements in $mapped_from_lines and
mp-wp_genesis 266 * $mapped_to_lines are what is actually
mp-wp_genesis 267 * compared when computing the diff.
mp-wp_genesis 268 * @param array $mapped_to_lines This array should have the same number
mp-wp_genesis 269 * of elements as $to_lines.
mp-wp_genesis 270 */
mp-wp_genesis 271 function Text_MappedDiff($from_lines, $to_lines,
mp-wp_genesis 272 $mapped_from_lines, $mapped_to_lines)
mp-wp_genesis 273 {
mp-wp_genesis 274 assert(count($from_lines) == count($mapped_from_lines));
mp-wp_genesis 275 assert(count($to_lines) == count($mapped_to_lines));
mp-wp_genesis 276
mp-wp_genesis 277 parent::Text_Diff($mapped_from_lines, $mapped_to_lines);
mp-wp_genesis 278
mp-wp_genesis 279 $xi = $yi = 0;
mp-wp_genesis 280 for ($i = 0; $i < count($this->_edits); $i++) {
mp-wp_genesis 281 $orig = &$this->_edits[$i]->orig;
mp-wp_genesis 282 if (is_array($orig)) {
mp-wp_genesis 283 $orig = array_slice($from_lines, $xi, count($orig));
mp-wp_genesis 284 $xi += count($orig);
mp-wp_genesis 285 }
mp-wp_genesis 286
mp-wp_genesis 287 $final = &$this->_edits[$i]->final;
mp-wp_genesis 288 if (is_array($final)) {
mp-wp_genesis 289 $final = array_slice($to_lines, $yi, count($final));
mp-wp_genesis 290 $yi += count($final);
mp-wp_genesis 291 }
mp-wp_genesis 292 }
mp-wp_genesis 293 }
mp-wp_genesis 294
mp-wp_genesis 295 }
mp-wp_genesis 296
mp-wp_genesis 297 /**
mp-wp_genesis 298 * @package Text_Diff
mp-wp_genesis 299 * @author Geoffrey T. Dairiki <dairiki@dairiki.org>
mp-wp_genesis 300 *
mp-wp_genesis 301 * @access private
mp-wp_genesis 302 */
mp-wp_genesis 303 class Text_Diff_Op {
mp-wp_genesis 304
mp-wp_genesis 305 var $orig;
mp-wp_genesis 306 var $final;
mp-wp_genesis 307
mp-wp_genesis 308 function &reverse()
mp-wp_genesis 309 {
mp-wp_genesis 310 trigger_error('Abstract method', E_USER_ERROR);
mp-wp_genesis 311 }
mp-wp_genesis 312
mp-wp_genesis 313 function norig()
mp-wp_genesis 314 {
mp-wp_genesis 315 return $this->orig ? count($this->orig) : 0;
mp-wp_genesis 316 }
mp-wp_genesis 317
mp-wp_genesis 318 function nfinal()
mp-wp_genesis 319 {
mp-wp_genesis 320 return $this->final ? count($this->final) : 0;
mp-wp_genesis 321 }
mp-wp_genesis 322
mp-wp_genesis 323 }
mp-wp_genesis 324
mp-wp_genesis 325 /**
mp-wp_genesis 326 * @package Text_Diff
mp-wp_genesis 327 * @author Geoffrey T. Dairiki <dairiki@dairiki.org>
mp-wp_genesis 328 *
mp-wp_genesis 329 * @access private
mp-wp_genesis 330 */
mp-wp_genesis 331 class Text_Diff_Op_copy extends Text_Diff_Op {
mp-wp_genesis 332
mp-wp_genesis 333 function Text_Diff_Op_copy($orig, $final = false)
mp-wp_genesis 334 {
mp-wp_genesis 335 if (!is_array($final)) {
mp-wp_genesis 336 $final = $orig;
mp-wp_genesis 337 }
mp-wp_genesis 338 $this->orig = $orig;
mp-wp_genesis 339 $this->final = $final;
mp-wp_genesis 340 }
mp-wp_genesis 341
mp-wp_genesis 342 function &reverse()
mp-wp_genesis 343 {
mp-wp_genesis 344 $reverse = &new Text_Diff_Op_copy($this->final, $this->orig);
mp-wp_genesis 345 return $reverse;
mp-wp_genesis 346 }
mp-wp_genesis 347
mp-wp_genesis 348 }
mp-wp_genesis 349
mp-wp_genesis 350 /**
mp-wp_genesis 351 * @package Text_Diff
mp-wp_genesis 352 * @author Geoffrey T. Dairiki <dairiki@dairiki.org>
mp-wp_genesis 353 *
mp-wp_genesis 354 * @access private
mp-wp_genesis 355 */
mp-wp_genesis 356 class Text_Diff_Op_delete extends Text_Diff_Op {
mp-wp_genesis 357
mp-wp_genesis 358 function Text_Diff_Op_delete($lines)
mp-wp_genesis 359 {
mp-wp_genesis 360 $this->orig = $lines;
mp-wp_genesis 361 $this->final = false;
mp-wp_genesis 362 }
mp-wp_genesis 363
mp-wp_genesis 364 function &reverse()
mp-wp_genesis 365 {
mp-wp_genesis 366 $reverse = &new Text_Diff_Op_add($this->orig);
mp-wp_genesis 367 return $reverse;
mp-wp_genesis 368 }
mp-wp_genesis 369
mp-wp_genesis 370 }
mp-wp_genesis 371
mp-wp_genesis 372 /**
mp-wp_genesis 373 * @package Text_Diff
mp-wp_genesis 374 * @author Geoffrey T. Dairiki <dairiki@dairiki.org>
mp-wp_genesis 375 *
mp-wp_genesis 376 * @access private
mp-wp_genesis 377 */
mp-wp_genesis 378 class Text_Diff_Op_add extends Text_Diff_Op {
mp-wp_genesis 379
mp-wp_genesis 380 function Text_Diff_Op_add($lines)
mp-wp_genesis 381 {
mp-wp_genesis 382 $this->final = $lines;
mp-wp_genesis 383 $this->orig = false;
mp-wp_genesis 384 }
mp-wp_genesis 385
mp-wp_genesis 386 function &reverse()
mp-wp_genesis 387 {
mp-wp_genesis 388 $reverse = &new Text_Diff_Op_delete($this->final);
mp-wp_genesis 389 return $reverse;
mp-wp_genesis 390 }
mp-wp_genesis 391
mp-wp_genesis 392 }
mp-wp_genesis 393
mp-wp_genesis 394 /**
mp-wp_genesis 395 * @package Text_Diff
mp-wp_genesis 396 * @author Geoffrey T. Dairiki <dairiki@dairiki.org>
mp-wp_genesis 397 *
mp-wp_genesis 398 * @access private
mp-wp_genesis 399 */
mp-wp_genesis 400 class Text_Diff_Op_change extends Text_Diff_Op {
mp-wp_genesis 401
mp-wp_genesis 402 function Text_Diff_Op_change($orig, $final)
mp-wp_genesis 403 {
mp-wp_genesis 404 $this->orig = $orig;
mp-wp_genesis 405 $this->final = $final;
mp-wp_genesis 406 }
mp-wp_genesis 407
mp-wp_genesis 408 function &reverse()
mp-wp_genesis 409 {
mp-wp_genesis 410 $reverse = &new Text_Diff_Op_change($this->final, $this->orig);
mp-wp_genesis 411 return $reverse;
mp-wp_genesis 412 }
mp-wp_genesis 413
mp-wp_genesis 414 }