private Chunk CreateChunk(Diff.Item change, CompareOptions options) { var chunk = new Chunk(); chunk.start1 = change.StartA; chunk.start2 = change.StartB; chunk.length1 = change.deletedA + options.ContextSize; chunk.length2 = change.insertedB + options.ContextSize; chunk.diffs = new List <Line>(); return(chunk); }
public void patch_patchObjTest() { // Patch Object. var p = new Patch{ start1 = 20, start2 = 21, length1 = 18, length2 = 17, diffs = new List<Diff>{ new Diff(Operation.Equal, "jump"), new Diff(Operation.Delete, "s"), new Diff(Operation.Insert, "ed"), new Diff(Operation.Equal, " over "), new Diff(Operation.Delete, "the"), new Diff(Operation.Insert, "a"), new Diff(Operation.Equal, "\nlaz") } }; const string strp = "@@ -21,18 +22,17 @@\n jump\n-s\n+ed\n over \n-the\n+a\n %0alaz\n"; Assert.AreEqual(strp, p.ToString(), "Patch: toString."); }
public void patch_patchObjTest() { // Patch Object. Patch p = new Patch(); p.start1 = 20; p.start2 = 21; p.length1 = 18; p.length2 = 17; p.diffs = new List<Diff> { new Diff(Operation.EQUAL, "jump"), new Diff(Operation.DELETE, "s"), new Diff(Operation.INSERT, "ed"), new Diff(Operation.EQUAL, " over "), new Diff(Operation.DELETE, "the"), new Diff(Operation.INSERT, "a"), new Diff(Operation.EQUAL, "\nlaz")}; string strp = "@@ -21,18 +22,17 @@\n jump\n-s\n+ed\n over \n-the\n+a\n %0alaz\n"; Assert.AreEqual(strp, p.ToString(), "Patch: toString."); }
public IEnumerable <DiffMatchPatch.Patch> MakePatch(string contentOne, string contentTwo, CompareOptions options) { var diff = new my.utils.Diff(); var changes = diff.DiffText(contentOne, contentTwo); var contentOneLines = contentOne.Split(new[] { Environment.NewLine }, StringSplitOptions.None); var contentTwoLines = contentTwo.Split(new[] { Environment.NewLine }, StringSplitOptions.None); var chunks = new HashSet <Chunk>(); Chunk currentChunk = null; for (var i = 0; i < changes.Length; i++) { var change = changes[i]; Diff.Item?nextChange = null; if (changes.Length > i + 1) { nextChange = changes.ElementAtOrDefault(i + 1); } var continuation = currentChunk != null; if (!continuation) { currentChunk = CreateChunk(change, options); chunks.Add(currentChunk); } if (change.StartA != 0 && !continuation) { // no start context needed currentChunk.start1 = change.StartA - options.ContextSize; currentChunk.start2 = change.StartB - options.ContextSize; // stick some context in var start = change.StartB - options.ContextSize; for (var j = start; j < change.StartB; j++) { currentChunk.diffs.Add(new Line(Operation.EQUAL, contentTwoLines[j])); } } if (change.deletedA > 0) { for (var j = 0; j < change.deletedA; j++) { var line = contentOneLines[j + change.StartA]; currentChunk.diffs.Add(new Line(Operation.DELETE, line)); } } if (change.insertedB > 0) { for (var j = 0; j < change.insertedB; j++) { var line = contentTwoLines[j + change.StartB]; currentChunk.diffs.Add(new Line(Operation.INSERT, line)); } } var start2 = change.StartB + change.insertedB; int end; if (nextChange.HasValue) { end = Min(start2 + options.ContextSize, contentTwoLines.Length, nextChange.Value.StartB); } else { end = Min(start2 + options.ContextSize, contentTwoLines.Length); } for (var j = start2; j < end; j++) { currentChunk.diffs.Add(new Line(Operation.EQUAL, contentTwoLines[j])); } if (nextChange.HasValue && nextChange.Value.StartB - end > 0) { // need to split the diff into multiple chunks currentChunk = null; } } foreach (var chunk in chunks) { chunk.length1 = chunk.diffs.Count(x => x.operation == Operation.EQUAL || x.operation == Operation.DELETE); chunk.length2 = chunk.diffs.Count(x => x.operation == Operation.EQUAL || x.operation == Operation.INSERT); } return(chunks); }
/** * 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. * @param patches List of Patch objects. */ public void patch_splitMax(List<Patch> patches) { const short patch_size = Match_MaxBits; for (int x = 0; x < patches.Count; x++) { if (patches[x].length1 > patch_size) { 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. var 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 - Patch_Margin) { Operation diff_type = bigpatch.diffs[0].operation; if (bigpatch.diffs[0].text == null) throw new Exception("Diff text null"); 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 = diff_text2(patch.diffs); precontext = precontext.Substring(Math.Max(0, precontext.Length - Patch_Margin)); string postcontext; // Append the end context for this patch. if (diff_text1(bigpatch.diffs).Length > Patch_Margin) { postcontext = diff_text1(bigpatch.diffs) .Substring(0, Patch_Margin); } else { postcontext = diff_text1(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); } } } } }
// PATCH FUNCTIONS /** * Increase the context until it is unique, * but don't let the pattern expand beyond Match_MaxBits. * @param patch The patch to grow. * @param text Source text. */ protected void patch_addContext(Patch patch, string text) { if (text.Length == 0) { return; } string pattern = text.Substring(patch.start2, patch.length1); int padding = 0; // Look for the first and last matches of pattern in text. If two // different matches are found, increase the pattern length. while (text.IndexOf(pattern, StringComparison.Ordinal) != text.LastIndexOf(pattern, StringComparison.Ordinal) && pattern.Length < Match_MaxBits - Patch_Margin - Patch_Margin) { padding += Patch_Margin; pattern = text.JavaSubstring(Math.Max(0, patch.start2 - padding), Math.Min(text.Length, patch.start2 + patch.length1 + padding)); } // Add one chunk for good luck. padding += Patch_Margin; // Add the prefix. string prefix = text.JavaSubstring(Math.Max(0, patch.start2 - padding), patch.start2); if (prefix.Length != 0) { patch.diffs.Insert(0, new Diff(Operation.Equal, prefix)); } // Add the suffix. string suffix = text.JavaSubstring(patch.start2 + patch.length1, Math.Min(text.Length, patch.start2 + patch.length1 + padding)); if (suffix.Length != 0) { patch.diffs.Add(new Diff(Operation.Equal, suffix)); } // Roll back the start points. patch.start1 -= prefix.Length; patch.start2 -= prefix.Length; // Extend the lengths. patch.length1 += prefix.Length + suffix.Length; patch.length2 += prefix.Length + suffix.Length; }
/** * Compute a list of patches to turn text1 into text2. * text2 is not provided, diffs are the delta between text1 and text2. * @param text1 Old text. * @param diffs Array of diff tuples for text1 to text2. * @return List of Patch objects. */ public List<Patch> patch_make(string text1, List<Diff> diffs) { // Check for null inputs not needed since null can't be passed in C#. var patches = new List<Patch>(); if (diffs.Count == 0) { return patches; // Get rid of the null case. } var 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) { patch_addContext(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) { patch_addContext(patch, prepatch_text); patches.Add(patch); } return patches; }
/** * Parse a textual representation of patches and return a List of Patch * objects. * @param textline Text representation of patches. * @return List of Patch objects. * @throws ArgumentException If invalid input. */ public List<Patch> patch_fromText(string textline) { var patches = new List<Patch>(); if (textline.Length == 0) { return patches; } string[] text = textline.Split('\n'); int textPointer = 0; Patch patch; var patchHeader = new Regex("^@@ -(\\d+),?(\\d*) \\+(\\d+),?(\\d*) @@$"); Match m; 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) { char sign; try { sign = text[textPointer][0]; } catch (IndexOutOfRangeException) { // Blank line? Whatever. textPointer++; continue; } line = text[textPointer].Substring(1); line = line.Replace("+", "%2b"); line = HttpUtility.UrlDecode(line, new UTF8Encoding(false, true)); 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; }
/** * Given an array of patches, return another array that is identical. * @param patches Array of patch objects. * @return Array of patch objects. */ public List<Patch> patch_deepCopy(List<Patch> patches) { var patchesCopy = new List<Patch>(); foreach (Patch aPatch in patches) { var patchCopy = new Patch(); foreach (Diff aDiff in aPatch.diffs) { var diffCopy = new Diff(aDiff.operation, aDiff.text); patchCopy.diffs.Add(diffCopy); } patchCopy.start1 = aPatch.start1; patchCopy.start2 = aPatch.start2; patchCopy.length1 = aPatch.length1; patchCopy.length2 = aPatch.length2; patchesCopy.Add(patchCopy); } return patchesCopy; }
/// <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"></param> /// <param name="diffs"></param> /// <param name="patchMargin"></param> /// <returns></returns> public static List <Patch> Compute(string text1, List <Diff> diffs, short patchMargin = 4) { // Check for null inputs not needed since null can't be passed in C#. var patches = new List <Patch>(); if (diffs.Count == 0) { return(patches); // Get rid of the null case. } var patch = new Patch(); var charCount1 = 0; // Number of characters into the text1 string. var charCount2 = 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. var prepatchText = text1; var postpatchText = text1; foreach (var aDiff in diffs) { if (!patch.Diffs.Any() && aDiff.Operation != Operation.Equal) { // A new patch starts here. patch.Start1 = charCount1; patch.Start2 = charCount2; } switch (aDiff.Operation) { case Operation.Insert: patch.Diffs.Add(aDiff); patch.Length2 += aDiff.Text.Length; postpatchText = postpatchText.Insert(charCount2, aDiff.Text); break; case Operation.Delete: patch.Length1 += aDiff.Text.Length; patch.Diffs.Add(aDiff); postpatchText = postpatchText.Remove(charCount2, aDiff.Text.Length); break; case Operation.Equal: if (aDiff.Text.Length <= 2 * patchMargin && patch.Diffs.Any() && 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 * patchMargin) { // Time for a new patch. if (patch.Diffs.Any()) { patch.AddContext(prepatchText); 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. prepatchText = postpatchText; charCount1 = charCount2; } } break; } // Update the current character count. if (aDiff.Operation != Operation.Insert) { charCount1 += aDiff.Text.Length; } if (aDiff.Operation != Operation.Delete) { charCount2 += aDiff.Text.Length; } } // Pick up the leftover patch if not empty. if (patch.Diffs.Any()) { patch.AddContext(prepatchText); patches.Add(patch); } return(patches); }
private Chunk CreateChunk(Diff.Item change, CompareOptions options) { var chunk = new Chunk(); chunk.start1 = change.StartA; chunk.start2 = change.StartB; chunk.length1 = change.deletedA + options.ContextSize; chunk.length2 = change.insertedB + options.ContextSize; chunk.diffs = new List<Line>(); return chunk; }