src/Patch.php
public function apply($patches, $text)
    {
        if (empty($patches)) {
            return array($text, array());
        }
        // Deep copy the patches so that no changes are made to originals.
        // FIXME don't need in PHP
        $patches = $this->deepCopy($patches);

        $nullPadding = $this->addPadding($patches);
        $text = $nullPadding . $text . $nullPadding;
        $this->splitMax($patches);

        // Delta keeps track of the offset between the expected and actual location
        // of the previous patch.  If there are patches expected at positions 10 and
        // 20, but the first patch was found at 12, delta is 2 and the second patch
        // has an effective expected position of 22.
        $delta = 0;
        $results = array();
        $diff = $this->getDiff();
        $match = $this->getMatch();
        $maxBits = $match->getMaxBits();

        foreach ($patches as $patch) {
            $expectedLoc = $patch->getStart2() + $delta;
            $diff->setChanges($patch->getChanges());
            $text1 = $diff->text1();
            $text1Len = mb_strlen($text1);
            $endLoc = -1;

            if ($text1Len > $maxBits) {
                // self::splitMax() will only provide an oversized pattern in the case of
                // a monster delete.
                $startLoc = $match->main($text, mb_substr($text1, 0, $maxBits), $expectedLoc);

                if ($startLoc != -1) {
                    $endLoc = $match->main($text, mb_substr($text1, -$maxBits),
                        $expectedLoc + $text1Len - $maxBits);
                    if ($endLoc == -1 || $startLoc >= $endLoc) {
                        // Can't find valid trailing context.  Drop this patch.
                        $startLoc = -1;
                    }
                }
            } else {
                $startLoc = $match->main($text, $text1, $expectedLoc);
            }

            if ($startLoc == -1) {
                // No match found.  :(
                $results[] = false;
                // Subtract the delta for this failed patch from subsequent patches.
                $delta -= $patch->getLength2() - $patch->getLength1();
            } else {
                // Found a match.  :)
                $results[] = true;
                $delta = $startLoc - $expectedLoc;
                if ($endLoc == -1) {
                    $text2 = mb_substr($text, $startLoc, $text1Len);
                } else {
                    $text2 = mb_substr($text, $startLoc, $endLoc + $maxBits - $startLoc);
                }
                if ($text1 == $text2) {
                    // Perfect match, just shove the replacement text in.
                    $text = mb_substr($text, 0, $startLoc) . $diff->text2() .
                        mb_substr($text, $startLoc + $text1Len);
                } else {
                    // Imperfect match.
                    // Run a diff to get a framework of equivalent indices.
                    $diff->main($text1, $text2, false);
                    if (
                        $text1Len > $maxBits &&
                        $diff->levenshtein() / $text1Len > $this->getDeleteTreshold()
                    ) {
                        // The end points match, but the content is unacceptably bad.
                        $results[count($results) - 1] = false;
                    } else {
                        $diff->cleanupSemanticLossless();
                        $index1 = 0;
                        foreach ($patch->getChanges() as $change) {
                            list ($op, $data) = $change;
                            if ($op != Diff::EQUAL) {
                                $index2 = $diff->xIndex($index1);
                                if ($op == Diff::INSERT) {
                                    $text = mb_substr($text, 0, $startLoc + $index2) . $data .
                                        mb_substr($text, $startLoc + $index2);
                                } elseif ($op == Diff::DELETE) {
                                    $text = mb_substr($text, 0, $startLoc + $index2) .
                                        mb_substr($text, $startLoc + $diff->xIndex($index1 + mb_strlen($data)));
                                }
                            }
                            if ($op != Diff::DELETE) {
                                $index1 += mb_strlen($data);
                            }
                        }
                    }
                }
            }
        }

        // Strip the padding off.
        $text = mb_substr($text, mb_strlen($nullPadding), -mb_strlen($nullPadding));

        return array($text, $results);
    }
public function apply($patches, $text)
    {
        if (empty($patches)) {
            return array($text, array());
        }
        // Deep copy the patches so that no changes are made to originals.
        // FIXME don't need in PHP
        $patches = $this->deepCopy($patches);

        $nullPadding = $this->addPadding($patches);
        $text = $nullPadding . $text . $nullPadding;
        $this->splitMax($patches);

        // Delta keeps track of the offset between the expected and actual location
        // of the previous patch.  If there are patches expected at positions 10 and
        // 20, but the first patch was found at 12, delta is 2 and the second patch
        // has an effective expected position of 22.
        $delta = 0;
        $results = array();
        $diff = $this->getDiff();
        $match = $this->getMatch();
        $maxBits = $match->getMaxBits();

        foreach ($patches as $patch) {
            $expectedLoc = $patch->getStart2() + $delta;
            $diff->setChanges($patch->getChanges());
            $text1 = $diff->text1();
            $text1Len = mb_strlen($text1);
            $endLoc = -1;

            if ($text1Len > $maxBits) {
                // self::splitMax() will only provide an oversized pattern in the case of
                // a monster delete.
                $startLoc = $match->main($text, mb_substr($text1, 0, $maxBits), $expectedLoc);

                if ($startLoc != -1) {
                    $endLoc = $match->main($text, mb_substr($text1, -$maxBits),
                        $expectedLoc + $text1Len - $maxBits);
                    if ($endLoc == -1 || $startLoc >= $endLoc) {
                        // Can't find valid trailing context.  Drop this patch.
                        $startLoc = -1;
                    }
                }
            } else {
                $startLoc = $match->main($text, $text1, $expectedLoc);
            }

            if ($startLoc == -1) {
                // No match found.  :(
                $results[] = false;
                // Subtract the delta for this failed patch from subsequent patches.
                $delta -= $patch->getLength2() - $patch->getLength1();
            } else {
                // Found a match.  :)
                $results[] = true;
                $delta = $startLoc - $expectedLoc;
                if ($endLoc == -1) {
                    $text2 = mb_substr($text, $startLoc, $text1Len);
                } else {
                    $text2 = mb_substr($text, $startLoc, $endLoc + $maxBits - $startLoc);
                }
                if ($text1 == $text2) {
                    // Perfect match, just shove the replacement text in.
                    $text = mb_substr($text, 0, $startLoc) . $diff->text2() .
                        mb_substr($text, $startLoc + $text1Len);
                } else {
                    // Imperfect match.
                    // Run a diff to get a framework of equivalent indices.
                    $diff->main($text1, $text2, false);
                    if (
                        $text1Len > $maxBits &&
                        $diff->levenshtein() / $text1Len > $this->getDeleteTreshold()
                    ) {
                        // The end points match, but the content is unacceptably bad.
                        $results[count($results) - 1] = false;
                    } else {
                        $diff->cleanupSemanticLossless();
                        $index1 = 0;
                        foreach ($patch->getChanges() as $change) {
                            list ($op, $data) = $change;
                            if ($op != Diff::EQUAL) {
                                $index2 = $diff->xIndex($index1);
                                if ($op == Diff::INSERT) {
                                    $text = mb_substr($text, 0, $startLoc + $index2) . $data .
                                        mb_substr($text, $startLoc + $index2);
                                } elseif ($op == Diff::DELETE) {
                                    $text = mb_substr($text, 0, $startLoc + $index2) .
                                        mb_substr($text, $startLoc + $diff->xIndex($index1 + mb_strlen($data)));
                                }
                            }
                            if ($op != Diff::DELETE) {
                                $index1 += mb_strlen($data);
                            }
                        }
                    }
                }
            }
        }

        // Strip the padding off.
        $text = mb_substr($text, mb_strlen($nullPadding), -mb_strlen($nullPadding));

        return array($text, $results);
    }
Merge a set of patches onto the text.  Return a patched text, as well
as a list of true/false values indicating which patches were applied.

@param PatchObject[] $patches Array of PatchObjects.
@param string        $text    Old text.

@return array Two element Array, containing the new text and an array of boolean values.