/// <summary> /// Parse a textual representation of patches and return a List of Patch objects. /// </summary> /// <param name="textline">Text representation of patches.</param> /// <returns>List of Patch objects.</returns> /// <exception cref="ArgumentException">If invalid input.</exception> public List <Patch> PatchFromText(string textline) { List <Patch> patches = new List <Patch>(); if (textline.Length == 0) { return(patches); } string[] text = textline.Split('\n'); int textPointer = 0; Patch patch; Regex patchHeader = new Regex("^@@ -(\\d+),?(\\d*) \\+(\\d+),?(\\d*) @@$"); Match m; char sign; string line; while (textPointer < text.Length) { m = patchHeader.Match(text[textPointer]); if (!m.Success) { throw new ArgumentException("Invalid patch string: " + text[textPointer]); } patch = new Patch(); patches.Add(patch); patch.start1 = Convert.ToInt32(m.Groups[1].Value); if (m.Groups[2].Length == 0) { patch.start1--; patch.length1 = 1; } else if (m.Groups[2].Value == "0") { patch.length1 = 0; } else { patch.start1--; patch.length1 = Convert.ToInt32(m.Groups[2].Value); } patch.start2 = Convert.ToInt32(m.Groups[3].Value); if (m.Groups[4].Length == 0) { patch.start2--; patch.length2 = 1; } else if (m.Groups[4].Value == "0") { patch.length2 = 0; } else { patch.start2--; patch.length2 = Convert.ToInt32(m.Groups[4].Value); } textPointer++; while (textPointer < text.Length) { try { sign = text[textPointer][0]; } catch (IndexOutOfRangeException) { // Blank line? Whatever. textPointer++; continue; } line = text[textPointer].Substring(1); line = line.Replace("+", "%2b"); line = System.Net.WebUtility.UrlDecode(line); if (sign == '-') { // Deletion. patch.diffs.Add(new Diff(Operation.DELETE, line)); } else if (sign == '+') { // Insertion. patch.diffs.Add(new Diff(Operation.INSERT, line)); } else if (sign == ' ') { // Minor equality. patch.diffs.Add(new Diff(Operation.EQUAL, line)); } else if (sign == '@') { // Start of next patch. break; } else { // WTF? throw new ArgumentException( "Invalid patch mode '" + sign + "' in: " + line); } textPointer++; } } return(patches); }
/// <summary> /// Add some padding on text start and end so that edges can match something. /// </summary> /// <remarks>Intended to be called only from within patch_apply.</remarks> /// <param name="patches">Array of Patch objects.</param> /// <returns>The padding string added to each side.</returns> public string PatchAddPadding(List <Patch> patches) { short paddingLength = this.Patch_Margin; string nullPadding = string.Empty; for (short x = 1; x <= paddingLength; x++) { nullPadding += (char)x; } // Bump all the patches forward. foreach (Patch aPatch in patches) { aPatch.start1 += paddingLength; aPatch.start2 += paddingLength; } // Add some padding on start of first diff. Patch patch = patches.First(); List <Diff> diffs = patch.diffs; if (diffs.Count == 0 || diffs.First().operation != Operation.EQUAL) { // Add nullPadding equality. diffs.Insert(0, new Diff(Operation.EQUAL, nullPadding)); patch.start1 -= paddingLength; // Should be 0. patch.start2 -= paddingLength; // Should be 0. patch.length1 += paddingLength; patch.length2 += paddingLength; } else if (paddingLength > diffs.First().text.Length) { // Grow first equality. Diff firstDiff = diffs.First(); int extraLength = paddingLength - firstDiff.text.Length; firstDiff.text = nullPadding.Substring(firstDiff.text.Length) + firstDiff.text; patch.start1 -= extraLength; patch.start2 -= extraLength; patch.length1 += extraLength; patch.length2 += extraLength; } // Add some padding on end of last diff. patch = patches.Last(); diffs = patch.diffs; if (diffs.Count == 0 || diffs.Last().operation != Operation.EQUAL) { // Add nullPadding equality. diffs.Add(new Diff(Operation.EQUAL, nullPadding)); patch.length1 += paddingLength; patch.length2 += paddingLength; } else if (paddingLength > diffs.Last().text.Length) { // Grow last equality. Diff lastDiff = diffs.Last(); int extraLength = paddingLength - lastDiff.text.Length; lastDiff.text += nullPadding.Substring(0, extraLength); patch.length1 += extraLength; patch.length2 += extraLength; } return(nullPadding); }
/// <summary> /// Look through the patches and break up any which are longer than the /// maximum limit of the match algorithm. /// </summary> /// <remarks>Intended to be called only from within patch_apply.</remarks> /// <param name="patches">List of Patch objects.</param> public void PatchSplitMax(List <Patch> patches) { short patch_size = Match.Match_MaxBits; for (int x = 0; x < patches.Count; x++) { if (patches[x].length1 <= patch_size) { continue; } Patch bigpatch = patches[x]; // Remove the big old patch. patches.Splice(x--, 1); int start1 = bigpatch.start1; int start2 = bigpatch.start2; string precontext = string.Empty; while (bigpatch.diffs.Count != 0) { // Create one of several smaller patches. Patch patch = new Patch(); bool 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(new Diff(Operation.EQUAL, precontext)); } while (bigpatch.diffs.Count != 0 && patch.length1 < patch_size - this.Patch_Margin) { Operation diff_type = bigpatch.diffs[0].operation; string diff_text = bigpatch.diffs[0].text; if (diff_type == Operation.INSERT) { // Insertions are harmless. patch.length2 += diff_text.Length; start2 += diff_text.Length; patch.diffs.Add(bigpatch.diffs.First()); bigpatch.diffs.RemoveAt(0); empty = false; } else if (diff_type == Operation.DELETE && patch.diffs.Count == 1 && patch.diffs.First().operation == Operation.EQUAL && diff_text.Length > 2 * patch_size) { // This is a large deletion. Let it pass in one chunk. patch.length1 += diff_text.Length; start1 += diff_text.Length; empty = false; patch.diffs.Add(new Diff(diff_type, diff_text)); bigpatch.diffs.RemoveAt(0); } else { // Deletion or equality. Only take as much as we can stomach. diff_text = diff_text.Substring(0, Math.Min(diff_text.Length, patch_size - patch.length1 - Patch_Margin)); patch.length1 += diff_text.Length; start1 += diff_text.Length; if (diff_type == Operation.EQUAL) { patch.length2 += diff_text.Length; start2 += diff_text.Length; } else { empty = false; } patch.diffs.Add(new Diff(diff_type, diff_text)); if (diff_text == bigpatch.diffs[0].text) { bigpatch.diffs.RemoveAt(0); } else { bigpatch.diffs[0].text = bigpatch.diffs[0].text.Substring(diff_text.Length); } } } // Compute the head context for the next patch. precontext = DiffOps.GetText2(patch.diffs); precontext = precontext.Substring(Math.Max(0, precontext.Length - this.Patch_Margin)); string postcontext = null; // Append the end context for this patch. if (DiffOps.GetText1(bigpatch.diffs).Length > Patch_Margin) { postcontext = DiffOps.GetText1(bigpatch.diffs) .Substring(0, Patch_Margin); } else { postcontext = DiffOps.GetText1(bigpatch.diffs); } 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].text += postcontext; } else { patch.diffs.Add(new Diff(Operation.EQUAL, postcontext)); } } if (!empty) { patches.Splice(++x, 0, patch); } } } }
/// <summary> /// Compute a list of patches to turn text1 into text2. /// text2 is not provided, diffs are the delta between text1 and text2. /// </summary> /// <param name="text1">Old text.</param> /// <param name="diffs">Array of Diff objects for text1 to text2.</param> /// <returns>List of Patch objects.</returns> public List <Patch> PatchMake(string text1, List <Diff> diffs) { // Check for null inputs not needed since null can't be passed in C#. List <Patch> patches = new List <Patch>(); if (diffs.Count == 0) { return(patches); // Get rid of the null case. } Patch patch = new Patch(); int char_count1 = 0; // Number of characters into the text1 string. int char_count2 = 0; // Number of characters into the text2 string. // Start with text1 (prepatch_text) and apply the diffs until we arrive at // text2 (postpatch_text). We recreate the patches one by one to determine // context info. string prepatch_text = text1; string postpatch_text = text1; foreach (Diff aDiff in diffs) { if (patch.diffs.Count == 0 && aDiff.operation != Operation.EQUAL) { // A new patch starts here. patch.start1 = char_count1; patch.start2 = char_count2; } switch (aDiff.operation) { case Operation.INSERT: patch.diffs.Add(aDiff); patch.length2 += aDiff.text.Length; postpatch_text = postpatch_text.Insert(char_count2, aDiff.text); break; case Operation.DELETE: patch.length1 += aDiff.text.Length; patch.diffs.Add(aDiff); postpatch_text = postpatch_text.Remove(char_count2, aDiff.text.Length); break; case Operation.EQUAL: if (aDiff.text.Length <= 2 * Patch_Margin && patch.diffs.Count() != 0 && aDiff != diffs.Last()) { // Small equality inside a patch. patch.diffs.Add(aDiff); patch.length1 += aDiff.text.Length; patch.length2 += aDiff.text.Length; } if (aDiff.text.Length >= 2 * Patch_Margin) { // Time for a new patch. if (patch.diffs.Count != 0) { PatchAaddContext(patch, prepatch_text); patches.Add(patch); patch = new Patch(); // Unlike Unidiff, our patch lists have a rolling context. // http://code.google.com/p/google-diff-match-patch/wiki/Unidiff // Update prepatch text & pos to reflect the application of the // just completed patch. prepatch_text = postpatch_text; char_count1 = char_count2; } } break; } // Update the current character count. if (aDiff.operation != Operation.INSERT) { char_count1 += aDiff.text.Length; } if (aDiff.operation != Operation.DELETE) { char_count2 += aDiff.text.Length; } } // Pick up the leftover patch if not empty. if (patch.diffs.Count != 0) { PatchAaddContext(patch, prepatch_text); patches.Add(patch); } return(patches); }