예제 #1
0
        /// <summary>
        /// Parse a textual represenation of patches and return a List of PatchOp objects.
        /// </summary>
        /// <param name="textline">Text representation of patches</param>
        /// <returns>List of PatchOp objects.</returns>
        public static List <PatchOp> PatchFromText(this string textline)
        {
            List <PatchOp> res = new List <PatchOp>();

            if (string.IsNullOrEmpty(textline) || textline == "<null>")
            {
                return(res);
            }

            string[] text = textline.Split('\n');
            int      pos  = 0;

            while (pos < text.Length)
            {
                if (!PatchOp.TryParse(text[pos], out PatchOp patch))
                {
                    throw new ArgumentException($"Invalid path string: {text[pos]}");
                }
                res.Add(patch);
                pos++;

                while (pos < text.Length)
                {
                    if (string.IsNullOrEmpty(text[pos]))
                    {
                        // Blank line; continue.
                        pos++;
                        continue;
                    }
                    if (text[pos].StartsWith("@"))                     // Start next patch.
                    {
                        break;
                    }

                    if (DiffOp.TryParse(text[pos], out DiffOp diff))
                    {
                        patch.Diffs.AddIfNotNull(diff);
                        pos++;
                    }
                    else
                    {
                        break;
                    }
                }
            }
            return(res);
        }
예제 #2
0
        /// <summary>
        /// Increase the context until it is unique, but don't let the pattern expand beyond MAX_BITS
        /// </summary>
        /// <param name="patch">The patch to grow</param>
        /// <param name="text">Source text</param>
        /// <param name="margin">Chunk size for context length.</param>
        private static void AddContext(this PatchOp patch, string text, short margin = 4)
        {
            if (string.IsNullOrEmpty(text))
            {
                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 < MAX_BITS - margin - margin)
            {
                padding += 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 += 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 DiffOp(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 DiffOp(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;
        }
예제 #3
0
        internal static bool TryParse(string s, out PatchOp res)
        {
            Match m = PATCH_HEADER.Match(s);

            if (!m.Success)
            {
                res = null;
                return(false);
            }

            int[] coords = new int[4];
            for (int i = 1; i < 5; i++)
            {
                if (i % 2 == 1)                 // odd (start)
                {
                    coords[i - 1] = int.Parse(m.Groups[i].Value);
                }
                else                    //even (length)
                {
                    if (m.Groups[i].Length == 0)
                    {
                        coords[i - 2]--;
                        coords[i - 1] = 1;
                    }
                    else if (m.Groups[i].Value == "0")
                    {
                        coords[i - 1] = 0;
                    }
                    else
                    {
                        coords[i - 2]--;
                        coords[i - 1] = int.Parse(m.Groups[i].Value);
                    }
                }
            }
            res = new PatchOp(coords);
            return(true);
        }
예제 #4
0
        /// <summary>
        /// Add some padding on text start and end so that edges can match something.
        /// </summary>
        /// <param name="patches">List of PatchOp objects</param>
        /// <param name="margin">Chuck size for context length.</param>
        /// <returns>The padding added to each side.</returns>
        private static string AddPadding(this List <PatchOp> patches, short margin = 4)
        {
            string padding = string.Empty;

            for (short x = 1; x <= margin; x++)
            {
                padding += (char)x;
            }

            // Bump all the patches forward.
            foreach (var aPatch in patches)
            {
                aPatch.Start1 += margin;
                aPatch.Start2 += margin;
            }

            // Add some padding on start of first diff.
            PatchOp patch = patches.First();

            if (patch.Diffs.Count == 0 || patch.Diffs.First().Operation != Operation.EQUAL)
            {
                // Add padding equality.
                patch.Diffs.Insert(0, new DiffOp(Operation.EQUAL, padding));
                patch.Start1  -= margin;                // should be 0;
                patch.Start2  -= margin;                // should be 0;
                patch.Length1 += margin;
                patch.Length2 += margin;
            }
            else if (margin > patch.Diffs.First().Text.Length)
            {
                // Grow first equality.
                DiffOp fDif     = patch.Diffs.First();
                int    extraLen = margin - fDif.Text.Length;
                fDif.Text      = padding.Substring(fDif.Text.Length) + fDif.Text;
                patch.Start1  -= extraLen;
                patch.Start2  -= extraLen;
                patch.Length1 += extraLen;
                patch.Length2 += extraLen;
            }

            // Add some padding on end of last diff.
            patch = patches.Last();
            if (patch.Diffs.Count == 0 || patch.Diffs.Last().Operation != Operation.EQUAL)
            {
                // Add padding equality
                patch.Diffs.Add(new DiffOp(Operation.EQUAL, padding));
                patch.Length1 += margin;
                patch.Length2 += margin;
            }
            else if (margin > patch.Diffs.Last().Text.Length)
            {
                // Grow last equality.
                DiffOp lDif    = patch.Diffs.Last();
                int    extaLen = margin - lDif.Text.Length;
                lDif.Text     += padding.Substring(0, extaLen);
                patch.Length1 += extaLen;
                patch.Length2 += extaLen;
            }

            return(padding);
        }
예제 #5
0
        /// <summary>
        /// Compute a list of patches to turn original into current text.
        /// </summary>
        /// <remarks>Current text is not provided, it is inferred from the DiffOps</remarks>
        /// <param name="original">Original text.</param>
        /// <param name="diffs">List of DiffOp objects between original and current text.</param>
        /// <param name="margin">Chuck size for context length (default = 4).</param>
        /// <returns>List of PatchOp objects.</returns>
        public static List <PatchOp> MakePatch(this string original, List <DiffOp> diffs, short margin = 4)
        {
            List <PatchOp> res = new List <PatchOp>();

            if (diffs == null || diffs.Count < 1)
            {
                return(res);                // Get rid of the null case.
            }
            PatchOp patch  = new PatchOp();
            int     cChar1 = 0;         // Number of characters in the text1 string.
            int     cChar2 = 0;         // Number of characters in the text2 string.

            // Start with text1 (prepatch) and apply the diffs until we arrive at text2 (postpatch).
            // We recreate the patches one-by-one to determine the contact info.
            string prepatch  = original;
            string postpatch = original;

            foreach (DiffOp aDiff in diffs)
            {
                if (patch.Diffs.Count == 0 && aDiff.Operation != Operation.EQUAL)
                {
                    // A new patch starts here.
                    patch.Start1 = cChar1;
                    patch.Start2 = cChar2;
                }

                switch (aDiff.Operation)
                {
                case Operation.INSERT: {
                    patch.Diffs.Add(aDiff);
                    patch.Length2 += aDiff.Text.Length;
                    postpatch      = postpatch.Insert(cChar2, aDiff.Text);
                    break;
                }

                case Operation.DELETE: {
                    patch.Length1 += aDiff.Text.Length;
                    patch.Diffs.Add(aDiff);
                    postpatch = postpatch.Remove(cChar2, aDiff.Text.Length);
                    break;
                }

                default: {
                    if (aDiff.Text.Length <= 2 * 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 * margin)
                    {
                        // Time for a new patch.
                        if (patch.Diffs.Count > 0)
                        {
                            patch.AddContext(prepatch);
                            res.Add(patch);
                            patch = new PatchOp();

                            // Unlike UniDiff, our patch lists have a rolling context. https://github.com/google/diff-match-patch/wiki/unidiff
                            // Update prepatch text & pos to reflect the application of the just completed patch.
                            prepatch = postpatch;
                            cChar1   = cChar2;
                        }
                    }
                    break;
                }
                }

                // Update the current character count;
                if (aDiff.Operation != Operation.INSERT)
                {
                    cChar1 += aDiff.Text.Length;
                }
                if (aDiff.Operation != Operation.DELETE)
                {
                    cChar2 += aDiff.Text.Length;
                }
            }
            // Pick up the leftover patch if not empty
            if (patch.Diffs.Count != 0)
            {
                patch.AddContext(prepatch);
                res.Add(patch);
            }

            return(res);
        }
예제 #6
0
        /// <summary>
        /// Look through the patches and break up any which are longer than the maximum limit of the match algorithm.
        /// </summary>
        /// <param name="patches">List of PatchOp objects.</param>
        private static void SplitMax(this List <PatchOp> patches, short margin = 4)
        {
            for (int x = 0; x < patches.Count; x++)
            {
                if (patches[x].Length1 <= MAX_BITS)
                {
                    continue;
                }
                PatchOp 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 small patches
                    PatchOp nPatch = new PatchOp();
                    bool    empty  = true;
                    nPatch.Start1 = start1 - precontext.Length;
                    nPatch.Start2 = start2 - precontext.Length;
                    if (precontext.Length > 0)
                    {
                        nPatch.Length1 = precontext.Length;
                        nPatch.Length2 = precontext.Length;
                        nPatch.Diffs.Add(new DiffOp(Operation.EQUAL, precontext));
                    }
                    while (bigPatch.Diffs.Count != 0 && nPatch.Length1 < MAX_BITS - margin)
                    {
                        Operation diffType = bigPatch.Diffs[0].Operation;
                        string    diffText = bigPatch.Diffs[0].Text;
                        if (diffType == Operation.INSERT)
                        {
                            // Insertions are harmless
                            nPatch.Length2 = diffText.Length;
                            start2        += diffText.Length;
                            nPatch.Diffs.Add(bigPatch.Diffs[0].Clone());
                            bigPatch.Diffs.RemoveAt(0);
                        }
                        else if (diffType == Operation.DELETE && nPatch.Diffs.Count == 1 &&
                                 nPatch.Diffs.First().Operation == Operation.EQUAL && diffText.Length > 2 * MAX_BITS)
                        {
                            // This is a large deletion. Let it pass in one chunk.
                            nPatch.Length1 += diffText.Length;
                            start1         += diffText.Length;
                            empty           = false;
                            nPatch.Diffs.Add(new DiffOp(diffType, diffText));
                            bigPatch.Diffs.RemoveAt(0);
                        }
                        else
                        {
                            //Deletion or equality. Only take as much as we can stomach.
                            diffText       = diffText.Substring(0, Math.Min(diffText.Length, MAX_BITS - nPatch.Length1 - margin));
                            nPatch.Length1 = diffText.Length;
                            start1        += diffText.Length;
                            if (diffType == Operation.EQUAL)
                            {
                                nPatch.Length2 += diffText.Length;
                                start2         += diffText.Length;
                            }
                            else
                            {
                                empty = false;
                            }
                            nPatch.Diffs.Add(new DiffOp(diffType, diffText));
                            if (diffText == bigPatch.Diffs[0].Text)
                            {
                                bigPatch.Diffs.RemoveAt(0);
                            }
                            else
                            {
                                bigPatch.Diffs[0].Text = bigPatch.Diffs[0].Text.Substring(diffText.Length);
                            }
                        }
                    }
                    // Compute the head context for the next patch.
                    precontext = nPatch.Diffs.CurrentText();
                    precontext = precontext.Substring(Math.Max(0, precontext.Length - margin));

                    // Append the end context for this patch.
                    string orig = bigPatch.Diffs.OriginalText();
                    string postContext;
                    if (orig.Length > margin)
                    {
                        postContext = orig.Substring(0, margin);
                    }
                    else
                    {
                        postContext = orig;
                    }

                    if (postContext.Length > 0)
                    {
                        nPatch.Length1 += postContext.Length;
                        nPatch.Length2 += postContext.Length;
                        if (nPatch.Diffs.Count > 0 && nPatch.Diffs[nPatch.Diffs.Count - 1].Operation == Operation.EQUAL)
                        {
                            nPatch.Diffs[nPatch.Diffs.Count - 1].Text += postContext;
                        }
                        else
                        {
                            nPatch.Diffs.Add(new DiffOp(Operation.EQUAL, postContext));
                        }
                    }
                    if (!empty)
                    {
                        patches.Splice(++x, 0, nPatch);
                    }
                }
            }
        }