<?php class Text_Diff { var $_edits; function __construct($engine, $params) { if (!is_string($engine)) { $params = array($engine, $params); $engine = 'auto'; } if ($engine == 'auto') { $engine = extension_loaded('xdiff') ? 'xdiff' : 'native'; } else { $engine = basename($engine); } require_once dirname(__FILE__) . '/Diff/Engine/' . $engine . '.php'; $class = 'Text_Diff_Engine_' . $engine; $diff_engine = new $class(); $this->_edits = call_user_func_array(array($diff_engine, 'diff'), $params); } public function Text_Diff($engine, $params) { self::__construct($engine, $params); } function getDiff() { return $this->_edits; } function countAddedLines() { $count = 0; foreach ($this->_edits as $edit) { if (is_a($edit, 'Text_Diff_Op_add') || is_a($edit, 'Text_Diff_Op_change')) { $count += $edit->nfinal(); } } return $count; } function countDeletedLines() { $count = 0; foreach ($this->_edits as $edit) { if (is_a($edit, 'Text_Diff_Op_delete') || is_a($edit, 'Text_Diff_Op_change')) { $count += $edit->norig(); } } return $count; } function reverse() { if (version_compare(zend_version(), '2', '>')) { $rev = clone $this; } else { $rev = $this; } $rev->_edits = array(); foreach ($this->_edits as $edit) { $rev->_edits[] = $edit->reverse(); } return $rev; } function isEmpty() { foreach ($this->_edits as $edit) { if (!is_a($edit, 'Text_Diff_Op_copy')) { return false; } } return true; } function lcs() { $lcs = 0; foreach ($this->_edits as $edit) { if (is_a($edit, 'Text_Diff_Op_copy')) { $lcs += count($edit->orig); } } return $lcs; } function getOriginal() { $lines = array(); foreach ($this->_edits as $edit) { if ($edit->orig) { array_splice($lines, count($lines), 0, $edit->orig); } } return $lines; } function getFinal() { $lines = array(); foreach ($this->_edits as $edit) { if ($edit->final) { array_splice($lines, count($lines), 0, $edit->final); } } return $lines; } static function trimNewlines(&$line, $key) { $line = str_replace(array("\n", "\r"), '', $line); } static function _getTempDir() { $tmp_locations = array('/tmp', '/var/tmp', 'c:\\WUTemp', 'c:\\temp', 'c:\\windows\\temp', 'c:\\winnt\\temp'); $tmp = ini_get('upload_tmp_dir'); if (!strlen($tmp)) { $tmp = getenv('TMPDIR'); } while (!strlen($tmp) && count($tmp_locations)) { $tmp_check = array_shift($tmp_locations); if (@is_dir($tmp_check)) { $tmp = $tmp_check; } } return strlen($tmp) ? $tmp : false; } function _check($from_lines, $to_lines) { if (serialize($from_lines) != serialize($this->getOriginal())) { trigger_error("Reconstructed original doesn't match", E_USER_ERROR); } if (serialize($to_lines) != serialize($this->getFinal())) { trigger_error("Reconstructed final doesn't match", E_USER_ERROR); } $rev = $this->reverse(); if (serialize($to_lines) != serialize($rev->getOriginal())) { trigger_error("Reversed original doesn't match", E_USER_ERROR); } if (serialize($from_lines) != serialize($rev->getFinal())) { trigger_error("Reversed final doesn't match", E_USER_ERROR); } $prevtype = null; foreach ($this->_edits as $edit) { if ($edit instanceof $prevtype) { trigger_error("Edit sequence is non-optimal", E_USER_ERROR); } $prevtype = get_class($edit); } return true; } } class Text_MappedDiff extends Text_Diff { function __construct($from_lines, $to_lines, $mapped_from_lines, $mapped_to_lines) { assert(count($from_lines) == count($mapped_from_lines)); assert(count($to_lines) == count($mapped_to_lines)); parent::Text_Diff($mapped_from_lines, $mapped_to_lines); $xi = $yi = 0; for ($i = 0; $i < count($this->_edits); $i++) { $orig =& $this->_edits[$i]->orig; if (is_array($orig)) { $orig = array_slice($from_lines, $xi, count($orig)); $xi += count($orig); } $final =& $this->_edits[$i]->final; if (is_array($final)) { $final = array_slice($to_lines, $yi, count($final)); $yi += count($final); } } } public function Text_MappedDiff($from_lines, $to_lines, $mapped_from_lines, $mapped_to_lines) { self::__construct($from_lines, $to_lines, $mapped_from_lines, $mapped_to_lines); } } class Text_Diff_Op { var $orig; var $final; function &reverse() { trigger_error('Abstract method', E_USER_ERROR); } function norig() { return $this->orig ? count($this->orig) : 0; } function nfinal() { return $this->final ? count($this->final) : 0; } } class Text_Diff_Op_copy extends Text_Diff_Op { function __construct($orig, $final = false) { if (!is_array($final)) { $final = $orig; } $this->orig = $orig; $this->final = $final; } public function Text_Diff_Op_copy($orig, $final = false) { self::__construct($orig, $final); } function &reverse() { $reverse = new Text_Diff_Op_copy($this->final, $this->orig); return $reverse; } } class Text_Diff_Op_delete extends Text_Diff_Op { function __construct($lines) { $this->orig = $lines; $this->final = false; } public function Text_Diff_Op_delete($lines) { self::__construct($lines); } function &reverse() { $reverse = new Text_Diff_Op_add($this->orig); return $reverse; } } class Text_Diff_Op_add extends Text_Diff_Op { function __construct($lines) { $this->final = $lines; $this->orig = false; } public function Text_Diff_Op_add($lines) { self::__construct($lines); } function &reverse() { $reverse = new Text_Diff_Op_delete($this->final); return $reverse; } } class Text_Diff_Op_change extends Text_Diff_Op { function __construct($orig, $final) { $this->orig = $orig; $this->final = $final; } public function Text_Diff_Op_change($orig, $final) { self::__construct($orig, $final); } function &reverse() { $reverse = new Text_Diff_Op_change($this->final, $this->orig); return $reverse; } }