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);
}