/// <summary> /// Parse a textual representation of patches and return a List of Patch /// objects.</summary> /// <param name="text"></param> /// <returns></returns> public static List <Patch> Parse(string text) { var patches = new List <Patch>(); if (text.Length == 0) { return(patches); } var lines = text.Split('\n'); var index = 0; while (index < lines.Length) { var m = PatchHeader.Match(lines[index]); if (!m.Success) { throw new ArgumentException("Invalid patch string: " + lines[index]); } (var start1, var length1) = m.GetStartLength(1, 2); (var start2, var length2) = m.GetStartLength(3, 4); index++; var diffs = new List <Diff>(); while (index < lines.Length) { if (!string.IsNullOrEmpty(lines[index])) { var sign = lines[index][0]; if (sign == '@') { // Start of next patch. break; } var line = lines[index].Substring(1).Replace("+", "%2b").UrlDecoded(); diffs.Add(Diff.Create((Operation)sign, line)); } index++; } var patch = new Patch ( start1, length1, start2, length2, diffs ); patches.Add(patch); } return(patches); }
/// <summary> /// Given the original text1, and an encoded string which describes the /// operations required to transform text1 into text2, compute the full diff. /// </summary> /// <param name="text1">Source string for the diff.</param> /// <param name="delta">Delta text.</param> /// <returns></returns> public static IEnumerable <Diff> FromDelta(string text1, string delta) { var pointer = 0; // Cursor in text1 var tokens = delta.Split(new[] { "\t" }, StringSplitOptions.None); foreach (var token in tokens) { if (token.Length == 0) { // Blank tokens are ok (from a trailing \t). continue; } // Each token begins with a one character parameter which specifies the // operation of this token (delete, insert, equality). var param = token.Substring(1); var operation = FromDelta(token[0]); string text; switch (operation) { case Operation.Insert: // decode would change all "+" to " " text = param.Replace("+", "%2b").UrlDecoded(); break; case Operation.Delete: case Operation.Equal: if (!int.TryParse(param, out var n)) { throw new ArgumentException($"Invalid number in Diff.FromDelta: {param}"); } if (pointer < 0 || n < 0 || pointer > text1.Length - n) { throw new ArgumentException($"Delta length ({pointer}) larger than source text length ({text1.Length})."); } text = text1.Substring(pointer, n); pointer += n; break; default: throw new ArgumentException($"Unknown Operation: {operation}"); } yield return(Diff.Create(operation, text)); } if (pointer != text1.Length) { throw new ArgumentException($"Delta length ({pointer}) smaller than source text length ({text1.Length})."); } }
/// <summary> /// Compute and return equivalent location in target text. /// </summary> /// <param name="diffs">list of diffs</param> /// <param name="location1">location in source</param> /// <returns>location in target</returns> internal static int FindEquivalentLocation2(this IEnumerable <Diff> diffs, int location1) { var chars1 = 0; var chars2 = 0; var lastChars1 = 0; var lastChars2 = 0; var lastDiff = Diff.Create(Operation.Equal, string.Empty); foreach (var aDiff in diffs) { if (aDiff.Operation != Operation.Insert) { // Equality or deletion. chars1 += aDiff.Text.Length; } if (aDiff.Operation != Operation.Delete) { // Equality or insertion. chars2 += aDiff.Text.Length; } if (chars1 > location1) { // Overshot the location. lastDiff = aDiff; break; } lastChars1 = chars1; lastChars2 = chars2; } if (lastDiff.Operation == Operation.Delete) { // The location was deleted. return(lastChars2); } // Add the remaining character length. return(lastChars2 + (location1 - lastChars1)); }
/// <summary> /// Find the differences between two texts. Assumes that the texts do not /// have any common prefix or suffix. /// </summary> /// <param name="text1">Old string to be diffed.</param> /// <param name="text2">New string to be diffed.</param> /// <param name="checklines">Speedup flag. If false, then don't run a line-level diff first to identify the changed areas. If true, then run a faster slightly less optimal diff.</param> /// <param name="token">Cancellation token for cooperative cancellation</param> /// <param name="optimizeForSpeed">Should optimizations be enabled?</param> /// <returns></returns> private static List <Diff> ComputeImpl( string text1, string text2, bool checklines, CancellationToken token, bool optimizeForSpeed) { var diffs = new List <Diff>(); if (text1.Length == 0) { // Just add some text (speedup). diffs.Add(Diff.Insert(text2)); return(diffs); } if (text2.Length == 0) { // Just delete some text (speedup). diffs.Add(Diff.Delete(text1)); return(diffs); } var longtext = text1.Length > text2.Length ? text1 : text2; var shorttext = text1.Length > text2.Length ? text2 : text1; var i = longtext.IndexOf(shorttext, StringComparison.Ordinal); if (i != -1) { // Shorter text is inside the longer text (speedup). var op = text1.Length > text2.Length ? Operation.Delete : Operation.Insert; diffs.Add(Diff.Create(op, longtext.Substring(0, i))); diffs.Add(Diff.Equal(shorttext)); diffs.Add(Diff.Create(op, longtext.Substring(i + shorttext.Length))); return(diffs); } if (shorttext.Length == 1) { // Single character string. // After the previous speedup, the character can't be an equality. diffs.Add(Diff.Delete(text1)); diffs.Add(Diff.Insert(text2)); return(diffs); } // Don't risk returning a non-optimal diff if we have unlimited time. if (optimizeForSpeed) { // Check to see if the problem can be split in two. var result = TextUtil.HalfMatch(text1, text2); if (!result.IsEmpty) { // A half-match was found, sort out the return data. // Send both pairs off for separate processing. var diffsA = Compute(result.Prefix1, result.Prefix2, checklines, token, optimizeForSpeed); var diffsB = Compute(result.Suffix1, result.Suffix2, checklines, token, optimizeForSpeed); // Merge the results. diffs = diffsA; diffs.Add(Diff.Equal(result.CommonMiddle)); diffs.AddRange(diffsB); return(diffs); } } if (checklines && text1.Length > 100 && text2.Length > 100) { return(LineDiff(text1, text2, token, optimizeForSpeed)); } return(MyersDiffBisect(text1, text2, token, optimizeForSpeed)); }
/// <summary> /// Look through the patches and break up any which are longer than the /// maximum limit of the match algorithm. /// Intended to be called only from within patch_apply. /// </summary> /// <param name="patches"></param> /// <param name="patchMargin"></param> internal static void SplitMax(this List <Patch> patches, short patchMargin = 4) { var patchSize = Constants.MatchMaxBits; for (var x = 0; x < patches.Count; x++) { if (patches[x].Length1 <= patchSize) { continue; } var bigpatch = patches[x]; // Remove the big old patch. patches.Splice(x--, 1); var start1 = bigpatch.Start1; var start2 = bigpatch.Start2; var precontext = string.Empty; var diffs = bigpatch.Diffs; while (diffs.Count != 0) { // Create one of several smaller patches. var patch = new Patch(); var empty = true; patch.Start1 = start1 - precontext.Length; patch.Start2 = start2 - precontext.Length; if (precontext.Length != 0) { patch.Length1 = patch.Length2 = precontext.Length; patch.Diffs.Add(Diff.Equal(precontext)); } while (diffs.Count != 0 && patch.Length1 < patchSize - patchMargin) { var diffType = diffs[0].Operation; var diffText = diffs[0].Text; if (diffType == Operation.Insert) { // Insertions are harmless. patch.Length2 += diffText.Length; start2 += diffText.Length; patch.Diffs.Add(diffs.First()); diffs.RemoveAt(0); empty = false; } else if (diffType == Operation.Delete && patch.Diffs.Count == 1 && patch.Diffs.First().Operation == Operation.Equal && diffText.Length > 2 * patchSize) { // This is a large deletion. Let it pass in one chunk. patch.Length1 += diffText.Length; start1 += diffText.Length; empty = false; patch.Diffs.Add(Diff.Create(diffType, diffText)); diffs.RemoveAt(0); } else { // Deletion or equality. Only take as much as we can stomach. diffText = diffText.Substring(0, Math.Min(diffText.Length, patchSize - patch.Length1 - patchMargin)); patch.Length1 += diffText.Length; start1 += diffText.Length; if (diffType == Operation.Equal) { patch.Length2 += diffText.Length; start2 += diffText.Length; } else { empty = false; } patch.Diffs.Add(Diff.Create(diffType, diffText)); if (diffText == diffs[0].Text) { diffs.RemoveAt(0); } else { diffs[0] = diffs[0].Replace(diffs[0].Text.Substring(diffText.Length)); } } } // Compute the head context for the next patch. precontext = patch.Diffs.Text2(); precontext = precontext.Substring(Math.Max(0, precontext.Length - patchMargin)); // Append the end context for this patch. var text1 = diffs.Text1(); var postcontext = text1.Length > patchMargin?text1.Substring(0, patchMargin) : text1; if (postcontext.Length != 0) { patch.Length1 += postcontext.Length; patch.Length2 += postcontext.Length; if (patch.Diffs.Count != 0 && patch.Diffs[patch.Diffs.Count - 1].Operation == Operation.Equal) { patch.Diffs[patch.Diffs.Count - 1] = patch.Diffs[patch.Diffs.Count - 1].Replace(patch.Diffs[patch.Diffs.Count - 1].Text + postcontext); } else { patch.Diffs.Add(Diff.Equal(postcontext)); } } if (!empty) { patches.Splice(++x, 0, patch); } } } }