<?php
defined('WikyBlog') or die("Not an entry point...");
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
//
// difference_main
//
class difference_main{
var $instructions;
var $keys;
var $differences = 0;
function difference_main($source,$result){
$instruction = array();
// 1) if Objects => arrays
if( is_object($result) ){
$type = get_class( $result );
$type2 = get_class( $source );
if( $type !== $type2){
trigger_error('Objects are not of the same type '.$type.' :: '.$type2);
}
$source = get_object_vars( $source );
$result = get_object_vars( $result );
}
// 2) Make instructions for each field
$workingClass = new getInstructions();
foreach($result as $key => $resultValue){
$sourceValue = $source[$key];
if( empty($resultValue) ){
$resultValue = '';
}
if( empty($sourceValue) ){
$sourceValue = '';
}
$workingClass->calcDifference($sourceValue, $resultValue);
$workingClass->setInstructions();
if( isset($workingClass->instructions) ){
$instructions[$key] = $workingClass->instructions;
$this->keys[] = $key;
$this->differences += $workingClass->differences;
}
}
// 3) Serialize, Compress and return
if( !empty($instructions) ){
//echo showArray($instructions);
$instructions = serialize($instructions);
$this->instructions = $instructions;
if( function_exists('gzdeflate') ){
$this->instructions = gzdeflate($this->instructions);
}
}
}
}
//
// difference_main
//
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
//
// difference
//
class difference{
////////////////////////////////////////////////////////////////////////////
////
//// VARIABLES
////
var $source;
var $result;
var $left;
var $leftIndex;
var $maxleft;
var $right;
var $rightIndex;
var $maxRight;
var $deleteLeft;
var $addRight;
var $instructions;
var $debugText;
var $differences;
function resetVars(){
$array = get_class_vars( get_class($this) );
foreach($array as $key => $value){
unset($this->$key);
}
//$this->differences = 0;
$this->leftIndex = 0;
$this->rightIndex = 0;
$this->deleteLeft = array();
$this->addRight = array();
}
////////////////////////////////////////////////////////////////////////////
////
//// This Does the Work
//// $left = source; $right = result;
////
function calcDifference($left,$right,$doBlankLines=true){
// 0) don't bother getting instructions if result is very small
// this doesn't exactly work because of later comparison...
// if( wbStrlen($right) < 80 ){
// $this->instructions = $right;
// return;
// }
$this->resetVars();
$this->source = $left;
$this->result = $right;
$this->left = stringToArray($left);
$this->right = stringToArray($right);
$this->maxLeft = count($this->left);
$this->maxRight = count($this->right);
/// should use a foreach(..) here... I think they're faster too
while( ($this->leftIndex < $this->maxLeft) or ($this->rightIndex < $this->maxRight) ){
//echo $this->debugText;
//$this->debugText = '<p>';
////////////////////////////////////////////////////////////////////////////
////
//// I) End of left or right
////
// left at max
if( $this->leftIndex == $this->maxLeft ){
//$this->debugText .= '<p>LeftIndex = max = '.$this->maxLeft;
while($this->rightIndex < $this->maxRight){
$this->addRight();
}
break;
}
// right at max
if( $this->rightIndex == $this->maxRight ){
//$this->debugText .= '<p>RightIndex = max = '.$this->maxRight;
while($this->leftIndex < $this->maxLeft){
$this->deleteLeft();
}
break;
}
////////////////////////////////////////////////////////////////////////////
////
//// II) left == right
////
if( $this->left[$this->leftIndex] == $this->right[$this->rightIndex] ){
//$this->debugText .= '<p>Equal at left: '.$this->leftIndex.' Right: '.$this->rightIndex;
unset($this->left[$this->leftIndex]);
unset($this->right[$this->rightIndex]);
$this->leftIndex++;
$this->rightIndex++;
continue;
}
////////////////////////////////////////////////////////////////////////////
////
//// III) left != right
////
if($doBlankLines){
// 2) Add or Delete Blank Lines
if( empty($this->left[$this->leftIndex]) || $this->left[$this->leftIndex] == ''){
//$this->debugText .= '<br/><b>Empty line on left at</b> '.$this->leftIndex;
$this->deleteLeft();
continue;
}
if( empty($this->right[$this->rightIndex]) || $this->right[$this->rightIndex] == ''){
//$this->debugText .= '<br/><b>Empty line on right at</b> '.$this->rightIndex;
$this->addRight();
continue;
}
}
// 3) Distance to next similarity
$rightMatchAt = false;
$leftMatchAt = false;
$rightMatchAt = array_search($this->left[$this->leftIndex],$this->right);
$leftMatchAt = array_search($this->right[$this->rightIndex],$this->left);
//$this->debugText .= '<p>Left: '.$this->leftIndex. ' Right-Match At: '.$rightMatchAt .' Right: '.$this->rightIndex. ' Left-Match at: '.$leftMatchAt;
// 4) left never equals right with either line:
// - this should prevent both differences from being negative below
if( ($rightMatchAt === false) && ($leftMatchAt === false)){
$this->addRight();
$this->deleteLeft();
continue;
}
if($rightMatchAt === false){
$this->deleteLeft();
continue;
}
if($leftMatchAt === false){
$this->addRight();
continue;
}
$numAdds = ($rightMatchAt - $this->rightIndex);
$numDeletes = ($leftMatchAt - $this->leftIndex);;
//$this->debugText .= '<br/> Number of Adds: '.$numAdds;
//$this->debugText .= '<br/> Number of Deletes: '.$numDeletes;
if( (($numAdds>0) && ($numAdds<=$numDeletes)) || ($numDeletes<0) ){
while($this->rightIndex < $rightMatchAt){
$this->addRight();
}
}elseif($numDeletes >= 0){
while($this->leftIndex < $leftMatchAt){
$this->deleteLeft();
}
}
}// 1st while
//echo $this->debugText;
$this->differences = $this->numberOfDifferences();
return;
}//end function findDifference
function addRight(){
//$this->debugText .= '<br/> Add right: (leftIndex) '.$this->leftIndex;
$this->addRight[$this->leftIndex][$this->rightIndex] = $this->right[$this->rightIndex];
unset($this->right[$this->rightIndex]);
$this->rightIndex++;
//$this->differences++;
}
function deleteLeft(){
//$this->debugText .= '<br/> Delete left: (leftIndex) '.$this->leftIndex;
$this->deleteLeft[$this->leftIndex] = $this->left[$this->leftIndex];
unset($this->left[$this->leftIndex]);
$this->leftIndex++;
//$this->differences++;
}
function numberOfDifferences(){
$differences = 0;
$temp1 = array_keys($this->deleteLeft);
$temp2 = array_keys($this->addRight);
$intersection = count( array_intersect($temp1,$temp2) );
$differences = count($this->deleteLeft) + (count($this->addRight,1)-count($this->addRight) );
$differences -= $intersection;
return $differences;
}
}
//
// difference
//
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
//
// getInstructions
// translates add/delete then serializes
class getInstructions extends difference{
function setInstructions(){
if( empty($this->deleteLeft) && empty($this->addRight) ){
return;
}
// 1) Make Move Instructions
$deleteTemp = $this->deleteLeft;
$addTemp = $this->addRight;
$moveTemp = NULL;
foreach($this->addRight as $leftIndex => $addArray){
foreach($addArray as $rightIndex => $addLine){
if( ($addLine == '') || empty($addLine)){
continue;
}
$moveFrom = false;
$moveFrom = array_search($addLine,$deleteTemp);
if( ($moveFrom !== false) && !is_null($moveFrom)){ //is_null for php versions < 4.2
$moveTemp[$moveFrom]=$leftIndex.'.'.$rightIndex;
unset($deleteTemp[$moveFrom]);
unset($addTemp[$leftIndex][$rightIndex]);
}
}
}
if( !empty($moveTemp) ){
$array['m'] = $moveTemp;
}
// 2) Replace Instructions
$replaceTemp = array();
foreach($deleteTemp as $leftIndex => $deleteLine){
if( isset($addTemp[$leftIndex]) ){
foreach($addTemp[$leftIndex] as $addKey => $addLine){
$replaceTemp[$leftIndex][$addKey] = $addLine;
//$replaceTemp[$leftIndex] = $addLine; // other options for saving space? only a few bytes here and there
//$replaceTemp[$leftIndex.'.'.$addKey] = $addLine; //
}
unset($addTemp[$leftIndex]);
unset($deleteTemp[$leftIndex]);
}
}
if( !empty($replaceTemp) ){
$array['r'] = $replaceTemp;
}
// 3) Compress Delete Instrucions
foreach($deleteTemp as $key => $deleteLine){
$array['d'][$key] = '';
}
// 4) Add instructions
if( !empty($addTemp) ){
$array['a'] = $addTemp;
}
// 5) Check Size of instructions vs just saving value
if( function_exists('gzdeflate') ){
$resultSize = strlen(gzdeflate($this->result));
$instructionSize = strlen(gzdeflate( serialize($array) ));
}else{
$resultSize = $this->result;
$instructionSize = strlen(serialize($array));
}
// echo '<p><b>Sizes</b> (compressed)';
// echo '<br/>Result Size: '.$resultSize;
// echo '<br/>Size of Instructions: '.$instructionSize;
if($resultSize <= $instructionSize){
$this->instructions = $this->result;
}else{
$this->instructions = $array;
}
}// end setInstructions
}
//
// getInstructions
//
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////