Location: PHPKode > scripts > PHP unified diff patcher > php-unified-diff-patcher/phppatcher.php
<?php
## PhpPatcher class
# @author legolas558
# @version 0.1.1
# Licensed under GNU General Public License (GPL)
#
# Facility to merge unified diff files
# First use Merge() and then ApplyPatch() to commit changes
# files will be created, updated and/or deleted

define('_PHPP_INVALID_INPUT', 'Invalid input');
define('_PHPP_UNEXPECTED_EOF', 'Unexpected end of file');
define('_PHPP_UNEXPECTED_ADD_LINE', 'Unexpected add line at line %d');
define('_PHPP_UNEXPECTED_REMOVE_LINE', 'Unexpected remove line at line %d');
define('_PHPP_INVALID_DIFF', 'Invalid unified diff block');
define('_PHPP_FAILED_VERIFY', 'Failed source verification of file %s at line %d');

class	PhpPatcher {
	
	var $root;
	var $msg;
	var $sources = array();
	var $destinations = array();
	var $removals = array();
	var $newline = "\n";
	
	function PhpPatcher($root_path) {
		// if you specify a root path all paths will be intended as relative to it (and not written, too)
		$this->root = $root_path;
	}
	
	function &_get_source($src) {
		if (isset($this->sources[$src]))
			return $this->sources[$src];
		if (!is_readable($src)) {
			$n = null;
			return $n;
		}
		$this->sources[$src] = $this->_linesplit(file_get_contents($src));
		return $this->sources[$src];
	}
	
	function &_get_destin($dst, $src) {
		if (isset($this->destinations[$dst]))
			return $this->destinations[$dst];
		$this->destinations[$dst] = $this->_get_source($src);
		return $this->destinations[$dst];
	}

	// separate CR or CRLF lines
	function &_linesplit(&$data) {
		$lines = preg_split('/(\r\n)|(\r)|(\n)/', $data);
		return $lines;
	}
	
	function Merge($udiff) {
		// (1) Separate the input into lines
		$lines = $this->_linesplit($udiff);
		if (!isset($lines)) {
			$this->msg = _PHPP_INVALID_INPUT;
			return false;
		}
		unset($udiff);
	
		$line = current($lines);
		do {
			if (strlen($line)<5)
				continue;
			// start recognition when a new diff block is found
			if (substr($line, 0, 4)!='--- ')
				continue;
			$p = strpos($line, "\t", 4);
			if ($p===false)	$p = strlen($line);
			$src = $this->root.substr($line, 4, $p-4);
			$line = next($lines);
			if (!isset($line)) {
				$this->msg = _PHPP_UNEXPECTED_EOF;
				return false;
			}
			if (substr($line, 0, 4)!='+++ ') {
				$this->msg = _PHPP_INVALID_DIFF;
				return false;
			}
			$p = strpos($line, "\t", 4);
			if ($p===false)	$p = strlen($line);
			$dst = $this->root.substr($line, 4, $p-4);
			
			$line = next($lines);
			if (!isset($line)) {
				$this->msg = _PHPP_UNEXPECTED_EOF;
				return false;
			}
			
			$done=0;
			while (preg_match('/@@ -(\\d+)(,(\\d+))?\\s+\\+(\\d+)(,(\\d+))?\\s+@@($)/A', $line, $m)) {
			
				if ($m[3]==='')
					$src_size = 1;
				else $src_size = (int)$m[3];
				if ($m[6]==='')
					$dst_size = 1;
				else $dst_size = (int)$m[6];
				if (!$this->_apply_diff($lines, $src, $dst,
									(int)$m[1], $src_size, (int)$m[4],
									$dst_size))
					return false;
				$done++;
				$line = next($lines);
				if ($line === FALSE)
					break 2;
			}
			if ($done==0) {
				$this->msg = _PHPP_INVALID_DIFF;
				return false;
			}
			
		} while (FALSE !== ($line = next($lines)));
		
		//NOTE: previously opened files are still cached
		return true;
	}
	
	function ClearCache() {
		$this->sources = array();
		$this->destinations = array();
		$this->removals = array();
	}
	
	function ApplyPatch() {
		if (empty($this->destinations))
			return 0;
		$done = 0;
		$files = array_keys($this->destinations);
		foreach($files as $file) {
			$f = @fopen($file, 'w');
			if ($f===null)
				continue;
			fwrite($f, implode($this->newline, $this->destinations[$file]));
			fclose($f);
			$done++;
		}
		foreach($this->removals as $file) {
			if (@unlink($file))
				$done++;
			if (isset($this->sources[$file]))
				unset($this->sources[$file]);
		}
		$this->destinations = array(); // clear the destinations cache
		$this->removals = array();
		return $done;
	}
	
	function _apply_diff(&$lines, $src, $dst, $src_line, $src_size, $dst_line, $dst_size) {
		$src_line--;
		$dst_line--;
		$line = next($lines);
		if ($line === false) {
			$this->msg = _PHPP_UNEXPECTED_EOF;
			return false;
		}
		$source = array();		// source lines (old file)
		$destin = array();		// new lines (new file)
		$src_left = $src_size;
		$dst_left = $dst_size;
		do {
			if (!isset($line{0})) {
				$source[] = '';
				$destin[] = '';
				$src_left--;
				$dst_left--;
				continue;
			}
			if ($line{0}=='-') {
				if ($src_left==0) {
					$this->msg = sprintf(_PHPP_UNEXPECTED_REMOVE_LINE, key($lines));
					return false;
				}
				$source[] = substr($line, 1);
				$src_left--;
			} else if ($line{0}=='+') {
				if ($dst_left==0) {
					$this->msg = sprintf(_PHPP_UNEXPECTED_ADD_LINE, key($lines));
					return false;
				}
				$destin[] = substr($line, 1);
				$dst_left--;
			} else {
				if (!isset($line{1}))
					$line = '';
				else if ($line{0}=='\\') {
					if ($line=='\\ No newline at end of file')
						continue;
				} else
					$line = substr($line, 1);
				$source[] = $line;
				$destin[] = $line;
				$src_left--;
				$dst_left--;
			}
			
			if (($src_left==0) && ($dst_left==0)) {
				// now apply the patch, finally!
				if ($src_size>0) {
					$src_lines =& $this->_get_source($src);
					if (!isset($src_lines))
						return false;
				}
				if ($dst_size>0) {
					if ($src_size>0) {
						$dst_lines =& $this->_get_destin($dst, $src);
						if (!isset($dst_lines))
							return false;
						$src_bottom=$src_line+count($source);
						$dst_bottom=$dst_line+count($destin);
						
						for ($l=$src_line;$l<$src_bottom;$l++) {
							if ($src_lines[$l]!=$source[$l-$src_line]) {
								$this->msg = sprintf(_PHPP_FAILED_VERIFY, $src, $l);
								return false;
							}
						}
						array_splice($dst_lines, $dst_line, count($source), $destin);
					} else
						$this->destinations[$dst] = $destin;
				} else
					$this->removals[] = $src;
				
				return true;
			}
		} while (FALSE !== ($line = next($lines)));

		$this->msg = _PHPP_UNEXPECTED_EOF;
		return false;
	}
	
	function Diff(&$latest, &$udiff) {
		$this->msg = 'Not available';
		return false;
	}

}

?>
Return current item: PHP unified diff patcher