/// <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);
        }
Example #2
0
        /// <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}).");
            }
        }
Example #3
0
        /// <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);
                    }
                }
            }
        }