patchApply function
Merge a set of patches onto the text.
Return a patched text, as well as an array of true/false values indicating which patches were applied.
patches
is a List of Patch objectstext
is the old text.
Returns a two element List, containing the new text and a List of bool values.
Implementation
List patchApply(List<Patch> patches, String text,
{double deleteThreshold = 0.5,
double diffTimeout = 1.0,
DateTime? diffDeadline,
double matchThreshold = 0.5,
int matchDistance = 1000,
int margin = 4}) {
if (patches.isEmpty) {
return [text, []];
}
// Deep copy the patches so that no changes are made to originals.
patches = patchDeepCopy(patches);
final nullPadding = patchAddPadding(patches, margin: margin);
text = '$nullPadding$text$nullPadding';
patchSplitMax(patches, margin: margin);
final text_buffer = StringBuffer();
var x = 0;
// 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.
var delta = 0;
final results = List<bool>.filled(patches.length, false);
for (var aPatch in patches) {
var expected_loc = aPatch.start2 + delta;
var text1 = diffText1(aPatch.diffs);
int start_loc;
var end_loc = -1;
if (text1.length > BITS_PER_INT) {
// patch_splitMax will only provide an oversized pattern in the case of
// a monster delete.
start_loc = match(text, text1.substring(0, BITS_PER_INT), expected_loc,
threshold: matchThreshold, distance: matchDistance);
if (start_loc != -1) {
end_loc = match(text, text1.substring(text1.length - BITS_PER_INT),
expected_loc + text1.length - BITS_PER_INT,
threshold: matchThreshold, distance: matchDistance);
if (end_loc == -1 || start_loc >= end_loc) {
// Can't find valid trailing context. Drop this patch.
start_loc = -1;
}
}
} else {
start_loc = match(text, text1, expected_loc,
threshold: matchThreshold, distance: matchDistance);
}
if (start_loc == -1) {
// No match found. :(
results[x] = false;
// Subtract the delta for this failed patch from subsequent patches.
delta -= aPatch.length2 - aPatch.length1;
} else {
// Found a match. :)
results[x] = true;
delta = start_loc - expected_loc;
String text2;
if (end_loc == -1) {
text2 = text.substring(
start_loc, min(start_loc + text1.length, text.length));
} else {
text2 =
text.substring(start_loc, min(end_loc + BITS_PER_INT, text.length));
}
if (text1 == text2) {
// Perfect match, just shove the replacement text in.
text_buffer.clear();
text_buffer
..write(text.substring(0, start_loc))
..write(diffText2(aPatch.diffs))
..write(text.substring(start_loc + text1.length));
text = text_buffer.toString();
} else {
// Imperfect match. Run a diff to get a framework of equivalent
// indices.
final diffs = diff(text1, text2,
checklines: false, deadline: diffDeadline, timeout: diffTimeout);
if ((text1.length > BITS_PER_INT) &&
(levenshtein(diffs) / text1.length > deleteThreshold)) {
// The end points match, but the content is unacceptably bad.
results[x] = false;
} else {
cleanupSemanticLossless(diffs);
var index1 = 0;
for (var aDiff in aPatch.diffs) {
if (aDiff.operation != DIFF_EQUAL) {
var index2 = diffXIndex(diffs, index1);
if (aDiff.operation == DIFF_INSERT) {
// Insertion
text_buffer.clear();
text_buffer
..write(text.substring(0, start_loc + index2))
..write(aDiff.text)
..write(text.substring(start_loc + index2));
text = text_buffer.toString();
} else if (aDiff.operation == DIFF_DELETE) {
// Deletion
text_buffer.clear();
text_buffer
..write(text.substring(0, start_loc + index2))
..write(text.substring(start_loc +
diffXIndex(diffs, index1 + aDiff.text.length)));
text = text_buffer.toString();
}
}
if (aDiff.operation != DIFF_DELETE) {
index1 += aDiff.text.length;
}
}
}
}
}
x++;
}
// Strip the padding off.
text = text.substring(nullPadding.length, text.length - nullPadding.length);
return [text, results];
}