src/Diff.php
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;
    }
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;
    }
Reorder and merge like edit sections.  Merge equalities.
Any edit section can move as long as it doesn't cross an equality.

@return $this