public function cleanupMerge()
{
$diffs = $this->getChanges();
$diffs[] = array(self::EQUAL, '');
$pointer = 0;
$count_delete = 0;
$count_insert = 0;
$text_delete = '';
$text_insert = '';
while ($pointer < count($diffs)) {
if ($diffs[$pointer][0] == self::INSERT) {
$count_insert++;
$text_insert .= $diffs[$pointer][1];
$pointer++;
} elseif ($diffs[$pointer][0] == self::DELETE) {
$count_delete++;
$text_delete .= $diffs[$pointer][1];
$pointer++;
} elseif ($diffs[$pointer][0] == self::EQUAL) {
// Upon reaching an equality, check for prior redundancies.
if ($count_delete + $count_insert > 1) {
if ($count_delete != 0 && $count_insert != 0) {
// Factor out any common prefixies.
$commonlength = $this->getToolkit()->commonPrefix($text_insert, $text_delete);
if ($commonlength != 0) {
$x = $pointer - $count_delete - $count_insert - 1;
if ($x >= 0 && $diffs[$x][0] == self::EQUAL) {
$diffs[$x][1] .= mb_substr($text_insert, 0, $commonlength);
} else {
array_unshift($diffs, array(self::EQUAL, mb_substr($text_insert, 0, $commonlength)));
$pointer++;
}
$text_insert = mb_substr($text_insert, $commonlength);
$text_delete = mb_substr($text_delete, $commonlength);
}
// Factor out any common suffixies.
$commonlength = $this->getToolkit()->commonSuffix($text_insert, $text_delete);
if ($commonlength != 0) {
$diffs[$pointer][1] = mb_substr($text_insert, -$commonlength) . $diffs[$pointer][1];
$text_insert = mb_substr($text_insert, 0, -$commonlength);
$text_delete = mb_substr($text_delete, 0, -$commonlength);
}
}
// Delete the offending records and add the merged ones.
if ($count_delete == 0) {
array_splice($diffs, $pointer - $count_insert, $count_insert, array(array(self::INSERT, $text_insert)));
} elseif ($count_insert == 0) {
array_splice($diffs, $pointer - $count_delete, $count_delete, array(array(self::DELETE, $text_delete)));
} else {
array_splice($diffs, $pointer - $count_delete - $count_insert, $count_delete + $count_insert, array(array(self::DELETE, $text_delete), array(self::INSERT, $text_insert)));
}
$pointer = $pointer - $count_delete - $count_insert + 1;
if ($count_delete != 0) {
$pointer += 1;
}
if ($count_insert != 0) {
$pointer += 1;
}
} elseif ($pointer != 0 && $diffs[$pointer - 1][0] == self::EQUAL) {
// Merge this equality with the previous one.
$diffs[$pointer - 1][1] .= $diffs[$pointer][1];
array_splice($diffs, $pointer, 1);
} else {
$pointer++;
}
$count_delete = 0;
$count_insert = 0;
$text_delete = '';
$text_insert = '';
}
}
if ($diffs[count($diffs) - 1][1] == '') {
array_pop($diffs);
}
// Second pass: look for single edits surrounded on both sides by equalities
// which can be shifted sideways to eliminate an equality.
// e.g: A<ins>BA</ins>C -> <ins>AB</ins>AC
$changes = false;
$pointer = 1;
// Intentionally ignore the first and last element (don't need checking).
while ($pointer < count($diffs) - 1) {
if ($diffs[$pointer - 1][0] == self::EQUAL && $diffs[$pointer + 1][0] == self::EQUAL) {
// This is a single edit surrounded by equalities.
if (mb_substr($diffs[$pointer][1], -mb_strlen($diffs[$pointer - 1][1])) == $diffs[$pointer - 1][1]) {
// Shift the edit over the previous equality.
$diffs[$pointer][1] = $diffs[$pointer - 1][1] . mb_substr($diffs[$pointer][1], 0, -mb_strlen($diffs[$pointer - 1][1]));
$diffs[$pointer + 1][1] = $diffs[$pointer - 1][1] . $diffs[$pointer + 1][1];
array_splice($diffs, $pointer - 1, 1);
$changes = true;
} elseif (mb_substr($diffs[$pointer][1], 0, mb_strlen($diffs[$pointer + 1][1])) == $diffs[$pointer + 1][1]) {
// Shift the edit over the next equality.
$diffs[$pointer - 1][1] = $diffs[$pointer - 1][1] . $diffs[$pointer + 1][1];
$diffs[$pointer][1] = mb_substr($diffs[$pointer][1], mb_strlen($diffs[$pointer + 1][1])) . $diffs[$pointer + 1][1];
array_splice($diffs, $pointer + 1, 1);
$changes = true;
}
}
$pointer++;
}
$this->setChanges($diffs);
// If shifts were made, the diff needs reordering and another shift sweep.
if ($changes) {
$this->cleanupMerge();
}
return $this;
}