diff --git a/csharp/.gitignore b/csharp/.gitignore new file mode 100644 index 0000000..f431ddc --- /dev/null +++ b/csharp/.gitignore @@ -0,0 +1,329 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush +.cr/ + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ diff --git a/csharp/DiffMatchPatch.Tests.Performance/DiffMainTest.cs b/csharp/DiffMatchPatch.Tests.Performance/DiffMainTest.cs new file mode 100644 index 0000000..11c6ac3 --- /dev/null +++ b/csharp/DiffMatchPatch.Tests.Performance/DiffMainTest.cs @@ -0,0 +1,45 @@ +/* + * Diff Match and Patch + * Copyright 2018 The diff-match-patch Authors. + * https://github.com/google/diff-match-patch + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Collections.Generic; +using System.IO; +using BenchmarkDotNet.Attributes; + +namespace Google.DiffMatchPatch.Tests.Performance +{ + public class DiffMainTest + { + private string _text1; + private string _text2; + + [GlobalSetup] + public void Init() + { + _text1 = File.ReadAllText("Speedtest1.txt"); + _text2 = File.ReadAllText("Speedtest2.txt"); + } + + [Benchmark] + public List DiffMain() + { + var dmp = new diff_match_patch {Diff_Timeout = 0}; + + return dmp.diff_main(_text1, _text2); + } + } +} diff --git a/csharp/DiffMatchPatch.Tests.Performance/DiffMatchPatch.Tests.Performance.csproj b/csharp/DiffMatchPatch.Tests.Performance/DiffMatchPatch.Tests.Performance.csproj new file mode 100644 index 0000000..ee6a5e4 --- /dev/null +++ b/csharp/DiffMatchPatch.Tests.Performance/DiffMatchPatch.Tests.Performance.csproj @@ -0,0 +1,21 @@ + + + Exe + netcoreapp2.1 + Google.DiffMatchPatch.Tests.Performance + + + + + + + + + + PreserveNewest + + + PreserveNewest + + + \ No newline at end of file diff --git a/csharp/DiffMatchPatch.Tests.Performance/Program.cs b/csharp/DiffMatchPatch.Tests.Performance/Program.cs new file mode 100644 index 0000000..0dcd0a8 --- /dev/null +++ b/csharp/DiffMatchPatch.Tests.Performance/Program.cs @@ -0,0 +1,30 @@ +/* + * Diff Match and Patch + * Copyright 2018 The diff-match-patch Authors. + * https://github.com/google/diff-match-patch + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using BenchmarkDotNet.Running; + +namespace Google.DiffMatchPatch.Tests.Performance +{ + internal static class Program + { + private static void Main(string[] args) + { + var summary = BenchmarkRunner.Run(); + } + } +} diff --git a/csharp/tests/Speedtest1.txt b/csharp/DiffMatchPatch.Tests.Performance/Speedtest1.txt similarity index 100% rename from csharp/tests/Speedtest1.txt rename to csharp/DiffMatchPatch.Tests.Performance/Speedtest1.txt diff --git a/csharp/tests/Speedtest2.txt b/csharp/DiffMatchPatch.Tests.Performance/Speedtest2.txt similarity index 100% rename from csharp/tests/Speedtest2.txt rename to csharp/DiffMatchPatch.Tests.Performance/Speedtest2.txt diff --git a/csharp/DiffMatchPatch.Tests/DiffMatchPatch.Tests.csproj b/csharp/DiffMatchPatch.Tests/DiffMatchPatch.Tests.csproj new file mode 100644 index 0000000..1835252 --- /dev/null +++ b/csharp/DiffMatchPatch.Tests/DiffMatchPatch.Tests.csproj @@ -0,0 +1,16 @@ + + + netcoreapp2.1 + false + Google.DiffMatchPatch.Tests + + + + + + + + + + + \ No newline at end of file diff --git a/csharp/DiffMatchPatch.Tests/DiffTests.cs b/csharp/DiffMatchPatch.Tests/DiffTests.cs new file mode 100644 index 0000000..9b1a0c4 --- /dev/null +++ b/csharp/DiffMatchPatch.Tests/DiffTests.cs @@ -0,0 +1,1089 @@ +/* + * Diff Match and Patch + * Copyright 2018 The diff-match-patch Authors. + * https://github.com/google/diff-match-patch + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System; +using System.Collections.Generic; +using System.Text; +using Xunit; + +namespace Google.DiffMatchPatch.Tests +{ + public class DiffTests : diff_match_patch + { + [Fact] + public void CommonPrefixTest() + { + // Detect any common suffix. + Assert.Equal(0, diff_commonPrefix("abc", "xyz")); + Assert.Equal(4, diff_commonPrefix("1234abcdef", "1234xyz")); + Assert.Equal(4, diff_commonPrefix("1234", "1234xyz")); + } + + [Fact] + public void CommonSuffixTest() + { + // Detect any common suffix. + Assert.Equal(0, diff_commonSuffix("abc", "xyz")); + Assert.Equal(4, diff_commonSuffix("abcdef1234", "xyz1234")); + Assert.Equal(4, diff_commonSuffix("1234", "xyz1234")); + } + + [Fact] + public void CommonOverlapTest() + { + // Detect any suffix/prefix overlap. + Assert.Equal(0, diff_commonOverlap("", "abcd")); + Assert.Equal(3, diff_commonOverlap("abc", "abcd")); + Assert.Equal(0, diff_commonOverlap("123456", "abcd")); + Assert.Equal(3, diff_commonOverlap("123456xxx", "xxxabcd")); + + // Some overly clever languages (C#) may treat ligatures as equal to their + // component letters. E.g. U+FB01 == 'fi' + Assert.Equal(0, diff_commonOverlap("fi", "\ufb01i")); + } + + [Fact] + public void HalfmatchTest() + { + Diff_Timeout = 1; + Assert.Null(diff_halfMatch("1234567890", "abcdef")); + Assert.Null(diff_halfMatch("12345", "23")); + + Assert.Equal(new[] {"12", "90", "a", "z", "345678"}, diff_halfMatch("1234567890", "a345678z")); + Assert.Equal(new[] {"a", "z", "12", "90", "345678"}, diff_halfMatch("a345678z", "1234567890")); + Assert.Equal(new[] {"abc", "z", "1234", "0", "56789"}, diff_halfMatch("abc56789z", "1234567890")); + Assert.Equal(new[] {"a", "xyz", "1", "7890", "23456"}, diff_halfMatch("a23456xyz", "1234567890")); + Assert.Equal(new[] {"12123", "123121", "a", "z", "1234123451234"}, + diff_halfMatch("121231234123451234123121", "a1234123451234z")); + Assert.Equal(new[] {"", "-=-=-=-=-=", "x", "", "x-=-=-=-=-=-=-="}, + diff_halfMatch("x-=-=-=-=-=-=-=-=-=-=-=-=", "xx-=-=-=-=-=-=-=")); + Assert.Equal(new[] {"-=-=-=-=-=", "", "", "y", "-=-=-=-=-=-=-=y"}, + diff_halfMatch("-=-=-=-=-=-=-=-=-=-=-=-=y", "-=-=-=-=-=-=-=yy")); + + // Optimal diff would be -q+x=H-i+e=lloHe+Hu=llo-Hew+y not -qHillo+x=HelloHe-w+Hulloy + Assert.Equal(new[] {"qHillo", "w", "x", "Hulloy", "HelloHe"}, + diff_halfMatch("qHilloHelloHew", "xHelloHeHulloy")); + + Diff_Timeout = 0; + Assert.Null(diff_halfMatch("qHilloHelloHew", "xHelloHeHulloy")); + } + + [Fact] + public void LinesToCharsTest() + { + // Convert lines down to characters. + var tmpVector = new List {"", "alpha\n", "beta\n"}; + var result = diff_linesToChars("alpha\nbeta\nalpha\n", "beta\nalpha\nbeta\n"); + + Assert.Equal("\u0001\u0002\u0001", (string) result[0]); + Assert.Equal("\u0002\u0001\u0002", (string) result[1]); + Assert.Equal(tmpVector, (List) result[2]); + + tmpVector.Clear(); + tmpVector.Add(""); + tmpVector.Add("alpha\r\n"); + tmpVector.Add("beta\r\n"); + tmpVector.Add("\r\n"); + result = diff_linesToChars("", "alpha\r\nbeta\r\n\r\n\r\n"); + + Assert.Equal("", (string) result[0]); + Assert.Equal("\u0001\u0002\u0003\u0003", (string) result[1]); + Assert.Equal(tmpVector, (List) result[2]); + + tmpVector.Clear(); + tmpVector.Add(""); + tmpVector.Add("a"); + tmpVector.Add("b"); + result = diff_linesToChars("a", "b"); + + Assert.Equal("\u0001", (string) result[0]); + Assert.Equal("\u0002", (string) result[1]); + Assert.Equal(tmpVector, (List) result[2]); + + // More than 256 to reveal any 8-bit limitations. + const int n = 300; + tmpVector.Clear(); + var lineList = new StringBuilder(); + var charList = new StringBuilder(); + for (var i = 1; i < n + 1; i++) + { + tmpVector.Add(i + "\n"); + lineList.Append(i + "\n"); + charList.Append(Convert.ToChar(i)); + } + + Assert.Equal(n, tmpVector.Count); + var lines = lineList.ToString(); + var chars = charList.ToString(); + Assert.Equal(n, chars.Length); + tmpVector.Insert(0, ""); + result = diff_linesToChars(lines, ""); + Assert.Equal(chars, (string) result[0]); + Assert.Equal("", (string) result[1]); + Assert.Equal(tmpVector, (List) result[2]); + } + + [Fact] + public void CharsToLinesTest() + { + // First check that Diff equality works. + Assert.True(new Diff(Operation.EQUAL, "a").Equals(new Diff(Operation.EQUAL, "a"))); + + Assert.Equal(new Diff(Operation.EQUAL, "a"), new Diff(Operation.EQUAL, "a")); + + // Convert chars up to lines. + var diffs = new List + { + new Diff(Operation.EQUAL, "\u0001\u0002\u0001"), + new Diff(Operation.INSERT, "\u0002\u0001\u0002") + }; + var tmpVector = new List {"", "alpha\n", "beta\n"}; + diff_charsToLines(diffs, tmpVector); + Assert.Equal(new List + { + new Diff(Operation.EQUAL, "alpha\nbeta\nalpha\n"), + new Diff(Operation.INSERT, "beta\nalpha\nbeta\n") + }, diffs); + + // More than 256 to reveal any 8-bit limitations. + const int n = 300; + tmpVector.Clear(); + var lineList = new StringBuilder(); + var charList = new StringBuilder(); + for (var i = 1; i < n + 1; i++) + { + tmpVector.Add(i + "\n"); + lineList.Append(i + "\n"); + charList.Append(Convert.ToChar(i)); + } + + Assert.Equal(n, tmpVector.Count); + var lines = lineList.ToString(); + var chars = charList.ToString(); + Assert.Equal(n, chars.Length); + tmpVector.Insert(0, ""); + diffs = new List {new Diff(Operation.DELETE, chars)}; + diff_charsToLines(diffs, tmpVector); + Assert.Equal(new List + {new Diff(Operation.DELETE, lines)}, diffs); + + // More than 65536 to verify any 16-bit limitation. + lineList = new StringBuilder(); + for (var i = 0; i < 66000; i++) + { + lineList.Append(i + "\n"); + } + + chars = lineList.ToString(); + var result = diff_linesToChars(chars, ""); + diffs = new List {new Diff(Operation.INSERT, (string) result[0])}; + diff_charsToLines(diffs, (List) result[2]); + Assert.Equal(chars, diffs[0].text); + } + + [Fact] + public void CleanupMergeTest() + { + // Cleanup a messy diff. + // Null case. + var diffs = new List(); + Assert.Equal(new List(), diffs); + + diffs = new List + { + new Diff(Operation.EQUAL, "a"), + new Diff(Operation.DELETE, "b"), + new Diff(Operation.INSERT, "c") + }; + diff_cleanupMerge(diffs); + Assert.Equal( + new List + { + new Diff(Operation.EQUAL, "a"), + new Diff(Operation.DELETE, "b"), + new Diff(Operation.INSERT, "c") + }, diffs); + + diffs = new List + { + new Diff(Operation.EQUAL, "a"), + new Diff(Operation.EQUAL, "b"), + new Diff(Operation.EQUAL, "c") + }; + diff_cleanupMerge(diffs); + Assert.Equal(new List {new Diff(Operation.EQUAL, "abc")}, diffs); + + diffs = new List + { + new Diff(Operation.DELETE, "a"), + new Diff(Operation.DELETE, "b"), + new Diff(Operation.DELETE, "c") + }; + diff_cleanupMerge(diffs); + Assert.Equal(new List {new Diff(Operation.DELETE, "abc")}, diffs); + + diffs = new List + { + new Diff(Operation.INSERT, "a"), + new Diff(Operation.INSERT, "b"), + new Diff(Operation.INSERT, "c") + }; + diff_cleanupMerge(diffs); + Assert.Equal(new List {new Diff(Operation.INSERT, "abc")}, diffs); + + diffs = new List + { + new Diff(Operation.DELETE, "a"), + new Diff(Operation.INSERT, "b"), + new Diff(Operation.DELETE, "c"), + new Diff(Operation.INSERT, "d"), + new Diff(Operation.EQUAL, "e"), + new Diff(Operation.EQUAL, "f") + }; + diff_cleanupMerge(diffs); + Assert.Equal( + new List + { + new Diff(Operation.DELETE, "ac"), + new Diff(Operation.INSERT, "bd"), + new Diff(Operation.EQUAL, "ef") + }, diffs); + + diffs = new List + { + new Diff(Operation.DELETE, "a"), + new Diff(Operation.INSERT, "abc"), + new Diff(Operation.DELETE, "dc") + }; + diff_cleanupMerge(diffs); + Assert.Equal( + new List + { + new Diff(Operation.EQUAL, "a"), + new Diff(Operation.DELETE, "d"), + new Diff(Operation.INSERT, "b"), + new Diff(Operation.EQUAL, "c") + }, diffs); + + diffs = new List + { + new Diff(Operation.EQUAL, "x"), + new Diff(Operation.DELETE, "a"), + new Diff(Operation.INSERT, "abc"), + new Diff(Operation.DELETE, "dc"), + new Diff(Operation.EQUAL, "y") + }; + diff_cleanupMerge(diffs); + Assert.Equal( + new List + { + new Diff(Operation.EQUAL, "xa"), + new Diff(Operation.DELETE, "d"), + new Diff(Operation.INSERT, "b"), + new Diff(Operation.EQUAL, "cy") + }, diffs); + + diffs = new List + { + new Diff(Operation.EQUAL, "a"), + new Diff(Operation.INSERT, "ba"), + new Diff(Operation.EQUAL, "c") + }; + diff_cleanupMerge(diffs); + Assert.Equal(new List {new Diff(Operation.INSERT, "ab"), new Diff(Operation.EQUAL, "ac")}, diffs); + + diffs = new List + { + new Diff(Operation.EQUAL, "c"), + new Diff(Operation.INSERT, "ab"), + new Diff(Operation.EQUAL, "a") + }; + diff_cleanupMerge(diffs); + Assert.Equal(new List {new Diff(Operation.EQUAL, "ca"), new Diff(Operation.INSERT, "ba")}, diffs); + + diffs = new List + { + new Diff(Operation.EQUAL, "a"), + new Diff(Operation.DELETE, "b"), + new Diff(Operation.EQUAL, "c"), + new Diff(Operation.DELETE, "ac"), + new Diff(Operation.EQUAL, "x") + }; + diff_cleanupMerge(diffs); + Assert.Equal(new List {new Diff(Operation.DELETE, "abc"), new Diff(Operation.EQUAL, "acx")}, diffs); + + diffs = new List + { + new Diff(Operation.EQUAL, "x"), + new Diff(Operation.DELETE, "ca"), + new Diff(Operation.EQUAL, "c"), + new Diff(Operation.DELETE, "b"), + new Diff(Operation.EQUAL, "a") + }; + diff_cleanupMerge(diffs); + Assert.Equal(new List {new Diff(Operation.EQUAL, "xca"), new Diff(Operation.DELETE, "cba")}, diffs); + + diffs = new List + { + new Diff(Operation.DELETE, "b"), + new Diff(Operation.INSERT, "ab"), + new Diff(Operation.EQUAL, "c") + }; + diff_cleanupMerge(diffs); + Assert.Equal(new List {new Diff(Operation.INSERT, "a"), new Diff(Operation.EQUAL, "bc")}, diffs); + + diffs = new List + { + new Diff(Operation.EQUAL, ""), + new Diff(Operation.INSERT, "a"), + new Diff(Operation.EQUAL, "b") + }; + diff_cleanupMerge(diffs); + Assert.Equal(new List {new Diff(Operation.INSERT, "a"), new Diff(Operation.EQUAL, "b")}, diffs); + } + + [Fact] + public void CleanupSemanticLosslessTest() + { + // Slide diffs to match logical boundaries. + var diffs = new List(); + diff_cleanupSemanticLossless(diffs); + Assert.Equal(new List(), diffs); + + diffs = new List + { + new Diff(Operation.EQUAL, "AAA\r\n\r\nBBB"), + new Diff(Operation.INSERT, "\r\nDDD\r\n\r\nBBB"), + new Diff(Operation.EQUAL, "\r\nEEE") + }; + diff_cleanupSemanticLossless(diffs); + Assert.Equal(new List + { + new Diff(Operation.EQUAL, "AAA\r\n\r\n"), + new Diff(Operation.INSERT, "BBB\r\nDDD\r\n\r\n"), + new Diff(Operation.EQUAL, "BBB\r\nEEE") + }, diffs); + + diffs = new List + { + new Diff(Operation.EQUAL, "AAA\r\nBBB"), + new Diff(Operation.INSERT, " DDD\r\nBBB"), + new Diff(Operation.EQUAL, " EEE") + }; + diff_cleanupSemanticLossless(diffs); + Assert.Equal(new List + { + new Diff(Operation.EQUAL, "AAA\r\n"), + new Diff(Operation.INSERT, "BBB DDD\r\n"), + new Diff(Operation.EQUAL, "BBB EEE") + }, diffs); + + diffs = new List + { + new Diff(Operation.EQUAL, "The c"), + new Diff(Operation.INSERT, "ow and the c"), + new Diff(Operation.EQUAL, "at.") + }; + diff_cleanupSemanticLossless(diffs); + Assert.Equal(new List + { + new Diff(Operation.EQUAL, "The "), + new Diff(Operation.INSERT, "cow and the "), + new Diff(Operation.EQUAL, "cat.") + }, diffs); + + diffs = new List + { + new Diff(Operation.EQUAL, "The-c"), + new Diff(Operation.INSERT, "ow-and-the-c"), + new Diff(Operation.EQUAL, "at.") + }; + diff_cleanupSemanticLossless(diffs); + Assert.Equal(new List + { + new Diff(Operation.EQUAL, "The-"), + new Diff(Operation.INSERT, "cow-and-the-"), + new Diff(Operation.EQUAL, "cat.") + }, diffs); + + diffs = new List + { + new Diff(Operation.EQUAL, "a"), + new Diff(Operation.DELETE, "a"), + new Diff(Operation.EQUAL, "ax") + }; + diff_cleanupSemanticLossless(diffs); + Assert.Equal(new List + { + new Diff(Operation.DELETE, "a"), + new Diff(Operation.EQUAL, "aax") + }, diffs); + + diffs = new List + { + new Diff(Operation.EQUAL, "xa"), + new Diff(Operation.DELETE, "a"), + new Diff(Operation.EQUAL, "a") + }; + diff_cleanupSemanticLossless(diffs); + Assert.Equal(new List + { + new Diff(Operation.EQUAL, "xaa"), + new Diff(Operation.DELETE, "a") + }, diffs); + + diffs = new List + { + new Diff(Operation.EQUAL, "The xxx. The "), + new Diff(Operation.INSERT, "zzz. The "), + new Diff(Operation.EQUAL, "yyy.") + }; + diff_cleanupSemanticLossless(diffs); + Assert.Equal(new List + { + new Diff(Operation.EQUAL, "The xxx."), + new Diff(Operation.INSERT, " The zzz."), + new Diff(Operation.EQUAL, " The yyy.") + }, diffs); + } + + [Fact] + public void CleanupSemanticTest() + { + // Cleanup semantically trivial equalities. + // Null case. + var diffs = new List(); + diff_cleanupSemantic(diffs); + Assert.Equal(new List(), diffs); + + diffs = new List + { + new Diff(Operation.DELETE, "ab"), + new Diff(Operation.INSERT, "cd"), + new Diff(Operation.EQUAL, "12"), + new Diff(Operation.DELETE, "e") + }; + diff_cleanupSemantic(diffs); + Assert.Equal(new List + { + new Diff(Operation.DELETE, "ab"), + new Diff(Operation.INSERT, "cd"), + new Diff(Operation.EQUAL, "12"), + new Diff(Operation.DELETE, "e") + }, diffs); + + diffs = new List + { + new Diff(Operation.DELETE, "abc"), + new Diff(Operation.INSERT, "ABC"), + new Diff(Operation.EQUAL, "1234"), + new Diff(Operation.DELETE, "wxyz") + }; + diff_cleanupSemantic(diffs); + Assert.Equal(new List + { + new Diff(Operation.DELETE, "abc"), + new Diff(Operation.INSERT, "ABC"), + new Diff(Operation.EQUAL, "1234"), + new Diff(Operation.DELETE, "wxyz") + }, diffs); + + diffs = new List + { + new Diff(Operation.DELETE, "a"), + new Diff(Operation.EQUAL, "b"), + new Diff(Operation.DELETE, "c") + }; + diff_cleanupSemantic(diffs); + Assert.Equal(new List + { + new Diff(Operation.DELETE, "abc"), + new Diff(Operation.INSERT, "b") + }, diffs); + + diffs = new List + { + new Diff(Operation.DELETE, "ab"), + new Diff(Operation.EQUAL, "cd"), + new Diff(Operation.DELETE, "e"), + new Diff(Operation.EQUAL, "f"), + new Diff(Operation.INSERT, "g") + }; + diff_cleanupSemantic(diffs); + Assert.Equal(new List + { + new Diff(Operation.DELETE, "abcdef"), + new Diff(Operation.INSERT, "cdfg") + }, diffs); + + diffs = new List + { + new Diff(Operation.INSERT, "1"), + new Diff(Operation.EQUAL, "A"), + new Diff(Operation.DELETE, "B"), + new Diff(Operation.INSERT, "2"), + new Diff(Operation.EQUAL, "_"), + new Diff(Operation.INSERT, "1"), + new Diff(Operation.EQUAL, "A"), + new Diff(Operation.DELETE, "B"), + new Diff(Operation.INSERT, "2") + }; + diff_cleanupSemantic(diffs); + Assert.Equal(new List + { + new Diff(Operation.DELETE, "AB_AB"), + new Diff(Operation.INSERT, "1A2_1A2") + }, diffs); + + diffs = new List + { + new Diff(Operation.EQUAL, "The c"), + new Diff(Operation.DELETE, "ow and the c"), + new Diff(Operation.EQUAL, "at.") + }; + diff_cleanupSemantic(diffs); + Assert.Equal(new List + { + new Diff(Operation.EQUAL, "The "), + new Diff(Operation.DELETE, "cow and the "), + new Diff(Operation.EQUAL, "cat.") + }, diffs); + + diffs = new List + { + new Diff(Operation.DELETE, "abcxx"), + new Diff(Operation.INSERT, "xxdef") + }; + diff_cleanupSemantic(diffs); + Assert.Equal(new List + { + new Diff(Operation.DELETE, "abcxx"), + new Diff(Operation.INSERT, "xxdef") + }, diffs); + + diffs = new List + { + new Diff(Operation.DELETE, "abcxxx"), + new Diff(Operation.INSERT, "xxxdef") + }; + diff_cleanupSemantic(diffs); + Assert.Equal(new List + { + new Diff(Operation.DELETE, "abc"), + new Diff(Operation.EQUAL, "xxx"), + new Diff(Operation.INSERT, "def") + }, diffs); + + diffs = new List + { + new Diff(Operation.DELETE, "xxxabc"), + new Diff(Operation.INSERT, "defxxx") + }; + diff_cleanupSemantic(diffs); + Assert.Equal(new List + { + new Diff(Operation.INSERT, "def"), + new Diff(Operation.EQUAL, "xxx"), + new Diff(Operation.DELETE, "abc") + }, diffs); + + diffs = new List + { + new Diff(Operation.DELETE, "abcd1212"), + new Diff(Operation.INSERT, "1212efghi"), + new Diff(Operation.EQUAL, "----"), + new Diff(Operation.DELETE, "A3"), + new Diff(Operation.INSERT, "3BC") + }; + diff_cleanupSemantic(diffs); + Assert.Equal(new List + { + new Diff(Operation.DELETE, "abcd"), + new Diff(Operation.EQUAL, "1212"), + new Diff(Operation.INSERT, "efghi"), + new Diff(Operation.EQUAL, "----"), + new Diff(Operation.DELETE, "A"), + new Diff(Operation.EQUAL, "3"), + new Diff(Operation.INSERT, "BC") + }, diffs); + } + + [Fact] + public void CleanupEfficiencyTest() + { + // Cleanup operationally trivial equalities. + Diff_EditCost = 4; + var diffs = new List(); + diff_cleanupEfficiency(diffs); + Assert.Equal(new List(), diffs); + + diffs = new List + { + new Diff(Operation.DELETE, "ab"), + new Diff(Operation.INSERT, "12"), + new Diff(Operation.EQUAL, "wxyz"), + new Diff(Operation.DELETE, "cd"), + new Diff(Operation.INSERT, "34") + }; + diff_cleanupEfficiency(diffs); + Assert.Equal(new List + { + new Diff(Operation.DELETE, "ab"), + new Diff(Operation.INSERT, "12"), + new Diff(Operation.EQUAL, "wxyz"), + new Diff(Operation.DELETE, "cd"), + new Diff(Operation.INSERT, "34") + }, diffs); + + diffs = new List + { + new Diff(Operation.DELETE, "ab"), + new Diff(Operation.INSERT, "12"), + new Diff(Operation.EQUAL, "xyz"), + new Diff(Operation.DELETE, "cd"), + new Diff(Operation.INSERT, "34") + }; + diff_cleanupEfficiency(diffs); + Assert.Equal(new List + { + new Diff(Operation.DELETE, "abxyzcd"), + new Diff(Operation.INSERT, "12xyz34") + }, diffs); + + diffs = new List + { + new Diff(Operation.INSERT, "12"), + new Diff(Operation.EQUAL, "x"), + new Diff(Operation.DELETE, "cd"), + new Diff(Operation.INSERT, "34") + }; + diff_cleanupEfficiency(diffs); + Assert.Equal(new List + { + new Diff(Operation.DELETE, "xcd"), + new Diff(Operation.INSERT, "12x34") + }, diffs); + + diffs = new List + { + new Diff(Operation.DELETE, "ab"), + new Diff(Operation.INSERT, "12"), + new Diff(Operation.EQUAL, "xy"), + new Diff(Operation.INSERT, "34"), + new Diff(Operation.EQUAL, "z"), + new Diff(Operation.DELETE, "cd"), + new Diff(Operation.INSERT, "56") + }; + diff_cleanupEfficiency(diffs); + Assert.Equal(new List + { + new Diff(Operation.DELETE, "abxyzcd"), + new Diff(Operation.INSERT, "12xy34z56") + }, diffs); + + Diff_EditCost = 5; + diffs = new List + { + new Diff(Operation.DELETE, "ab"), + new Diff(Operation.INSERT, "12"), + new Diff(Operation.EQUAL, "wxyz"), + new Diff(Operation.DELETE, "cd"), + new Diff(Operation.INSERT, "34") + }; + diff_cleanupEfficiency(diffs); + Assert.Equal(new List + { + new Diff(Operation.DELETE, "abwxyzcd"), + new Diff(Operation.INSERT, "12wxyz34") + }, diffs); + Diff_EditCost = 4; + } + + [Fact] + public void PrettyHtmlTest() + { + // Pretty print. + var diffs = new List + { + new Diff(Operation.EQUAL, "a\n"), + new Diff(Operation.DELETE, "b"), + new Diff(Operation.INSERT, "c&d") + }; + Assert.Equal( + "
<B>b</B>c&d", + diff_prettyHtml(diffs)); + } + + [Fact] + public void TextTest() + { + // Compute the source and destination texts. + var diffs = new List + { + 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, " lazy") + }; + Assert.Equal("jumps over the lazy", diff_text1(diffs)); + + Assert.Equal("jumped over a lazy", diff_text2(diffs)); + } + + [Fact] + public void DeltaTest() + { + // Convert a diff into delta string. + var diffs = new List + { + 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, " lazy"), + new Diff(Operation.INSERT, "old dog") + }; + var text1 = diff_text1(diffs); + Assert.Equal("jumps over the lazy", text1); + + var delta = diff_toDelta(diffs); + Assert.Equal("=4\t-1\t+ed\t=6\t-3\t+a\t=5\t+old dog", delta); + + // Convert delta string into a diff. + Assert.Equal(diffs, diff_fromDelta(text1, delta)); + + // Generates error (19 < 20). + Assert.Throws(() => diff_fromDelta(text1 + "x", delta)); + + // Generates error (19 > 18). + Assert.Throws(() => diff_fromDelta(text1.Substring(1), delta)); + + // Test deltas with special characters. + const char zero = (char) 0; + const char one = (char) 1; + const char two = (char) 2; + diffs = new List + { + new Diff(Operation.EQUAL, "\u0680 " + zero + " \t %"), + new Diff(Operation.DELETE, "\u0681 " + one + " \n ^"), + new Diff(Operation.INSERT, "\u0682 " + two + " \\ |") + }; + text1 = diff_text1(diffs); + Assert.Equal("\u0680 " + zero + " \t %\u0681 " + one + " \n ^", text1); + + delta = diff_toDelta(diffs); + // Lowercase, due to UrlEncode uses lower. + Assert.Equal("=7\t-7\t+%da%82 %02 %5c %7c", delta); + + Assert.Equal(diffs, diff_fromDelta(text1, delta)); + + // Verify pool of unchanged characters. + diffs = new List + { + new Diff(Operation.INSERT, "A-Z a-z 0-9 - _ . ! ~ * ' ( ) ; / ? : @ & = + $ , # ") + }; + var text2 = diff_text2(diffs); + Assert.Equal("A-Z a-z 0-9 - _ . ! ~ * \' ( ) ; / ? : @ & = + $ , # ", text2); + + delta = diff_toDelta(diffs); + Assert.Equal("+A-Z a-z 0-9 - _ . ! ~ * \' ( ) ; / ? : @ & = + $ , # ", delta); + + // Convert delta string into a diff. + Assert.Equal(diffs, diff_fromDelta("", delta)); + + // 160 kb string. + var a = "abcdefghij"; + for (var i = 0; i < 14; i++) + { + a += a; + } + + diffs = new List {new Diff(Operation.INSERT, a)}; + delta = diff_toDelta(diffs); + Assert.Equal("+" + a, delta); + + // Convert delta string into a diff. + Assert.Equal(diffs, diff_fromDelta("", delta)); + } + + [Fact] + public void InvalidDeltaTest() + { + } + + [Fact] + public void X_IndexTest() + { + // Translate a location in text1 to text2. + var diffs = new List + { + new Diff(Operation.DELETE, "a"), + new Diff(Operation.INSERT, "1234"), + new Diff(Operation.EQUAL, "xyz") + }; + Assert.Equal(5, diff_xIndex(diffs, 2)); + + diffs = new List + { + new Diff(Operation.EQUAL, "a"), + new Diff(Operation.DELETE, "1234"), + new Diff(Operation.EQUAL, "xyz") + }; + Assert.Equal(1, diff_xIndex(diffs, 3)); + } + + [Fact] + public void LevenshteinTest() + { + var diffs = new List + { + new Diff(Operation.DELETE, "abc"), + new Diff(Operation.INSERT, "1234"), + new Diff(Operation.EQUAL, "xyz") + }; + Assert.Equal(4, diff_levenshtein(diffs)); + + diffs = new List + { + new Diff(Operation.EQUAL, "xyz"), + new Diff(Operation.DELETE, "abc"), + new Diff(Operation.INSERT, "1234") + }; + Assert.Equal(4, diff_levenshtein(diffs)); + + diffs = new List + { + new Diff(Operation.DELETE, "abc"), + new Diff(Operation.EQUAL, "xyz"), + new Diff(Operation.INSERT, "1234") + }; + Assert.Equal(7, diff_levenshtein(diffs)); + } + + [Fact] + public void BisectTest() + { + // Normal. + const string a = "cat"; + const string b = "map"; + // Since the resulting diff hasn't been normalized, it would be ok if + // the insertion and deletion pairs are swapped. + // If the order changes, tweak this test as required. + var diffs = new List + { + new Diff(Operation.DELETE, "c"), + new Diff(Operation.INSERT, "m"), + new Diff(Operation.EQUAL, "a"), + new Diff(Operation.DELETE, "t"), + new Diff(Operation.INSERT, "p") + }; + Assert.Equal(diffs, diff_bisect(a, b, DateTime.MaxValue)); + + // Timeout. + diffs = new List {new Diff(Operation.DELETE, "cat"), new Diff(Operation.INSERT, "map")}; + Assert.Equal(diffs, diff_bisect(a, b, DateTime.MinValue)); + } + + [Fact] + public void MainTest() + { + // Perform a trivial diff. + var diffs = new List(); + Assert.Equal(diffs, diff_main("", "", false)); + + diffs = new List {new Diff(Operation.EQUAL, "abc")}; + Assert.Equal(diffs, diff_main("abc", "abc", false)); + + diffs = new List + { + new Diff(Operation.EQUAL, "ab"), + new Diff(Operation.INSERT, "123"), + new Diff(Operation.EQUAL, "c") + }; + Assert.Equal(diffs, diff_main("abc", "ab123c", false)); + + diffs = new List + { + new Diff(Operation.EQUAL, "a"), + new Diff(Operation.DELETE, "123"), + new Diff(Operation.EQUAL, "bc") + }; + Assert.Equal(diffs, diff_main("a123bc", "abc", false)); + + diffs = new List + { + new Diff(Operation.EQUAL, "a"), + new Diff(Operation.INSERT, "123"), + new Diff(Operation.EQUAL, "b"), + new Diff(Operation.INSERT, "456"), + new Diff(Operation.EQUAL, "c") + }; + Assert.Equal(diffs, diff_main("abc", "a123b456c", false)); + + diffs = new List + { + new Diff(Operation.EQUAL, "a"), + new Diff(Operation.DELETE, "123"), + new Diff(Operation.EQUAL, "b"), + new Diff(Operation.DELETE, "456"), + new Diff(Operation.EQUAL, "c") + }; + Assert.Equal(diffs, diff_main("a123b456c", "abc", false)); + + // Perform a real diff. + // Switch off the timeout. + Diff_Timeout = 0; + diffs = new List {new Diff(Operation.DELETE, "a"), new Diff(Operation.INSERT, "b")}; + Assert.Equal(diffs, diff_main("a", "b", false)); + + diffs = new List + { + new Diff(Operation.DELETE, "Apple"), + new Diff(Operation.INSERT, "Banana"), + new Diff(Operation.EQUAL, "s are a"), + new Diff(Operation.INSERT, "lso"), + new Diff(Operation.EQUAL, " fruit.") + }; + Assert.Equal(diffs, diff_main("Apples are a fruit.", "Bananas are also fruit.", false)); + + diffs = new List + { + new Diff(Operation.DELETE, "a"), + new Diff(Operation.INSERT, "\u0680"), + new Diff(Operation.EQUAL, "x"), + new Diff(Operation.DELETE, "\t"), + new Diff(Operation.INSERT, new string(new[] {(char) 0})) + }; + Assert.Equal(diffs, diff_main("ax\t", "\u0680x" + (char) 0, false)); + + diffs = new List + { + new Diff(Operation.DELETE, "1"), + new Diff(Operation.EQUAL, "a"), + new Diff(Operation.DELETE, "y"), + new Diff(Operation.EQUAL, "b"), + new Diff(Operation.DELETE, "2"), + new Diff(Operation.INSERT, "xab") + }; + Assert.Equal(diffs, diff_main("1ayb2", "abxab", false)); + + diffs = new List + { + new Diff(Operation.INSERT, "xaxcx"), + new Diff(Operation.EQUAL, "abc"), + new Diff(Operation.DELETE, "y") + }; + Assert.Equal(diffs, diff_main("abcy", "xaxcxabc", false)); + + diffs = new List + { + new Diff(Operation.DELETE, "ABCD"), + new Diff(Operation.EQUAL, "a"), + new Diff(Operation.DELETE, "="), + new Diff(Operation.INSERT, "-"), + new Diff(Operation.EQUAL, "bcd"), + new Diff(Operation.DELETE, "="), + new Diff(Operation.INSERT, "-"), + new Diff(Operation.EQUAL, "efghijklmnopqrs"), + new Diff(Operation.DELETE, "EFGHIJKLMNOefg") + }; + Assert.Equal(diffs, diff_main("ABCDa=bcd=efghijklmnopqrsEFGHIJKLMNOefg", "a-bcd-efghijklmnopqrs", false)); + + diffs = new List + { + new Diff(Operation.INSERT, " "), + new Diff(Operation.EQUAL, "a"), + new Diff(Operation.INSERT, "nd"), + new Diff(Operation.EQUAL, " [[Pennsylvania]]"), + new Diff(Operation.DELETE, " and [[New") + }; + Assert.Equal(diffs, diff_main("a [[Pennsylvania]] and [[New", " and [[Pennsylvania]]", false)); + + Diff_Timeout = 0.1f; // 100ms + var a = + "`Twas brillig, and the slithy toves\nDid gyre and gimble in the wabe:\nAll mimsy were the borogoves,\nAnd the mome raths outgrabe.\n"; + var b = + "I am the very model of a modern major general,\nI've information vegetable, animal, and mineral,\nI know the kings of England, and I quote the fights historical,\nFrom Marathon to Waterloo, in order categorical.\n"; + // Increase the text lengths by 1024 times to ensure a timeout. + for (var i = 0; i < 10; i++) + { + a += a; + b += b; + } + + var startTime = DateTime.Now; + diff_main(a, b); + var endTime = DateTime.Now; + // Test that we took at least the timeout period. + Assert.True(new TimeSpan(((long) (Diff_Timeout * 1000)) * 10000) <= endTime - startTime); + // Test that we didn't take forever (be forgiving). + // Theoretically this test could fail very occasionally if the + // OS task swaps or locks up for a second at the wrong moment. + Assert.True(new TimeSpan(((long) (Diff_Timeout * 1000)) * 10000 * 2) > endTime - startTime); + Diff_Timeout = 0; + + // Test the linemode speedup. + // Must be long to pass the 100 char cutoff. + a = + "1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n"; + b = + "abcdefghij\nabcdefghij\nabcdefghij\nabcdefghij\nabcdefghij\nabcdefghij\nabcdefghij\nabcdefghij\nabcdefghij\nabcdefghij\nabcdefghij\nabcdefghij\nabcdefghij\n"; + Assert.Equal(diff_main(a, b, true), diff_main(a, b, false)); + + a = + "1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890"; + b = + "abcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghij"; + Assert.Equal(diff_main(a, b, true), diff_main(a, b, false)); + + a = + "1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n"; + b = + "abcdefghij\n1234567890\n1234567890\n1234567890\nabcdefghij\n1234567890\n1234567890\n1234567890\nabcdefghij\n1234567890\n1234567890\n1234567890\nabcdefghij\n"; + var textsLinemode = RebuildTexts(diff_main(a, b, true)); + var textsTextmode = RebuildTexts(diff_main(a, b, false)); + Assert.Equal(textsTextmode, textsLinemode); + + // Test null inputs -- not needed because nulls can't be passed in C#. + } + + private static string[] RebuildTexts(IEnumerable diffs) + { + string[] text = {"", ""}; + foreach (var myDiff in diffs) + { + if (myDiff.operation != Operation.INSERT) + { + text[0] += myDiff.text; + } + + if (myDiff.operation != Operation.DELETE) + { + text[1] += myDiff.text; + } + } + + return text; + } + } +} diff --git a/csharp/DiffMatchPatch.Tests/MatchTests.cs b/csharp/DiffMatchPatch.Tests/MatchTests.cs new file mode 100644 index 0000000..06860ce --- /dev/null +++ b/csharp/DiffMatchPatch.Tests/MatchTests.cs @@ -0,0 +1,110 @@ +/* + * Diff Match and Patch + * Copyright 2018 The diff-match-patch Authors. + * https://github.com/google/diff-match-patch + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Collections.Generic; +using Xunit; + +namespace Google.DiffMatchPatch.Tests +{ + public class MatchTests : diff_match_patch + { + [Fact] + public void AlphabetTest() + { + // Initialise the bitmasks for Bitap. + var bitmask = new Dictionary {{'a', 4}, {'b', 2}, {'c', 1}}; + Assert.Equal(bitmask, match_alphabet("abc")); + + bitmask.Clear(); + bitmask.Add('a', 37); + bitmask.Add('b', 18); + bitmask.Add('c', 8); + Assert.Equal(bitmask, match_alphabet("abcaba")); + } + + [Fact] + public void BitapTest() + { + // Bitap algorithm. + Match_Distance = 100; + Match_Threshold = 0.5f; + Assert.Equal(5, match_bitap("abcdefghijk", "fgh", 5)); + + Assert.Equal(5, match_bitap("abcdefghijk", "fgh", 0)); + + Assert.Equal(4, match_bitap("abcdefghijk", "efxhi", 0)); + + Assert.Equal(2, match_bitap("abcdefghijk", "cdefxyhijk", 5)); + + Assert.Equal(-1, match_bitap("abcdefghijk", "bxy", 1)); + + Assert.Equal(2, match_bitap("123456789xx0", "3456789x0", 2)); + + Assert.Equal(0, match_bitap("abcdef", "xxabc", 4)); + + Assert.Equal(3, match_bitap("abcdef", "defyy", 4)); + + Assert.Equal(0, match_bitap("abcdef", "xabcdefy", 0)); + + Match_Threshold = 0.4f; + Assert.Equal(4, match_bitap("abcdefghijk", "efxyhi", 1)); + + Match_Threshold = 0.3f; + Assert.Equal(-1, match_bitap("abcdefghijk", "efxyhi", 1)); + + Match_Threshold = 0.0f; + Assert.Equal(1, match_bitap("abcdefghijk", "bcdef", 1)); + + Match_Threshold = 0.5f; + Assert.Equal(0, match_bitap("abcdexyzabcde", "abccde", 3)); + + Assert.Equal(8, match_bitap("abcdexyzabcde", "abccde", 5)); + + Match_Distance = 10; // Strict location. + Assert.Equal(-1, match_bitap("abcdefghijklmnopqrstuvwxyz", "abcdefg", 24)); + + Assert.Equal(0, match_bitap("abcdefghijklmnopqrstuvwxyz", "abcdxxefg", 1)); + + Match_Distance = 1000; // Loose location. + Assert.Equal(0, match_bitap("abcdefghijklmnopqrstuvwxyz", "abcdefg", 24)); + } + + [Fact] + public void MainTest() + { + // Full match. + Assert.Equal(0, match_main("abcdef", "abcdef", 1000)); + + Assert.Equal(-1, match_main("", "abcdef", 1)); + + Assert.Equal(3, match_main("abcdef", "", 3)); + + Assert.Equal(3, match_main("abcdef", "de", 3)); + + Assert.Equal(3, match_main("abcdef", "defy", 4)); + + Assert.Equal(0, match_main("abcdef", "abcdefy", 0)); + + Match_Threshold = 0.7f; + Assert.Equal(4, match_main("I am the very model of a modern major general.", " that berry ", 5)); + Match_Threshold = 0.5f; + + // Test null inputs -- not needed because nulls can't be passed in C#. + } + } +} diff --git a/csharp/DiffMatchPatch.Tests/PatchTests.cs b/csharp/DiffMatchPatch.Tests/PatchTests.cs new file mode 100644 index 0000000..1f89700 --- /dev/null +++ b/csharp/DiffMatchPatch.Tests/PatchTests.cs @@ -0,0 +1,311 @@ +/* + * Diff Match and Patch + * Copyright 2018 The diff-match-patch Authors. + * https://github.com/google/diff-match-patch + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System; +using System.Collections.Generic; +using Xunit; + +namespace Google.DiffMatchPatch.Tests +{ + public class PatchTests : diff_match_patch + { + [Fact] + public void PatchObjTest() + { + // Patch Object. + var p = new Patch + { + start1 = 20, + start2 = 21, + length1 = 18, + length2 = 17, + diffs = new List + { + 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.Equal(strp, p.ToString()); + } + + [Fact] + public void FromTextTest() + { + Assert.True(patch_fromText("").Count == 0); + + const string strp = "@@ -21,18 +22,17 @@\n jump\n-s\n+ed\n over \n-the\n+a\n %0alaz\n"; + + Assert.Equal(strp, patch_fromText(strp)[0].ToString()); + Assert.Equal("@@ -1 +1 @@\n-a\n+b\n", patch_fromText("@@ -1 +1 @@\n-a\n+b\n")[0].ToString()); + Assert.Equal("@@ -1,3 +0,0 @@\n-abc\n", patch_fromText("@@ -1,3 +0,0 @@\n-abc\n")[0].ToString()); + Assert.Equal("@@ -0,0 +1,3 @@\n+abc\n", patch_fromText("@@ -0,0 +1,3 @@\n+abc\n")[0].ToString()); + + Assert.Throws(() => patch_fromText("Bad\nPatch\n")); + } + + [Fact] + public void ToTextTest() + { + var strp = "@@ -21,18 +22,17 @@\n jump\n-s\n+ed\n over \n-the\n+a\n laz\n"; + var patches = patch_fromText(strp); + var result = patch_toText(patches); + Assert.Equal(strp, result); + + strp = "@@ -1,9 +1,9 @@\n-f\n+F\n oo+fooba\n@@ -7,9 +7,9 @@\n obar\n-,\n+.\n tes\n"; + patches = patch_fromText(strp); + result = patch_toText(patches); + Assert.Equal(strp, result); + } + + [Fact] + public void AddContextTest() + { + Patch_Margin = 4; + var p = patch_fromText("@@ -21,4 +21,10 @@\n-jump\n+somersault\n")[0]; + patch_addContext(p, "The quick brown fox jumps over the lazy dog."); + Assert.Equal("@@ -17,12 +17,18 @@\n fox \n-jump\n+somersault\n s ov\n", p.ToString()); + + p = patch_fromText("@@ -21,4 +21,10 @@\n-jump\n+somersault\n")[0]; + patch_addContext(p, "The quick brown fox jumps."); + Assert.Equal("@@ -17,10 +17,16 @@\n fox \n-jump\n+somersault\n s.\n", p.ToString()); + + p = patch_fromText("@@ -3 +3,2 @@\n-e\n+at\n")[0]; + patch_addContext(p, "The quick brown fox jumps."); + Assert.Equal("@@ -1,7 +1,8 @@\n Th\n-e\n+at\n qui\n", p.ToString()); + + p = patch_fromText("@@ -3 +3,2 @@\n-e\n+at\n")[0]; + patch_addContext(p, "The quick brown fox jumps. The quick brown fox crashes."); + Assert.Equal("@@ -1,27 +1,28 @@\n Th\n-e\n+at\n quick brown fox jumps. \n", p.ToString()); + } + + [Fact] + public void MakeTest() + { + var patches = patch_make("", ""); + Assert.Equal("", patch_toText(patches)); + + var text1 = "The quick brown fox jumps over the lazy dog."; + var text2 = "That quick brown fox jumped over a lazy dog."; + var expectedPatch = + "@@ -1,8 +1,7 @@\n Th\n-at\n+e\n qui\n@@ -21,17 +21,18 @@\n jump\n-ed\n+s\n over \n-a\n+the\n laz\n"; + // The second patch must be "-21,17 +21,18", not "-22,17 +21,18" due to rolling context. + patches = patch_make(text2, text1); + Assert.Equal(expectedPatch, patch_toText(patches)); + + expectedPatch = + "@@ -1,11 +1,12 @@\n Th\n-e\n+at\n quick b\n@@ -22,18 +22,17 @@\n jump\n-s\n+ed\n over \n-the\n+a\n laz\n"; + patches = patch_make(text1, text2); + Assert.Equal(expectedPatch, patch_toText(patches)); + + var diffs = diff_main(text1, text2, false); + patches = patch_make(diffs); + Assert.Equal(expectedPatch, patch_toText(patches)); + + patches = patch_make(text1, diffs); + Assert.Equal(expectedPatch, patch_toText(patches)); + + patches = patch_make(text1, text2, diffs); + Assert.Equal(expectedPatch, patch_toText(patches)); + + patches = patch_make("`1234567890-=[]\\;',./", "~!@#$%^&*()_+{}|:\"<>?"); + Assert.Equal("@@ -1,21 +1,21 @@\n-%601234567890-=%5b%5d%5c;',./\n+~!@#$%25%5e&*()_+%7b%7d%7c:%22%3c%3e?\n", + patch_toText(patches)); + + diffs = new List + { + new Diff(Operation.DELETE, "`1234567890-=[]\\;',./"), + new Diff(Operation.INSERT, "~!@#$%^&*()_+{}|:\"<>?") + }; + Assert.Equal(diffs, + patch_fromText( + "@@ -1,21 +1,21 @@\n-%601234567890-=%5B%5D%5C;',./\n+~!@#$%25%5E&*()_+%7B%7D%7C:%22%3C%3E?\n")[ + 0] + .diffs); + + text1 = ""; + for (var x = 0; x < 100; x++) + { + text1 += "abcdef"; + } + + text2 = text1 + "123"; + expectedPatch = "@@ -573,28 +573,31 @@\n cdefabcdefabcdefabcdefabcdef\n+123\n"; + patches = patch_make(text1, text2); + Assert.Equal(expectedPatch, patch_toText(patches)); + + // Test null inputs -- not needed because nulls can't be passed in C#. + } + + [Fact] + public void SplitMaxTest() + { + // Assumes that Match_MaxBits is 32. + + var patches = patch_make("abcdefghijklmnopqrstuvwxyz01234567890", + "XabXcdXefXghXijXklXmnXopXqrXstXuvXwxXyzX01X23X45X67X89X0"); + patch_splitMax(patches); + Assert.Equal( + "@@ -1,32 +1,46 @@\n+X\n ab\n+X\n cd\n+X\n ef\n+X\n gh\n+X\n ij\n+X\n kl\n+X\n mn\n+X\n op\n+X\n qr\n+X\n st\n+X\n uv\n+X\n wx\n+X\n yz\n+X\n 012345\n@@ -25,13 +39,18 @@\n zX01\n+X\n 23\n+X\n 45\n+X\n 67\n+X\n 89\n+X\n 0\n", + patch_toText(patches)); + + patches = patch_make("abcdef1234567890123456789012345678901234567890123456789012345678901234567890uvwxyz", + "abcdefuvwxyz"); + var oldToText = patch_toText(patches); + patch_splitMax(patches); + Assert.Equal(oldToText, patch_toText(patches)); + + patches = patch_make("1234567890123456789012345678901234567890123456789012345678901234567890", "abc"); + patch_splitMax(patches); + Assert.Equal( + "@@ -1,32 +1,4 @@\n-1234567890123456789012345678\n 9012\n@@ -29,32 +1,4 @@\n-9012345678901234567890123456\n 7890\n@@ -57,14 +1,3 @@\n-78901234567890\n+abc\n", + patch_toText(patches)); + + patches = patch_make("abcdefghij , h : 0 , t : 1 abcdefghij , h : 0 , t : 1 abcdefghij , h : 0 , t : 1", + "abcdefghij , h : 1 , t : 1 abcdefghij , h : 1 , t : 1 abcdefghij , h : 0 , t : 1"); + patch_splitMax(patches); + Assert.Equal( + "@@ -2,32 +2,32 @@\n bcdefghij , h : \n-0\n+1\n , t : 1 abcdef\n@@ -29,32 +29,32 @@\n bcdefghij , h : \n-0\n+1\n , t : 1 abcdef\n", + patch_toText(patches)); + } + + [Fact] + public void AddPaddingTest() + { + var patches = patch_make("", "test"); + Assert.Equal("@@ -0,0 +1,4 @@\n+test\n", + patch_toText(patches)); + patch_addPadding(patches); + Assert.Equal("@@ -1,8 +1,12 @@\n %01%02%03%04\n+test\n %01%02%03%04\n", + patch_toText(patches)); + + patches = patch_make("XY", "XtestY"); + Assert.Equal("@@ -1,2 +1,6 @@\n X\n+test\n Y\n", + patch_toText(patches)); + patch_addPadding(patches); + Assert.Equal("@@ -2,8 +2,12 @@\n %02%03%04X\n+test\n Y%01%02%03\n", + patch_toText(patches)); + + patches = patch_make("XXXXYYYY", "XXXXtestYYYY"); + Assert.Equal("@@ -1,8 +1,12 @@\n XXXX\n+test\n YYYY\n", + patch_toText(patches)); + patch_addPadding(patches); + Assert.Equal("@@ -5,8 +5,12 @@\n XXXX\n+test\n YYYY\n", + patch_toText(patches)); + } + + [Fact] + public void ApplyTest() + { + Match_Distance = 1000; + Match_Threshold = 0.5f; + Patch_DeleteThreshold = 0.5f; + var patches = patch_make("", ""); + var results = patch_apply(patches, "Hello world."); + var boolArray = (bool[]) results[1]; + var resultStr = results[0] + "\t" + boolArray.Length; + Assert.Equal("Hello world.\t0", resultStr); + + patches = patch_make("The quick brown fox jumps over the lazy dog.", + "That quick brown fox jumped over a lazy dog."); + results = patch_apply(patches, "The quick brown fox jumps over the lazy dog."); + boolArray = (bool[]) results[1]; + resultStr = results[0] + "\t" + boolArray[0] + "\t" + boolArray[1]; + Assert.Equal("That quick brown fox jumped over a lazy dog.\tTrue\tTrue", resultStr); + + results = patch_apply(patches, "The quick red rabbit jumps over the tired tiger."); + boolArray = (bool[]) results[1]; + resultStr = results[0] + "\t" + boolArray[0] + "\t" + boolArray[1]; + Assert.Equal("That quick red rabbit jumped over a tired tiger.\tTrue\tTrue", resultStr); + + results = patch_apply(patches, "I am the very model of a modern major general."); + boolArray = (bool[]) results[1]; + resultStr = results[0] + "\t" + boolArray[0] + "\t" + boolArray[1]; + Assert.Equal("I am the very model of a modern major general.\tFalse\tFalse", resultStr); + + patches = patch_make("x1234567890123456789012345678901234567890123456789012345678901234567890y", "xabcy"); + results = patch_apply(patches, + "x123456789012345678901234567890-----++++++++++-----123456789012345678901234567890y"); + boolArray = (bool[]) results[1]; + resultStr = results[0] + "\t" + boolArray[0] + "\t" + boolArray[1]; + Assert.Equal("xabcy\tTrue\tTrue", resultStr); + + patches = patch_make("x1234567890123456789012345678901234567890123456789012345678901234567890y", "xabcy"); + results = patch_apply(patches, + "x12345678901234567890---------------++++++++++---------------12345678901234567890y"); + boolArray = (bool[]) results[1]; + resultStr = results[0] + "\t" + boolArray[0] + "\t" + boolArray[1]; + Assert.Equal( + "xabc12345678901234567890---------------++++++++++---------------12345678901234567890y\tFalse\tTrue", + resultStr); + + Patch_DeleteThreshold = 0.6f; + patches = patch_make("x1234567890123456789012345678901234567890123456789012345678901234567890y", "xabcy"); + results = patch_apply(patches, + "x12345678901234567890---------------++++++++++---------------12345678901234567890y"); + boolArray = (bool[]) results[1]; + resultStr = results[0] + "\t" + boolArray[0] + "\t" + boolArray[1]; + Assert.Equal("xabcy\tTrue\tTrue", resultStr); + Patch_DeleteThreshold = 0.5f; + + Match_Threshold = 0.0f; + Match_Distance = 0; + patches = patch_make("abcdefghijklmnopqrstuvwxyz--------------------1234567890", + "abcXXXXXXXXXXdefghijklmnopqrstuvwxyz--------------------1234567YYYYYYYYYY890"); + results = patch_apply(patches, "ABCDEFGHIJKLMNOPQRSTUVWXYZ--------------------1234567890"); + boolArray = (bool[]) results[1]; + resultStr = results[0] + "\t" + boolArray[0] + "\t" + boolArray[1]; + Assert.Equal("ABCDEFGHIJKLMNOPQRSTUVWXYZ--------------------1234567YYYYYYYYYY890\tFalse\tTrue", resultStr); + Match_Threshold = 0.5f; + Match_Distance = 1000; + + patches = patch_make("", "test"); + var patchStr = patch_toText(patches); + patch_apply(patches, ""); + Assert.Equal(patchStr, patch_toText(patches)); + + patches = patch_make("The quick brown fox jumps over the lazy dog.", "Woof"); + patchStr = patch_toText(patches); + patch_apply(patches, "The quick brown fox jumps over the lazy dog."); + Assert.Equal(patchStr, patch_toText(patches)); + + patches = patch_make("", "test"); + results = patch_apply(patches, ""); + boolArray = (bool[]) results[1]; + resultStr = results[0] + "\t" + boolArray[0]; + Assert.Equal("test\tTrue", resultStr); + + patches = patch_make("XY", "XtestY"); + results = patch_apply(patches, "XY"); + boolArray = (bool[]) results[1]; + resultStr = results[0] + "\t" + boolArray[0]; + Assert.Equal("XtestY\tTrue", resultStr); + + patches = patch_make("y", "y123"); + results = patch_apply(patches, "x"); + boolArray = (bool[]) results[1]; + resultStr = results[0] + "\t" + boolArray[0]; + Assert.Equal("x123\tTrue", resultStr); + } + } +} diff --git a/csharp/DiffMatchPatch.cs b/csharp/DiffMatchPatch.cs index 6e19641..385a6d7 100644 --- a/csharp/DiffMatchPatch.cs +++ b/csharp/DiffMatchPatch.cs @@ -1,4 +1,5 @@ /* + * !IMPORTANT: This file is obsolete. All further development will continue from under C# solution (DiffMatchPatch.sln). * Diff Match and Patch * Copyright 2018 The diff-match-patch Authors. * https://github.com/google/diff-match-patch @@ -55,6 +56,7 @@ public enum Operation { /** * Class representing one diff operation. */ + [Obsolete] public class Diff { public Operation operation; // One of: INSERT, DELETE or EQUAL. @@ -121,6 +123,7 @@ public override int GetHashCode() { /** * Class representing one patch operation. */ + [Obsolete] public class Patch { public List diffs = new List(); public int start1; @@ -178,6 +181,7 @@ public override string ToString() { * Class containing the diff, match and patch methods. * Also Contains the behaviour settings. */ + [Obsolete] public class diff_match_patch { // Defaults. // Set these on your diff_match_patch instance to override the defaults. diff --git a/csharp/DiffMatchPatch.sln b/csharp/DiffMatchPatch.sln new file mode 100644 index 0000000..e719ac8 --- /dev/null +++ b/csharp/DiffMatchPatch.sln @@ -0,0 +1,28 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DiffMatchPatch", "DiffMatchPatch\DiffMatchPatch.csproj", "{13DE99BE-E8C4-496D-8489-E2C10C0AA10B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DiffMatchPatch.Tests", "DiffMatchPatch.Tests\DiffMatchPatch.Tests.csproj", "{CB1CA713-68C4-49DB-A62E-987B74E891DB}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DiffMatchPatch.Tests.Performance", "DiffMatchPatch.Tests.Performance\DiffMatchPatch.Tests.Performance.csproj", "{3F423BD6-9AE8-4A8D-B424-895FD3E97D4B}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {13DE99BE-E8C4-496D-8489-E2C10C0AA10B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {13DE99BE-E8C4-496D-8489-E2C10C0AA10B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {13DE99BE-E8C4-496D-8489-E2C10C0AA10B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {13DE99BE-E8C4-496D-8489-E2C10C0AA10B}.Release|Any CPU.Build.0 = Release|Any CPU + {CB1CA713-68C4-49DB-A62E-987B74E891DB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CB1CA713-68C4-49DB-A62E-987B74E891DB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CB1CA713-68C4-49DB-A62E-987B74E891DB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CB1CA713-68C4-49DB-A62E-987B74E891DB}.Release|Any CPU.Build.0 = Release|Any CPU + {3F423BD6-9AE8-4A8D-B424-895FD3E97D4B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3F423BD6-9AE8-4A8D-B424-895FD3E97D4B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3F423BD6-9AE8-4A8D-B424-895FD3E97D4B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3F423BD6-9AE8-4A8D-B424-895FD3E97D4B}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/csharp/DiffMatchPatch/DiffMatchPatch.cs b/csharp/DiffMatchPatch/DiffMatchPatch.cs new file mode 100644 index 0000000..15a4e9a --- /dev/null +++ b/csharp/DiffMatchPatch/DiffMatchPatch.cs @@ -0,0 +1,2805 @@ +/* + * Diff Match and Patch + * Copyright 2018 The diff-match-patch Authors. + * https://github.com/google/diff-match-patch + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Web; + +namespace Google.DiffMatchPatch +{ + internal static class CompatibilityExtensions + { + // JScript splice function + public static List Splice(this List input, int start, int count, + params T[] objects) + { + List deletedRange = input.GetRange(start, count); + input.RemoveRange(start, count); + input.InsertRange(start, objects); + + return deletedRange; + } + + // Java substring function + public static string JavaSubstring(this string s, int begin, int end) + { + return s.Substring(begin, end - begin); + } + } + + /**- + * The data structure representing a diff is a List of Diff objects: + * {Diff(Operation.DELETE, "Hello"), Diff(Operation.INSERT, "Goodbye"), + * Diff(Operation.EQUAL, " world.")} + * which means: delete "Hello", add "Goodbye" and keep " world." + */ + public enum Operation + { + DELETE, + INSERT, + EQUAL + } + + + /** + * Class representing one diff operation. + */ + public class Diff + { + public Operation operation; + + // One of: INSERT, DELETE or EQUAL. + public string text; + // The text associated with this diff operation. + + /** + * Constructor. Initializes the diff with the provided values. + * @param operation One of INSERT, DELETE or EQUAL. + * @param text The text being applied. + */ + public Diff(Operation operation, string text) + { + // Construct a diff with the specified operation and text. + this.operation = operation; + this.text = text; + } + + /** + * Display a human-readable version of this Diff. + * @return text version. + */ + public override string ToString() + { + string prettyText = this.text.Replace('\n', '\u00b6'); + return "Diff(" + this.operation + ",\"" + prettyText + "\")"; + } + + /** + * Is this Diff equivalent to another Diff? + * @param d Another Diff to compare against. + * @return true or false. + */ + public override bool Equals(Object obj) + { + // If parameter is null return false. + if (obj == null) + { + return false; + } + + // If parameter cannot be cast to Diff return false. + Diff p = obj as Diff; + if ((System.Object) p == null) + { + return false; + } + + // Return true if the fields match. + return p.operation == this.operation && p.text == this.text; + } + + public bool Equals(Diff obj) + { + // If parameter is null return false. + if (obj == null) + { + return false; + } + + // Return true if the fields match. + return obj.operation == this.operation && obj.text == this.text; + } + + public override int GetHashCode() + { + return text.GetHashCode() ^ operation.GetHashCode(); + } + } + + + /** + * Class representing one patch operation. + */ + public class Patch + { + public List diffs = new List(); + public int start1; + public int start2; + public int length1; + public int length2; + + /** + * Emulate GNU diff's format. + * Header: @@ -382,8 +481,9 @@ + * Indices are printed as 1-based, not 0-based. + * @return The GNU diff string. + */ + public override string ToString() + { + string coords1, coords2; + if (this.length1 == 0) + { + coords1 = this.start1 + ",0"; + } + else if (this.length1 == 1) + { + coords1 = Convert.ToString(this.start1 + 1); + } + else + { + coords1 = (this.start1 + 1) + "," + this.length1; + } + + if (this.length2 == 0) + { + coords2 = this.start2 + ",0"; + } + else if (this.length2 == 1) + { + coords2 = Convert.ToString(this.start2 + 1); + } + else + { + coords2 = (this.start2 + 1) + "," + this.length2; + } + + StringBuilder text = new StringBuilder(); + text.Append("@@ -").Append(coords1).Append(" +").Append(coords2) + .Append(" @@\n"); + // Escape the body of the patch with %xx notation. + foreach (Diff aDiff in this.diffs) + { + switch (aDiff.operation) + { + case Operation.INSERT: + text.Append('+'); + break; + case Operation.DELETE: + text.Append('-'); + break; + case Operation.EQUAL: + text.Append(' '); + break; + } + + text.Append(diff_match_patch.encodeURI(aDiff.text)).Append("\n"); + } + + return text.ToString(); + } + } + + + /** + * Class containing the diff, match and patch methods. + * Also Contains the behaviour settings. + */ + public class diff_match_patch + { + // Defaults. + // Set these on your diff_match_patch instance to override the defaults. + + // Number of seconds to map a diff before giving up (0 for infinity). + public float Diff_Timeout = 1.0f; + + // Cost of an empty edit operation in terms of edit characters. + public short Diff_EditCost = 4; + + // At what point is no match declared (0.0 = perfection, 1.0 = very loose). + public float Match_Threshold = 0.5f; + + // How far to search for a match (0 = exact location, 1000+ = broad match). + // A match this many characters away from the expected location will add + // 1.0 to the score (0.0 is a perfect match). + public int Match_Distance = 1000; + + // When deleting a large block of text (over ~64 characters), how close + // do the contents have to be to match the expected contents. (0.0 = + // perfection, 1.0 = very loose). Note that Match_Threshold controls + // how closely the end points of a delete need to match. + public float Patch_DeleteThreshold = 0.5f; + + // Chunk size for context length. + public short Patch_Margin = 4; + + // The number of bits in an int. + private short Match_MaxBits = 32; + + + // DIFF FUNCTIONS + + + /** + * Find the differences between two texts. + * Run a faster, slightly less optimal diff. + * This method allows the 'checklines' of diff_main() to be optional. + * Most of the time checklines is wanted, so default to true. + * @param text1 Old string to be diffed. + * @param text2 New string to be diffed. + * @return List of Diff objects. + */ + public List diff_main(string text1, string text2) + { + return diff_main(text1, text2, true); + } + + /** + * Find the differences between two texts. + * @param text1 Old string to be diffed. + * @param text2 New string to be diffed. + * @param 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. + * @return List of Diff objects. + */ + public List diff_main(string text1, string text2, bool checklines) + { + // Set a deadline by which time the diff must be complete. + DateTime deadline; + if (this.Diff_Timeout <= 0) + { + deadline = DateTime.MaxValue; + } + else + { + deadline = DateTime.Now + + new TimeSpan(((long) (Diff_Timeout * 1000)) * 10000); + } + + return diff_main(text1, text2, checklines, deadline); + } + + /** + * Find the differences between two texts. Simplifies the problem by + * stripping any common prefix or suffix off the texts before diffing. + * @param text1 Old string to be diffed. + * @param text2 New string to be diffed. + * @param 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 deadline Time when the diff should be complete by. Used + * internally for recursive calls. Users should set DiffTimeout + * instead. + * @return List of Diff objects. + */ + private List diff_main(string text1, string text2, bool checklines, + DateTime deadline) + { + // Check for null inputs not needed since null can't be passed in C#. + + // Check for equality (speedup). + List diffs; + if (text1 == text2) + { + diffs = new List(); + if (text1.Length != 0) + { + diffs.Add(new Diff(Operation.EQUAL, text1)); + } + + return diffs; + } + + // Trim off common prefix (speedup). + int commonlength = diff_commonPrefix(text1, text2); + string commonprefix = text1.Substring(0, commonlength); + text1 = text1.Substring(commonlength); + text2 = text2.Substring(commonlength); + + // Trim off common suffix (speedup). + commonlength = diff_commonSuffix(text1, text2); + string commonsuffix = text1.Substring(text1.Length - commonlength); + text1 = text1.Substring(0, text1.Length - commonlength); + text2 = text2.Substring(0, text2.Length - commonlength); + + // Compute the diff on the middle block. + diffs = diff_compute(text1, text2, checklines, deadline); + + // Restore the prefix and suffix. + if (commonprefix.Length != 0) + { + diffs.Insert(0, (new Diff(Operation.EQUAL, commonprefix))); + } + + if (commonsuffix.Length != 0) + { + diffs.Add(new Diff(Operation.EQUAL, commonsuffix)); + } + + diff_cleanupMerge(diffs); + return diffs; + } + + /** + * Find the differences between two texts. Assumes that the texts do not + * have any common prefix or suffix. + * @param text1 Old string to be diffed. + * @param text2 New string to be diffed. + * @param 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 deadline Time when the diff should be complete by. + * @return List of Diff objects. + */ + private List diff_compute(string text1, string text2, + bool checklines, DateTime deadline) + { + List diffs = new List(); + + if (text1.Length == 0) + { + // Just add some text (speedup). + diffs.Add(new Diff(Operation.INSERT, text2)); + return diffs; + } + + if (text2.Length == 0) + { + // Just delete some text (speedup). + diffs.Add(new Diff(Operation.DELETE, text1)); + return diffs; + } + + string longtext = text1.Length > text2.Length ? text1 : text2; + string shorttext = text1.Length > text2.Length ? text2 : text1; + int i = longtext.IndexOf(shorttext, StringComparison.Ordinal); + if (i != -1) + { + // Shorter text is inside the longer text (speedup). + Operation op = (text1.Length > text2.Length) ? Operation.DELETE : Operation.INSERT; + diffs.Add(new Diff(op, longtext.Substring(0, i))); + diffs.Add(new Diff(Operation.EQUAL, shorttext)); + diffs.Add(new Diff(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(new Diff(Operation.DELETE, text1)); + diffs.Add(new Diff(Operation.INSERT, text2)); + return diffs; + } + + // Check to see if the problem can be split in two. + string[] hm = diff_halfMatch(text1, text2); + if (hm != null) + { + // A half-match was found, sort out the return data. + string text1_a = hm[0]; + string text1_b = hm[1]; + string text2_a = hm[2]; + string text2_b = hm[3]; + string mid_common = hm[4]; + // Send both pairs off for separate processing. + List diffs_a = diff_main(text1_a, text2_a, checklines, deadline); + List diffs_b = diff_main(text1_b, text2_b, checklines, deadline); + // Merge the results. + diffs = diffs_a; + diffs.Add(new Diff(Operation.EQUAL, mid_common)); + diffs.AddRange(diffs_b); + return diffs; + } + + if (checklines && text1.Length > 100 && text2.Length > 100) + { + return diff_lineMode(text1, text2, deadline); + } + + return diff_bisect(text1, text2, deadline); + } + + /** + * Do a quick line-level diff on both strings, then rediff the parts for + * greater accuracy. + * This speedup can produce non-minimal diffs. + * @param text1 Old string to be diffed. + * @param text2 New string to be diffed. + * @param deadline Time when the diff should be complete by. + * @return List of Diff objects. + */ + private List diff_lineMode(string text1, string text2, + DateTime deadline) + { + // Scan the text on a line-by-line basis first. + Object[] a = diff_linesToChars(text1, text2); + text1 = (string) a[0]; + text2 = (string) a[1]; + List linearray = (List) a[2]; + + List diffs = diff_main(text1, text2, false, deadline); + + // Convert the diff back to original text. + diff_charsToLines(diffs, linearray); + // Eliminate freak matches (e.g. blank lines) + diff_cleanupSemantic(diffs); + + // Rediff any replacement blocks, this time character-by-character. + // Add a dummy entry at the end. + diffs.Add(new Diff(Operation.EQUAL, string.Empty)); + int pointer = 0; + int count_delete = 0; + int count_insert = 0; + string text_delete = string.Empty; + string text_insert = string.Empty; + while (pointer < diffs.Count) + { + switch (diffs[pointer].operation) + { + case Operation.INSERT: + count_insert++; + text_insert += diffs[pointer].text; + break; + case Operation.DELETE: + count_delete++; + text_delete += diffs[pointer].text; + break; + case Operation.EQUAL: + // Upon reaching an equality, check for prior redundancies. + if (count_delete >= 1 && count_insert >= 1) + { + // Delete the offending records and add the merged ones. + diffs.RemoveRange(pointer - count_delete - count_insert, + count_delete + count_insert); + pointer = pointer - count_delete - count_insert; + List subDiff = + this.diff_main(text_delete, text_insert, false, deadline); + diffs.InsertRange(pointer, subDiff); + pointer = pointer + subDiff.Count; + } + + count_insert = 0; + count_delete = 0; + text_delete = string.Empty; + text_insert = string.Empty; + break; + } + + pointer++; + } + + diffs.RemoveAt(diffs.Count - 1); // Remove the dummy entry at the end. + + return diffs; + } + + /** + * Find the 'middle snake' of a diff, split the problem in two + * and return the recursively constructed diff. + * See Myers 1986 paper: An O(ND) Difference Algorithm and Its Variations. + * @param text1 Old string to be diffed. + * @param text2 New string to be diffed. + * @param deadline Time at which to bail if not yet complete. + * @return List of Diff objects. + */ + protected List diff_bisect(string text1, string text2, + DateTime deadline) + { + // Cache the text lengths to prevent multiple calls. + int text1_length = text1.Length; + int text2_length = text2.Length; + int max_d = (text1_length + text2_length + 1) / 2; + int v_offset = max_d; + int v_length = 2 * max_d; + int[] v1 = new int[v_length]; + int[] v2 = new int[v_length]; + for (int x = 0; x < v_length; x++) + { + v1[x] = -1; + v2[x] = -1; + } + + v1[v_offset + 1] = 0; + v2[v_offset + 1] = 0; + int delta = text1_length - text2_length; + // If the total number of characters is odd, then the front path will + // collide with the reverse path. + bool front = (delta % 2 != 0); + // Offsets for start and end of k loop. + // Prevents mapping of space beyond the grid. + int k1start = 0; + int k1end = 0; + int k2start = 0; + int k2end = 0; + for (int d = 0; d < max_d; d++) + { + // Bail out if deadline is reached. + if (DateTime.Now > deadline) + { + break; + } + + // Walk the front path one step. + for (int k1 = -d + k1start; k1 <= d - k1end; k1 += 2) + { + int k1_offset = v_offset + k1; + int x1; + if (k1 == -d || k1 != d && v1[k1_offset - 1] < v1[k1_offset + 1]) + { + x1 = v1[k1_offset + 1]; + } + else + { + x1 = v1[k1_offset - 1] + 1; + } + + int y1 = x1 - k1; + while (x1 < text1_length && y1 < text2_length + && text1[x1] == text2[y1]) + { + x1++; + y1++; + } + + v1[k1_offset] = x1; + if (x1 > text1_length) + { + // Ran off the right of the graph. + k1end += 2; + } + else if (y1 > text2_length) + { + // Ran off the bottom of the graph. + k1start += 2; + } + else if (front) + { + int k2_offset = v_offset + delta - k1; + if (k2_offset >= 0 && k2_offset < v_length && v2[k2_offset] != -1) + { + // Mirror x2 onto top-left coordinate system. + int x2 = text1_length - v2[k2_offset]; + if (x1 >= x2) + { + // Overlap detected. + return diff_bisectSplit(text1, text2, x1, y1, deadline); + } + } + } + } + + // Walk the reverse path one step. + for (int k2 = -d + k2start; k2 <= d - k2end; k2 += 2) + { + int k2_offset = v_offset + k2; + int x2; + if (k2 == -d || k2 != d && v2[k2_offset - 1] < v2[k2_offset + 1]) + { + x2 = v2[k2_offset + 1]; + } + else + { + x2 = v2[k2_offset - 1] + 1; + } + + int y2 = x2 - k2; + while (x2 < text1_length && y2 < text2_length + && text1[text1_length - x2 - 1] + == text2[text2_length - y2 - 1]) + { + x2++; + y2++; + } + + v2[k2_offset] = x2; + if (x2 > text1_length) + { + // Ran off the left of the graph. + k2end += 2; + } + else if (y2 > text2_length) + { + // Ran off the top of the graph. + k2start += 2; + } + else if (!front) + { + int k1_offset = v_offset + delta - k2; + if (k1_offset >= 0 && k1_offset < v_length && v1[k1_offset] != -1) + { + int x1 = v1[k1_offset]; + int y1 = v_offset + x1 - k1_offset; + // Mirror x2 onto top-left coordinate system. + x2 = text1_length - v2[k2_offset]; + if (x1 >= x2) + { + // Overlap detected. + return diff_bisectSplit(text1, text2, x1, y1, deadline); + } + } + } + } + } + + // Diff took too long and hit the deadline or + // number of diffs equals number of characters, no commonality at all. + List diffs = new List(); + diffs.Add(new Diff(Operation.DELETE, text1)); + diffs.Add(new Diff(Operation.INSERT, text2)); + return diffs; + } + + /** + * Given the location of the 'middle snake', split the diff in two parts + * and recurse. + * @param text1 Old string to be diffed. + * @param text2 New string to be diffed. + * @param x Index of split point in text1. + * @param y Index of split point in text2. + * @param deadline Time at which to bail if not yet complete. + * @return LinkedList of Diff objects. + */ + private List diff_bisectSplit(string text1, string text2, + int x, int y, DateTime deadline) + { + string text1a = text1.Substring(0, x); + string text2a = text2.Substring(0, y); + string text1b = text1.Substring(x); + string text2b = text2.Substring(y); + + // Compute both diffs serially. + List diffs = diff_main(text1a, text2a, false, deadline); + List diffsb = diff_main(text1b, text2b, false, deadline); + + diffs.AddRange(diffsb); + return diffs; + } + + /** + * Split two texts into a list of strings. Reduce the texts to a string of + * hashes where each Unicode character represents one line. + * @param text1 First string. + * @param text2 Second string. + * @return Three element Object array, containing the encoded text1, the + * encoded text2 and the List of unique strings. The zeroth element + * of the List of unique strings is intentionally blank. + */ + protected Object[] diff_linesToChars(string text1, string text2) + { + List lineArray = new List(); + Dictionary lineHash = new Dictionary(); + // e.g. linearray[4] == "Hello\n" + // e.g. linehash.get("Hello\n") == 4 + + // "\x00" is a valid character, but various debuggers don't like it. + // So we'll insert a junk entry to avoid generating a null character. + lineArray.Add(string.Empty); + + // Allocate 2/3rds of the space for text1, the rest for text2. + string chars1 = diff_linesToCharsMunge(text1, lineArray, lineHash, 40000); + string chars2 = diff_linesToCharsMunge(text2, lineArray, lineHash, 65535); + return new Object[] {chars1, chars2, lineArray}; + } + + /** + * Split a text into a list of strings. Reduce the texts to a string of + * hashes where each Unicode character represents one line. + * @param text String to encode. + * @param lineArray List of unique strings. + * @param lineHash Map of strings to indices. + * @param maxLines Maximum length of lineArray. + * @return Encoded string. + */ + private string diff_linesToCharsMunge(string text, List lineArray, + Dictionary lineHash, int maxLines) + { + int lineStart = 0; + int lineEnd = -1; + string line; + StringBuilder chars = new StringBuilder(); + // Walk the text, pulling out a Substring for each line. + // text.split('\n') would would temporarily double our memory footprint. + // Modifying text would create many large strings to garbage collect. + while (lineEnd < text.Length - 1) + { + lineEnd = text.IndexOf('\n', lineStart); + if (lineEnd == -1) + { + lineEnd = text.Length - 1; + } + + line = text.JavaSubstring(lineStart, lineEnd + 1); + + if (lineHash.ContainsKey(line)) + { + chars.Append(((char) (int) lineHash[line])); + } + else + { + if (lineArray.Count == maxLines) + { + // Bail out at 65535 because char 65536 == char 0. + line = text.Substring(lineStart); + lineEnd = text.Length; + } + + lineArray.Add(line); + lineHash.Add(line, lineArray.Count - 1); + chars.Append(((char) (lineArray.Count - 1))); + } + + lineStart = lineEnd + 1; + } + + return chars.ToString(); + } + + /** + * Rehydrate the text in a diff from a string of line hashes to real lines + * of text. + * @param diffs List of Diff objects. + * @param lineArray List of unique strings. + */ + protected void diff_charsToLines(ICollection diffs, + IList lineArray) + { + StringBuilder text; + foreach (Diff diff in diffs) + { + text = new StringBuilder(); + for (int j = 0; j < diff.text.Length; j++) + { + text.Append(lineArray[diff.text[j]]); + } + + diff.text = text.ToString(); + } + } + + /** + * Determine the common prefix of two strings. + * @param text1 First string. + * @param text2 Second string. + * @return The number of characters common to the start of each string. + */ + public int diff_commonPrefix(string text1, string text2) + { + // Performance analysis: https://neil.fraser.name/news/2007/10/09/ + int n = Math.Min(text1.Length, text2.Length); + for (int i = 0; i < n; i++) + { + if (text1[i] != text2[i]) + { + return i; + } + } + + return n; + } + + /** + * Determine the common suffix of two strings. + * @param text1 First string. + * @param text2 Second string. + * @return The number of characters common to the end of each string. + */ + public int diff_commonSuffix(string text1, string text2) + { + // Performance analysis: https://neil.fraser.name/news/2007/10/09/ + int text1_length = text1.Length; + int text2_length = text2.Length; + int n = Math.Min(text1.Length, text2.Length); + for (int i = 1; i <= n; i++) + { + if (text1[text1_length - i] != text2[text2_length - i]) + { + return i - 1; + } + } + + return n; + } + + /** + * Determine if the suffix of one string is the prefix of another. + * @param text1 First string. + * @param text2 Second string. + * @return The number of characters common to the end of the first + * string and the start of the second string. + */ + protected int diff_commonOverlap(string text1, string text2) + { + // Cache the text lengths to prevent multiple calls. + int text1_length = text1.Length; + int text2_length = text2.Length; + // Eliminate the null case. + if (text1_length == 0 || text2_length == 0) + { + return 0; + } + + // Truncate the longer string. + if (text1_length > text2_length) + { + text1 = text1.Substring(text1_length - text2_length); + } + else if (text1_length < text2_length) + { + text2 = text2.Substring(0, text1_length); + } + + int text_length = Math.Min(text1_length, text2_length); + // Quick check for the worst case. + if (text1 == text2) + { + return text_length; + } + + // Start by looking for a single character match + // and increase length until no match is found. + // Performance analysis: https://neil.fraser.name/news/2010/11/04/ + int best = 0; + int length = 1; + while (true) + { + string pattern = text1.Substring(text_length - length); + int found = text2.IndexOf(pattern, StringComparison.Ordinal); + if (found == -1) + { + return best; + } + + length += found; + if (found == 0 || text1.Substring(text_length - length) == + text2.Substring(0, length)) + { + best = length; + length++; + } + } + } + + /** + * Do the two texts share a Substring which is at least half the length of + * the longer text? + * This speedup can produce non-minimal diffs. + * @param text1 First string. + * @param text2 Second string. + * @return Five element String array, containing the prefix of text1, the + * suffix of text1, the prefix of text2, the suffix of text2 and the + * common middle. Or null if there was no match. + */ + + protected string[] diff_halfMatch(string text1, string text2) + { + if (this.Diff_Timeout <= 0) + { + // Don't risk returning a non-optimal diff if we have unlimited time. + return null; + } + + string longtext = text1.Length > text2.Length ? text1 : text2; + string shorttext = text1.Length > text2.Length ? text2 : text1; + if (longtext.Length < 4 || shorttext.Length * 2 < longtext.Length) + { + return null; // Pointless. + } + + // First check if the second quarter is the seed for a half-match. + string[] hm1 = diff_halfMatchI(longtext, shorttext, + (longtext.Length + 3) / 4); + // Check again based on the third quarter. + string[] hm2 = diff_halfMatchI(longtext, shorttext, + (longtext.Length + 1) / 2); + string[] hm; + if (hm1 == null && hm2 == null) + { + return null; + } + else if (hm2 == null) + { + hm = hm1; + } + else if (hm1 == null) + { + hm = hm2; + } + else + { + // Both matched. Select the longest. + hm = hm1[4].Length > hm2[4].Length ? hm1 : hm2; + } + + // A half-match was found, sort out the return data. + if (text1.Length > text2.Length) + { + return hm; + //return new string[]{hm[0], hm[1], hm[2], hm[3], hm[4]}; + } + else + { + return new string[] {hm[2], hm[3], hm[0], hm[1], hm[4]}; + } + } + + /** + * Does a Substring of shorttext exist within longtext such that the + * Substring is at least half the length of longtext? + * @param longtext Longer string. + * @param shorttext Shorter string. + * @param i Start index of quarter length Substring within longtext. + * @return Five element string array, containing the prefix of longtext, the + * suffix of longtext, the prefix of shorttext, the suffix of shorttext + * and the common middle. Or null if there was no match. + */ + private string[] diff_halfMatchI(string longtext, string shorttext, int i) + { + // Start with a 1/4 length Substring at position i as a seed. + string seed = longtext.Substring(i, longtext.Length / 4); + int j = -1; + string best_common = string.Empty; + string best_longtext_a = string.Empty, best_longtext_b = string.Empty; + string best_shorttext_a = string.Empty, best_shorttext_b = string.Empty; + while (j < shorttext.Length && (j = shorttext.IndexOf(seed, j + 1, + StringComparison.Ordinal)) != -1) + { + int prefixLength = diff_commonPrefix(longtext.Substring(i), + shorttext.Substring(j)); + int suffixLength = diff_commonSuffix(longtext.Substring(0, i), + shorttext.Substring(0, j)); + if (best_common.Length < suffixLength + prefixLength) + { + best_common = shorttext.Substring(j - suffixLength, suffixLength) + + shorttext.Substring(j, prefixLength); + best_longtext_a = longtext.Substring(0, i - suffixLength); + best_longtext_b = longtext.Substring(i + prefixLength); + best_shorttext_a = shorttext.Substring(0, j - suffixLength); + best_shorttext_b = shorttext.Substring(j + prefixLength); + } + } + + if (best_common.Length * 2 >= longtext.Length) + { + return new string[] + { + best_longtext_a, best_longtext_b, + best_shorttext_a, best_shorttext_b, best_common + }; + } + else + { + return null; + } + } + + /** + * Reduce the number of edits by eliminating semantically trivial + * equalities. + * @param diffs List of Diff objects. + */ + public void diff_cleanupSemantic(List diffs) + { + bool changes = false; + // Stack of indices where equalities are found. + Stack equalities = new Stack(); + // Always equal to equalities[equalitiesLength-1][1] + string lastEquality = null; + int pointer = 0; // Index of current position. + // Number of characters that changed prior to the equality. + int length_insertions1 = 0; + int length_deletions1 = 0; + // Number of characters that changed after the equality. + int length_insertions2 = 0; + int length_deletions2 = 0; + while (pointer < diffs.Count) + { + if (diffs[pointer].operation == Operation.EQUAL) + { + // Equality found. + equalities.Push(pointer); + length_insertions1 = length_insertions2; + length_deletions1 = length_deletions2; + length_insertions2 = 0; + length_deletions2 = 0; + lastEquality = diffs[pointer].text; + } + else + { + // an insertion or deletion + if (diffs[pointer].operation == Operation.INSERT) + { + length_insertions2 += diffs[pointer].text.Length; + } + else + { + length_deletions2 += diffs[pointer].text.Length; + } + + // Eliminate an equality that is smaller or equal to the edits on both + // sides of it. + if (lastEquality != null && (lastEquality.Length + <= Math.Max(length_insertions1, length_deletions1)) + && (lastEquality.Length + <= Math.Max(length_insertions2, length_deletions2))) + { + // Duplicate record. + diffs.Insert(equalities.Peek(), + new Diff(Operation.DELETE, lastEquality)); + // Change second copy to insert. + diffs[equalities.Peek() + 1].operation = Operation.INSERT; + // Throw away the equality we just deleted. + equalities.Pop(); + if (equalities.Count > 0) + { + equalities.Pop(); + } + + pointer = equalities.Count > 0 ? equalities.Peek() : -1; + length_insertions1 = 0; // Reset the counters. + length_deletions1 = 0; + length_insertions2 = 0; + length_deletions2 = 0; + lastEquality = null; + changes = true; + } + } + + pointer++; + } + + // Normalize the diff. + if (changes) + { + diff_cleanupMerge(diffs); + } + + diff_cleanupSemanticLossless(diffs); + + // Find any overlaps between deletions and insertions. + // e.g: abcxxxxxxdef + // -> abcxxxdef + // e.g: xxxabcdefxxx + // -> defxxxabc + // Only extract an overlap if it is as big as the edit ahead or behind it. + pointer = 1; + while (pointer < diffs.Count) + { + if (diffs[pointer - 1].operation == Operation.DELETE && + diffs[pointer].operation == Operation.INSERT) + { + string deletion = diffs[pointer - 1].text; + string insertion = diffs[pointer].text; + int overlap_length1 = diff_commonOverlap(deletion, insertion); + int overlap_length2 = diff_commonOverlap(insertion, deletion); + if (overlap_length1 >= overlap_length2) + { + if (overlap_length1 >= deletion.Length / 2.0 || + overlap_length1 >= insertion.Length / 2.0) + { + // Overlap found. + // Insert an equality and trim the surrounding edits. + diffs.Insert(pointer, new Diff(Operation.EQUAL, + insertion.Substring(0, overlap_length1))); + diffs[pointer - 1].text = + deletion.Substring(0, deletion.Length - overlap_length1); + diffs[pointer + 1].text = insertion.Substring(overlap_length1); + pointer++; + } + } + else + { + if (overlap_length2 >= deletion.Length / 2.0 || + overlap_length2 >= insertion.Length / 2.0) + { + // Reverse overlap found. + // Insert an equality and swap and trim the surrounding edits. + diffs.Insert(pointer, new Diff(Operation.EQUAL, + deletion.Substring(0, overlap_length2))); + diffs[pointer - 1].operation = Operation.INSERT; + diffs[pointer - 1].text = + insertion.Substring(0, insertion.Length - overlap_length2); + diffs[pointer + 1].operation = Operation.DELETE; + diffs[pointer + 1].text = deletion.Substring(overlap_length2); + pointer++; + } + } + + pointer++; + } + + pointer++; + } + } + + /** + * Look for single edits surrounded on both sides by equalities + * which can be shifted sideways to align the edit to a word boundary. + * e.g: The cat came. -> The cat came. + * @param diffs List of Diff objects. + */ + public void diff_cleanupSemanticLossless(List diffs) + { + int pointer = 1; + // Intentionally ignore the first and last element (don't need checking). + while (pointer < diffs.Count - 1) + { + if (diffs[pointer - 1].operation == Operation.EQUAL && + diffs[pointer + 1].operation == Operation.EQUAL) + { + // This is a single edit surrounded by equalities. + string equality1 = diffs[pointer - 1].text; + string edit = diffs[pointer].text; + string equality2 = diffs[pointer + 1].text; + + // First, shift the edit as far left as possible. + int commonOffset = this.diff_commonSuffix(equality1, edit); + if (commonOffset > 0) + { + string commonString = edit.Substring(edit.Length - commonOffset); + equality1 = equality1.Substring(0, equality1.Length - commonOffset); + edit = commonString + edit.Substring(0, edit.Length - commonOffset); + equality2 = commonString + equality2; + } + + // Second, step character by character right, + // looking for the best fit. + string bestEquality1 = equality1; + string bestEdit = edit; + string bestEquality2 = equality2; + int bestScore = diff_cleanupSemanticScore(equality1, edit) + + diff_cleanupSemanticScore(edit, equality2); + while (edit.Length != 0 && equality2.Length != 0 + && edit[0] == equality2[0]) + { + equality1 += edit[0]; + edit = edit.Substring(1) + equality2[0]; + equality2 = equality2.Substring(1); + int score = diff_cleanupSemanticScore(equality1, edit) + + diff_cleanupSemanticScore(edit, equality2); + // The >= encourages trailing rather than leading whitespace on + // edits. + if (score >= bestScore) + { + bestScore = score; + bestEquality1 = equality1; + bestEdit = edit; + bestEquality2 = equality2; + } + } + + if (diffs[pointer - 1].text != bestEquality1) + { + // We have an improvement, save it back to the diff. + if (bestEquality1.Length != 0) + { + diffs[pointer - 1].text = bestEquality1; + } + else + { + diffs.RemoveAt(pointer - 1); + pointer--; + } + + diffs[pointer].text = bestEdit; + if (bestEquality2.Length != 0) + { + diffs[pointer + 1].text = bestEquality2; + } + else + { + diffs.RemoveAt(pointer + 1); + pointer--; + } + } + } + + pointer++; + } + } + + /** + * Given two strings, compute a score representing whether the internal + * boundary falls on logical boundaries. + * Scores range from 6 (best) to 0 (worst). + * @param one First string. + * @param two Second string. + * @return The score. + */ + private int diff_cleanupSemanticScore(string one, string two) + { + if (one.Length == 0 || two.Length == 0) + { + // Edges are the best. + return 6; + } + + // Each port of this function behaves slightly differently due to + // subtle differences in each language's definition of things like + // 'whitespace'. Since this function's purpose is largely cosmetic, + // the choice has been made to use each language's native features + // rather than force total conformity. + char char1 = one[one.Length - 1]; + char char2 = two[0]; + bool nonAlphaNumeric1 = !Char.IsLetterOrDigit(char1); + bool nonAlphaNumeric2 = !Char.IsLetterOrDigit(char2); + bool whitespace1 = nonAlphaNumeric1 && Char.IsWhiteSpace(char1); + bool whitespace2 = nonAlphaNumeric2 && Char.IsWhiteSpace(char2); + bool lineBreak1 = whitespace1 && Char.IsControl(char1); + bool lineBreak2 = whitespace2 && Char.IsControl(char2); + bool blankLine1 = lineBreak1 && BLANKLINEEND.IsMatch(one); + bool blankLine2 = lineBreak2 && BLANKLINESTART.IsMatch(two); + + if (blankLine1 || blankLine2) + { + // Five points for blank lines. + return 5; + } + else if (lineBreak1 || lineBreak2) + { + // Four points for line breaks. + return 4; + } + else if (nonAlphaNumeric1 && !whitespace1 && whitespace2) + { + // Three points for end of sentences. + return 3; + } + else if (whitespace1 || whitespace2) + { + // Two points for whitespace. + return 2; + } + else if (nonAlphaNumeric1 || nonAlphaNumeric2) + { + // One point for non-alphanumeric. + return 1; + } + + return 0; + } + + // Define some regex patterns for matching boundaries. + private Regex BLANKLINEEND = new Regex("\\n\\r?\\n\\Z"); + private Regex BLANKLINESTART = new Regex("\\A\\r?\\n\\r?\\n"); + + /** + * Reduce the number of edits by eliminating operationally trivial + * equalities. + * @param diffs List of Diff objects. + */ + public void diff_cleanupEfficiency(List diffs) + { + bool changes = false; + // Stack of indices where equalities are found. + Stack equalities = new Stack(); + // Always equal to equalities[equalitiesLength-1][1] + string lastEquality = string.Empty; + int pointer = 0; // Index of current position. + // Is there an insertion operation before the last equality. + bool pre_ins = false; + // Is there a deletion operation before the last equality. + bool pre_del = false; + // Is there an insertion operation after the last equality. + bool post_ins = false; + // Is there a deletion operation after the last equality. + bool post_del = false; + while (pointer < diffs.Count) + { + if (diffs[pointer].operation == Operation.EQUAL) + { + // Equality found. + if (diffs[pointer].text.Length < this.Diff_EditCost + && (post_ins || post_del)) + { + // Candidate found. + equalities.Push(pointer); + pre_ins = post_ins; + pre_del = post_del; + lastEquality = diffs[pointer].text; + } + else + { + // Not a candidate, and can never become one. + equalities.Clear(); + lastEquality = string.Empty; + } + + post_ins = post_del = false; + } + else + { + // An insertion or deletion. + if (diffs[pointer].operation == Operation.DELETE) + { + post_del = true; + } + else + { + post_ins = true; + } + + /* + * Five types to be split: + * ABXYCD + * AXCD + * ABXC + * AXCD + * ABXC + */ + if ((lastEquality.Length != 0) + && ((pre_ins && pre_del && post_ins && post_del) + || ((lastEquality.Length < this.Diff_EditCost / 2) + && ((pre_ins ? 1 : 0) + (pre_del ? 1 : 0) + (post_ins ? 1 : 0) + + (post_del ? 1 : 0)) == 3))) + { + // Duplicate record. + diffs.Insert(equalities.Peek(), + new Diff(Operation.DELETE, lastEquality)); + // Change second copy to insert. + diffs[equalities.Peek() + 1].operation = Operation.INSERT; + equalities.Pop(); // Throw away the equality we just deleted. + lastEquality = string.Empty; + if (pre_ins && pre_del) + { + // No changes made which could affect previous entry, keep going. + post_ins = post_del = true; + equalities.Clear(); + } + else + { + if (equalities.Count > 0) + { + equalities.Pop(); + } + + pointer = equalities.Count > 0 ? equalities.Peek() : -1; + post_ins = post_del = false; + } + + changes = true; + } + } + + pointer++; + } + + if (changes) + { + diff_cleanupMerge(diffs); + } + } + + /** + * Reorder and merge like edit sections. Merge equalities. + * Any edit section can move as long as it doesn't cross an equality. + * @param diffs List of Diff objects. + */ + public void diff_cleanupMerge(List diffs) + { + // Add a dummy entry at the end. + diffs.Add(new Diff(Operation.EQUAL, string.Empty)); + int pointer = 0; + int count_delete = 0; + int count_insert = 0; + string text_delete = string.Empty; + string text_insert = string.Empty; + int commonlength; + while (pointer < diffs.Count) + { + switch (diffs[pointer].operation) + { + case Operation.INSERT: + count_insert++; + text_insert += diffs[pointer].text; + pointer++; + break; + case Operation.DELETE: + count_delete++; + text_delete += diffs[pointer].text; + pointer++; + break; + case Operation.EQUAL: + // Upon reaching an equality, check for prior redundancies. + if (count_delete + count_insert > 1) + { + if (count_delete != 0 && count_insert != 0) + { + // Factor out any common prefixies. + commonlength = this.diff_commonPrefix(text_insert, text_delete); + if (commonlength != 0) + { + if ((pointer - count_delete - count_insert) > 0 && + diffs[pointer - count_delete - count_insert - 1].operation + == Operation.EQUAL) + { + diffs[pointer - count_delete - count_insert - 1].text + += text_insert.Substring(0, commonlength); + } + else + { + diffs.Insert(0, new Diff(Operation.EQUAL, + text_insert.Substring(0, commonlength))); + pointer++; + } + + text_insert = text_insert.Substring(commonlength); + text_delete = text_delete.Substring(commonlength); + } + + // Factor out any common suffixies. + commonlength = this.diff_commonSuffix(text_insert, text_delete); + if (commonlength != 0) + { + diffs[pointer].text = text_insert.Substring(text_insert.Length + - commonlength) + diffs[pointer].text; + text_insert = text_insert.Substring(0, text_insert.Length + - commonlength); + text_delete = text_delete.Substring(0, text_delete.Length + - commonlength); + } + } + + // Delete the offending records and add the merged ones. + pointer -= count_delete + count_insert; + diffs.Splice(pointer, count_delete + count_insert); + if (text_delete.Length != 0) + { + diffs.Splice(pointer, 0, + new Diff(Operation.DELETE, text_delete)); + pointer++; + } + + if (text_insert.Length != 0) + { + diffs.Splice(pointer, 0, + new Diff(Operation.INSERT, text_insert)); + pointer++; + } + + pointer++; + } + else if (pointer != 0 + && diffs[pointer - 1].operation == Operation.EQUAL) + { + // Merge this equality with the previous one. + diffs[pointer - 1].text += diffs[pointer].text; + diffs.RemoveAt(pointer); + } + else + { + pointer++; + } + + count_insert = 0; + count_delete = 0; + text_delete = string.Empty; + text_insert = string.Empty; + break; + } + } + + if (diffs[diffs.Count - 1].text.Length == 0) + { + diffs.RemoveAt(diffs.Count - 1); // Remove the dummy entry at the end. + } + + // Second pass: look for single edits surrounded on both sides by + // equalities which can be shifted sideways to eliminate an equality. + // e.g: ABAC -> ABAC + bool changes = false; + pointer = 1; + // Intentionally ignore the first and last element (don't need checking). + while (pointer < (diffs.Count - 1)) + { + if (diffs[pointer - 1].operation == Operation.EQUAL && + diffs[pointer + 1].operation == Operation.EQUAL) + { + // This is a single edit surrounded by equalities. + if (diffs[pointer].text.EndsWith(diffs[pointer - 1].text, + StringComparison.Ordinal)) + { + // Shift the edit over the previous equality. + diffs[pointer].text = diffs[pointer - 1].text + + diffs[pointer].text.Substring(0, diffs[pointer].text.Length - + diffs[pointer - 1].text.Length); + diffs[pointer + 1].text = diffs[pointer - 1].text + + diffs[pointer + 1].text; + diffs.Splice(pointer - 1, 1); + changes = true; + } + else if (diffs[pointer].text.StartsWith(diffs[pointer + 1].text, + StringComparison.Ordinal)) + { + // Shift the edit over the next equality. + diffs[pointer - 1].text += diffs[pointer + 1].text; + diffs[pointer].text = + diffs[pointer].text.Substring(diffs[pointer + 1].text.Length) + + diffs[pointer + 1].text; + diffs.Splice(pointer + 1, 1); + changes = true; + } + } + + pointer++; + } + + // If shifts were made, the diff needs reordering and another shift sweep. + if (changes) + { + this.diff_cleanupMerge(diffs); + } + } + + /** + * loc is a location in text1, compute and return the equivalent location in + * text2. + * e.g. "The cat" vs "The big cat", 1->1, 5->8 + * @param diffs List of Diff objects. + * @param loc Location within text1. + * @return Location within text2. + */ + public int diff_xIndex(List diffs, int loc) + { + int chars1 = 0; + int chars2 = 0; + int last_chars1 = 0; + int last_chars2 = 0; + Diff lastDiff = null; + foreach (Diff 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 > loc) + { + // Overshot the location. + lastDiff = aDiff; + break; + } + + last_chars1 = chars1; + last_chars2 = chars2; + } + + if (lastDiff != null && lastDiff.operation == Operation.DELETE) + { + // The location was deleted. + return last_chars2; + } + + // Add the remaining character length. + return last_chars2 + (loc - last_chars1); + } + + /** + * Convert a Diff list into a pretty HTML report. + * @param diffs List of Diff objects. + * @return HTML representation. + */ + public string diff_prettyHtml(List diffs) + { + StringBuilder html = new StringBuilder(); + foreach (Diff aDiff in diffs) + { + string text = aDiff.text.Replace("&", "&").Replace("<", "<") + .Replace(">", ">").Replace("\n", "¶
"); + switch (aDiff.operation) + { + case Operation.INSERT: + html.Append("").Append(text) + .Append(""); + break; + case Operation.DELETE: + html.Append("").Append(text) + .Append(""); + break; + case Operation.EQUAL: + html.Append("").Append(text).Append(""); + break; + } + } + + return html.ToString(); + } + + /** + * Compute and return the source text (all equalities and deletions). + * @param diffs List of Diff objects. + * @return Source text. + */ + public string diff_text1(List diffs) + { + StringBuilder text = new StringBuilder(); + foreach (Diff aDiff in diffs) + { + if (aDiff.operation != Operation.INSERT) + { + text.Append(aDiff.text); + } + } + + return text.ToString(); + } + + /** + * Compute and return the destination text (all equalities and insertions). + * @param diffs List of Diff objects. + * @return Destination text. + */ + public string diff_text2(List diffs) + { + StringBuilder text = new StringBuilder(); + foreach (Diff aDiff in diffs) + { + if (aDiff.operation != Operation.DELETE) + { + text.Append(aDiff.text); + } + } + + return text.ToString(); + } + + /** + * Compute the Levenshtein distance; the number of inserted, deleted or + * substituted characters. + * @param diffs List of Diff objects. + * @return Number of changes. + */ + public int diff_levenshtein(List diffs) + { + int levenshtein = 0; + int insertions = 0; + int deletions = 0; + foreach (Diff aDiff in diffs) + { + switch (aDiff.operation) + { + case Operation.INSERT: + insertions += aDiff.text.Length; + break; + case Operation.DELETE: + deletions += aDiff.text.Length; + break; + case Operation.EQUAL: + // A deletion and an insertion is one substitution. + levenshtein += Math.Max(insertions, deletions); + insertions = 0; + deletions = 0; + break; + } + } + + levenshtein += Math.Max(insertions, deletions); + return levenshtein; + } + + /** + * Crush the diff into an encoded string which describes the operations + * required to transform text1 into text2. + * E.g. =3\t-2\t+ing -> Keep 3 chars, delete 2 chars, insert 'ing'. + * Operations are tab-separated. Inserted text is escaped using %xx + * notation. + * @param diffs Array of Diff objects. + * @return Delta text. + */ + public string diff_toDelta(List diffs) + { + StringBuilder text = new StringBuilder(); + foreach (Diff aDiff in diffs) + { + switch (aDiff.operation) + { + case Operation.INSERT: + text.Append("+").Append(encodeURI(aDiff.text)).Append("\t"); + break; + case Operation.DELETE: + text.Append("-").Append(aDiff.text.Length).Append("\t"); + break; + case Operation.EQUAL: + text.Append("=").Append(aDiff.text.Length).Append("\t"); + break; + } + } + + string delta = text.ToString(); + if (delta.Length != 0) + { + // Strip off trailing tab character. + delta = delta.Substring(0, delta.Length - 1); + } + + return delta; + } + + /** + * Given the original text1, and an encoded string which describes the + * operations required to transform text1 into text2, compute the full diff. + * @param text1 Source string for the diff. + * @param delta Delta text. + * @return Array of Diff objects or null if invalid. + * @throws ArgumentException If invalid input. + */ + public List diff_fromDelta(string text1, string delta) + { + List diffs = new List(); + int pointer = 0; // Cursor in text1 + string[] tokens = delta.Split(new string[] {"\t"}, + StringSplitOptions.None); + foreach (string 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). + string param = token.Substring(1); + switch (token[0]) + { + case '+': + // decode would change all "+" to " " + param = param.Replace("+", "%2b"); + + param = HttpUtility.UrlDecode(param); + //} catch (UnsupportedEncodingException e) { + // // Not likely on modern system. + // throw new Error("This system does not support UTF-8.", e); + //} catch (IllegalArgumentException e) { + // // Malformed URI sequence. + // throw new IllegalArgumentException( + // "Illegal escape in diff_fromDelta: " + param, e); + //} + diffs.Add(new Diff(Operation.INSERT, param)); + break; + case '-': + // Fall through. + case '=': + int n; + try + { + n = Convert.ToInt32(param); + } + catch (FormatException e) + { + throw new ArgumentException( + "Invalid number in diff_fromDelta: " + param, e); + } + + if (n < 0) + { + throw new ArgumentException( + "Negative number in diff_fromDelta: " + param); + } + + string text; + try + { + text = text1.Substring(pointer, n); + pointer += n; + } + catch (ArgumentOutOfRangeException e) + { + throw new ArgumentException("Delta length (" + pointer + + ") larger than source text length (" + text1.Length + + ").", e); + } + + if (token[0] == '=') + { + diffs.Add(new Diff(Operation.EQUAL, text)); + } + else + { + diffs.Add(new Diff(Operation.DELETE, text)); + } + + break; + default: + // Anything else is an error. + throw new ArgumentException( + "Invalid diff operation in diff_fromDelta: " + token[0]); + } + } + + if (pointer != text1.Length) + { + throw new ArgumentException("Delta length (" + pointer + + ") smaller than source text length (" + text1.Length + ")."); + } + + return diffs; + } + + + // MATCH FUNCTIONS + + + /** + * Locate the best instance of 'pattern' in 'text' near 'loc'. + * Returns -1 if no match found. + * @param text The text to search. + * @param pattern The pattern to search for. + * @param loc The location to search around. + * @return Best match index or -1. + */ + public int match_main(string text, string pattern, int loc) + { + // Check for null inputs not needed since null can't be passed in C#. + + loc = Math.Max(0, Math.Min(loc, text.Length)); + if (text == pattern) + { + // Shortcut (potentially not guaranteed by the algorithm) + return 0; + } + else if (text.Length == 0) + { + // Nothing to match. + return -1; + } + else if (loc + pattern.Length <= text.Length + && text.Substring(loc, pattern.Length) == pattern) + { + // Perfect match at the perfect spot! (Includes case of null pattern) + return loc; + } + else + { + // Do a fuzzy compare. + return match_bitap(text, pattern, loc); + } + } + + /** + * Locate the best instance of 'pattern' in 'text' near 'loc' using the + * Bitap algorithm. Returns -1 if no match found. + * @param text The text to search. + * @param pattern The pattern to search for. + * @param loc The location to search around. + * @return Best match index or -1. + */ + protected int match_bitap(string text, string pattern, int loc) + { + // assert (Match_MaxBits == 0 || pattern.Length <= Match_MaxBits) + // : "Pattern too long for this application."; + + // Initialise the alphabet. + Dictionary s = match_alphabet(pattern); + + // Highest score beyond which we give up. + double score_threshold = Match_Threshold; + // Is there a nearby exact match? (speedup) + int best_loc = text.IndexOf(pattern, loc, StringComparison.Ordinal); + if (best_loc != -1) + { + score_threshold = Math.Min(match_bitapScore(0, best_loc, loc, + pattern), score_threshold); + // What about in the other direction? (speedup) + best_loc = text.LastIndexOf(pattern, + Math.Min(loc + pattern.Length, text.Length), + StringComparison.Ordinal); + if (best_loc != -1) + { + score_threshold = Math.Min(match_bitapScore(0, best_loc, loc, + pattern), score_threshold); + } + } + + // Initialise the bit arrays. + int matchmask = 1 << (pattern.Length - 1); + best_loc = -1; + + int bin_min, bin_mid; + int bin_max = pattern.Length + text.Length; + // Empty initialization added to appease C# compiler. + int[] last_rd = new int[0]; + for (int d = 0; d < pattern.Length; d++) + { + // Scan for the best match; each iteration allows for one more error. + // Run a binary search to determine how far from 'loc' we can stray at + // this error level. + bin_min = 0; + bin_mid = bin_max; + while (bin_min < bin_mid) + { + if (match_bitapScore(d, loc + bin_mid, loc, pattern) + <= score_threshold) + { + bin_min = bin_mid; + } + else + { + bin_max = bin_mid; + } + + bin_mid = (bin_max - bin_min) / 2 + bin_min; + } + + // Use the result from this iteration as the maximum for the next. + bin_max = bin_mid; + int start = Math.Max(1, loc - bin_mid + 1); + int finish = Math.Min(loc + bin_mid, text.Length) + pattern.Length; + + int[] rd = new int[finish + 2]; + rd[finish + 1] = (1 << d) - 1; + for (int j = finish; j >= start; j--) + { + int charMatch; + if (text.Length <= j - 1 || !s.ContainsKey(text[j - 1])) + { + // Out of range. + charMatch = 0; + } + else + { + charMatch = s[text[j - 1]]; + } + + if (d == 0) + { + // First pass: exact match. + rd[j] = ((rd[j + 1] << 1) | 1) & charMatch; + } + else + { + // Subsequent passes: fuzzy match. + rd[j] = ((rd[j + 1] << 1) | 1) & charMatch + | (((last_rd[j + 1] | last_rd[j]) << 1) | 1) | last_rd[j + 1]; + } + + if ((rd[j] & matchmask) != 0) + { + double score = match_bitapScore(d, j - 1, loc, pattern); + // This match will almost certainly be better than any existing + // match. But check anyway. + if (score <= score_threshold) + { + // Told you so. + score_threshold = score; + best_loc = j - 1; + if (best_loc > loc) + { + // When passing loc, don't exceed our current distance from loc. + start = Math.Max(1, 2 * loc - best_loc); + } + else + { + // Already passed loc, downhill from here on in. + break; + } + } + } + } + + if (match_bitapScore(d + 1, loc, loc, pattern) > score_threshold) + { + // No hope for a (better) match at greater error levels. + break; + } + + last_rd = rd; + } + + return best_loc; + } + + /** + * Compute and return the score for a match with e errors and x location. + * @param e Number of errors in match. + * @param x Location of match. + * @param loc Expected location of match. + * @param pattern Pattern being sought. + * @return Overall score for match (0.0 = good, 1.0 = bad). + */ + private double match_bitapScore(int e, int x, int loc, string pattern) + { + float accuracy = (float) e / pattern.Length; + int proximity = Math.Abs(loc - x); + if (Match_Distance == 0) + { + // Dodge divide by zero error. + return proximity == 0 ? accuracy : 1.0; + } + + return accuracy + (proximity / (float) Match_Distance); + } + + /** + * Initialise the alphabet for the Bitap algorithm. + * @param pattern The text to encode. + * @return Hash of character locations. + */ + protected Dictionary match_alphabet(string pattern) + { + Dictionary s = new Dictionary(); + char[] char_pattern = pattern.ToCharArray(); + foreach (char c in char_pattern) + { + if (!s.ContainsKey(c)) + { + s.Add(c, 0); + } + } + + int i = 0; + foreach (char c in char_pattern) + { + int value = s[c] | (1 << (pattern.Length - i - 1)); + s[c] = value; + i++; + } + + return s; + } + + + // 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. + * A set of diffs will be computed. + * @param text1 Old text. + * @param text2 New text. + * @return List of Patch objects. + */ + public List patch_make(string text1, string text2) + { + // Check for null inputs not needed since null can't be passed in C#. + // No diffs provided, compute our own. + List diffs = diff_main(text1, text2, true); + if (diffs.Count > 2) + { + diff_cleanupSemantic(diffs); + diff_cleanupEfficiency(diffs); + } + + return patch_make(text1, diffs); + } + + /** + * Compute a list of patches to turn text1 into text2. + * text1 will be derived from the provided diffs. + * @param diffs Array of Diff objects for text1 to text2. + * @return List of Patch objects. + */ + public List patch_make(List diffs) + { + // Check for null inputs not needed since null can't be passed in C#. + // No origin string provided, compute our own. + string text1 = diff_text1(diffs); + return patch_make(text1, diffs); + } + + /** + * Compute a list of patches to turn text1 into text2. + * text2 is ignored, diffs are the delta between text1 and text2. + * @param text1 Old text + * @param text2 Ignored. + * @param diffs Array of Diff objects for text1 to text2. + * @return List of Patch objects. + * @deprecated Prefer patch_make(string text1, List diffs). + */ + public List patch_make(string text1, string text2, + List diffs) + { + return patch_make(text1, diffs); + } + + /** + * 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 objects for text1 to text2. + * @return List of Patch objects. + */ + public List patch_make(string text1, List diffs) + { + // Check for null inputs not needed since null can't be passed in C#. + List patches = new List(); + 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) + { + patch_addContext(patch, prepatch_text); + patches.Add(patch); + patch = new Patch(); + // 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_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; + } + + /** + * 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_deepCopy(List patches) + { + List patchesCopy = new List(); + foreach (Patch aPatch in patches) + { + Patch patchCopy = new Patch(); + foreach (Diff aDiff in aPatch.diffs) + { + Diff 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; + } + + /** + * Merge a set of patches onto the text. Return a patched text, as well + * as an array of true/false values indicating which patches were applied. + * @param patches Array of Patch objects + * @param text Old text. + * @return Two element Object array, containing the new text and an array of + * bool values. + */ + public Object[] patch_apply(List patches, string text) + { + if (patches.Count == 0) + { + return new Object[] {text, new bool[0]}; + } + + // Deep copy the patches so that no changes are made to originals. + patches = patch_deepCopy(patches); + + string nullPadding = this.patch_addPadding(patches); + text = nullPadding + text + nullPadding; + patch_splitMax(patches); + + int x = 0; + // delta keeps track of the offset between the expected and actual + // location of the previous patch. If there are patches expected at + // positions 10 and 20, but the first patch was found at 12, delta is 2 + // and the second patch has an effective expected position of 22. + int delta = 0; + bool[] results = new bool[patches.Count]; + foreach (Patch aPatch in patches) + { + int expected_loc = aPatch.start2 + delta; + string text1 = diff_text1(aPatch.diffs); + int start_loc; + int end_loc = -1; + if (text1.Length > this.Match_MaxBits) + { + // patch_splitMax will only provide an oversized pattern + // in the case of a monster delete. + start_loc = match_main(text, + text1.Substring(0, this.Match_MaxBits), expected_loc); + if (start_loc != -1) + { + end_loc = match_main(text, + text1.Substring(text1.Length - this.Match_MaxBits), + expected_loc + text1.Length - this.Match_MaxBits); + if (end_loc == -1 || start_loc >= end_loc) + { + // Can't find valid trailing context. Drop this patch. + start_loc = -1; + } + } + } + else + { + start_loc = this.match_main(text, text1, expected_loc); + } + + if (start_loc == -1) + { + // No match found. :( + results[x] = false; + // Subtract the delta for this failed patch from subsequent patches. + delta -= aPatch.length2 - aPatch.length1; + } + else + { + // Found a match. :) + results[x] = true; + delta = start_loc - expected_loc; + string text2; + if (end_loc == -1) + { + text2 = text.JavaSubstring(start_loc, + Math.Min(start_loc + text1.Length, text.Length)); + } + else + { + text2 = text.JavaSubstring(start_loc, + Math.Min(end_loc + this.Match_MaxBits, text.Length)); + } + + if (text1 == text2) + { + // Perfect match, just shove the Replacement text in. + text = text.Substring(0, start_loc) + diff_text2(aPatch.diffs) + + text.Substring(start_loc + text1.Length); + } + else + { + // Imperfect match. Run a diff to get a framework of equivalent + // indices. + List diffs = diff_main(text1, text2, false); + if (text1.Length > this.Match_MaxBits + && this.diff_levenshtein(diffs) / (float) text1.Length + > this.Patch_DeleteThreshold) + { + // The end points match, but the content is unacceptably bad. + results[x] = false; + } + else + { + diff_cleanupSemanticLossless(diffs); + int index1 = 0; + foreach (Diff aDiff in aPatch.diffs) + { + if (aDiff.operation != Operation.EQUAL) + { + int index2 = diff_xIndex(diffs, index1); + if (aDiff.operation == Operation.INSERT) + { + // Insertion + text = text.Insert(start_loc + index2, aDiff.text); + } + else if (aDiff.operation == Operation.DELETE) + { + // Deletion + text = text.Remove(start_loc + index2, diff_xIndex(diffs, + index1 + aDiff.text.Length) - index2); + } + } + + if (aDiff.operation != Operation.DELETE) + { + index1 += aDiff.text.Length; + } + } + } + } + } + + x++; + } + + // Strip the padding off. + text = text.Substring(nullPadding.Length, text.Length + - 2 * nullPadding.Length); + return new Object[] {text, results}; + } + + /** + * Add some padding on text start and end so that edges can match something. + * Intended to be called only from within patch_apply. + * @param patches Array of Patch objects. + * @return The padding string added to each side. + */ + public string patch_addPadding(List 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 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; + } + + /** + * 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 patches) + { + short patch_size = this.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 = this.diff_text2(patch.diffs); + precontext = precontext.Substring(Math.Max(0, + precontext.Length - this.Patch_Margin)); + + string postcontext = null; + // 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); + } + } + } + } + + /** + * Take a list of patches and return a textual representation. + * @param patches List of Patch objects. + * @return Text representation of patches. + */ + public string patch_toText(List patches) + { + StringBuilder text = new StringBuilder(); + foreach (Patch aPatch in patches) + { + text.Append(aPatch); + } + + return text.ToString(); + } + + /** + * 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_fromText(string textline) + { + List patches = new List(); + 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 = HttpUtility.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; + } + + /** + * Encodes a string with URI-style % escaping. + * Compatible with JavaScript's encodeURI function. + * + * @param str The string to encode. + * @return The encoded string. + */ + public static string encodeURI(string str) + { + // C# is overzealous in the replacements. Walk back on a few. + return new StringBuilder(HttpUtility.UrlEncode(str)) + .Replace('+', ' ').Replace("%20", " ").Replace("%21", "!") + .Replace("%2a", "*").Replace("%27", "'").Replace("%28", "(") + .Replace("%29", ")").Replace("%3b", ";").Replace("%2f", "/") + .Replace("%3f", "?").Replace("%3a", ":").Replace("%40", "@") + .Replace("%26", "&").Replace("%3d", "=").Replace("%2b", "+") + .Replace("%24", "$").Replace("%2c", ",").Replace("%23", "#") + .Replace("%7e", "~") + .ToString(); + } + } +} diff --git a/csharp/DiffMatchPatch/DiffMatchPatch.csproj b/csharp/DiffMatchPatch/DiffMatchPatch.csproj new file mode 100644 index 0000000..26d875b --- /dev/null +++ b/csharp/DiffMatchPatch/DiffMatchPatch.csproj @@ -0,0 +1,6 @@ + + + netstandard2.0 + Google.DiffMatchPatch + + \ No newline at end of file diff --git a/csharp/tests/DiffMatchPatchTest.cs b/csharp/tests/DiffMatchPatchTest.cs deleted file mode 100644 index 5a226ee..0000000 --- a/csharp/tests/DiffMatchPatchTest.cs +++ /dev/null @@ -1,1230 +0,0 @@ -/* - * Diff Match and Patch -- Test Harness - * Copyright 2018 The diff-match-patch Authors. - * https://github.com/google/diff-match-patch - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/* - * To compile with Mono: - * mcs DiffMatchPatchTest.cs ../DiffMatchPatch.cs - * To run with Mono: - * mono DiffMatchPatchTest.exe -*/ - -using DiffMatchPatch; -using System.Collections.Generic; -using System; -using System.Text; - -public class diff_match_patchTest : diff_match_patch { - public void diff_commonPrefixTest() { - // Detect any common suffix. - assertEquals("diff_commonPrefix: Null case.", 0, this.diff_commonPrefix("abc", "xyz")); - - assertEquals("diff_commonPrefix: Non-null case.", 4, this.diff_commonPrefix("1234abcdef", "1234xyz")); - - assertEquals("diff_commonPrefix: Whole case.", 4, this.diff_commonPrefix("1234", "1234xyz")); - } - - public void diff_commonSuffixTest() { - // Detect any common suffix. - assertEquals("diff_commonSuffix: Null case.", 0, this.diff_commonSuffix("abc", "xyz")); - - assertEquals("diff_commonSuffix: Non-null case.", 4, this.diff_commonSuffix("abcdef1234", "xyz1234")); - - assertEquals("diff_commonSuffix: Whole case.", 4, this.diff_commonSuffix("1234", "xyz1234")); - } - - public void diff_commonOverlapTest() { - // Detect any suffix/prefix overlap. - assertEquals("diff_commonOverlap: Null case.", 0, this.diff_commonOverlap("", "abcd")); - - assertEquals("diff_commonOverlap: Whole case.", 3, this.diff_commonOverlap("abc", "abcd")); - - assertEquals("diff_commonOverlap: No overlap.", 0, this.diff_commonOverlap("123456", "abcd")); - - assertEquals("diff_commonOverlap: Overlap.", 3, this.diff_commonOverlap("123456xxx", "xxxabcd")); - - // Some overly clever languages (C#) may treat ligatures as equal to their - // component letters. E.g. U+FB01 == 'fi' - assertEquals("diff_commonOverlap: Unicode.", 0, this.diff_commonOverlap("fi", "\ufb01i")); - } - - public void diff_halfmatchTest() { - this.Diff_Timeout = 1; - assertNull("diff_halfMatch: No match #1.", this.diff_halfMatch("1234567890", "abcdef")); - - assertNull("diff_halfMatch: No match #2.", this.diff_halfMatch("12345", "23")); - - assertEquals("diff_halfMatch: Single Match #1.", new string[] { "12", "90", "a", "z", "345678" }, this.diff_halfMatch("1234567890", "a345678z")); - - assertEquals("diff_halfMatch: Single Match #2.", new string[] { "a", "z", "12", "90", "345678" }, this.diff_halfMatch("a345678z", "1234567890")); - - assertEquals("diff_halfMatch: Single Match #3.", new string[] { "abc", "z", "1234", "0", "56789" }, this.diff_halfMatch("abc56789z", "1234567890")); - - assertEquals("diff_halfMatch: Single Match #4.", new string[] { "a", "xyz", "1", "7890", "23456" }, this.diff_halfMatch("a23456xyz", "1234567890")); - - assertEquals("diff_halfMatch: Multiple Matches #1.", new string[] { "12123", "123121", "a", "z", "1234123451234" }, this.diff_halfMatch("121231234123451234123121", "a1234123451234z")); - - assertEquals("diff_halfMatch: Multiple Matches #2.", new string[] { "", "-=-=-=-=-=", "x", "", "x-=-=-=-=-=-=-=" }, this.diff_halfMatch("x-=-=-=-=-=-=-=-=-=-=-=-=", "xx-=-=-=-=-=-=-=")); - - assertEquals("diff_halfMatch: Multiple Matches #3.", new string[] { "-=-=-=-=-=", "", "", "y", "-=-=-=-=-=-=-=y" }, this.diff_halfMatch("-=-=-=-=-=-=-=-=-=-=-=-=y", "-=-=-=-=-=-=-=yy")); - - // Optimal diff would be -q+x=H-i+e=lloHe+Hu=llo-Hew+y not -qHillo+x=HelloHe-w+Hulloy - assertEquals("diff_halfMatch: Non-optimal halfmatch.", new string[] { "qHillo", "w", "x", "Hulloy", "HelloHe" }, this.diff_halfMatch("qHilloHelloHew", "xHelloHeHulloy")); - - this.Diff_Timeout = 0; - assertNull("diff_halfMatch: Optimal no halfmatch.", this.diff_halfMatch("qHilloHelloHew", "xHelloHeHulloy")); - } - - public void diff_linesToCharsTest() { - // Convert lines down to characters. - List tmpVector = new List(); - tmpVector.Add(""); - tmpVector.Add("alpha\n"); - tmpVector.Add("beta\n"); - Object[] result = this.diff_linesToChars("alpha\nbeta\nalpha\n", "beta\nalpha\nbeta\n"); - assertEquals("diff_linesToChars: Shared lines #1.", "\u0001\u0002\u0001", (string)result[0]); - assertEquals("diff_linesToChars: Shared lines #2.", "\u0002\u0001\u0002", (string)result[1]); - assertEquals("diff_linesToChars: Shared lines #3.", tmpVector, (List)result[2]); - - tmpVector.Clear(); - tmpVector.Add(""); - tmpVector.Add("alpha\r\n"); - tmpVector.Add("beta\r\n"); - tmpVector.Add("\r\n"); - result = this.diff_linesToChars("", "alpha\r\nbeta\r\n\r\n\r\n"); - assertEquals("diff_linesToChars: Empty string and blank lines #1.", "", (string)result[0]); - assertEquals("diff_linesToChars: Empty string and blank lines #2.", "\u0001\u0002\u0003\u0003", (string)result[1]); - assertEquals("diff_linesToChars: Empty string and blank lines #3.", tmpVector, (List)result[2]); - - tmpVector.Clear(); - tmpVector.Add(""); - tmpVector.Add("a"); - tmpVector.Add("b"); - result = this.diff_linesToChars("a", "b"); - assertEquals("diff_linesToChars: No linebreaks #1.", "\u0001", (string)result[0]); - assertEquals("diff_linesToChars: No linebreaks #2.", "\u0002", (string)result[1]); - assertEquals("diff_linesToChars: No linebreaks #3.", tmpVector, (List)result[2]); - - // More than 256 to reveal any 8-bit limitations. - int n = 300; - tmpVector.Clear(); - StringBuilder lineList = new StringBuilder(); - StringBuilder charList = new StringBuilder(); - for (int i = 1; i < n + 1; i++) { - tmpVector.Add(i + "\n"); - lineList.Append(i + "\n"); - charList.Append(Convert.ToChar(i)); - } - assertEquals("Test initialization fail #1.", n, tmpVector.Count); - string lines = lineList.ToString(); - string chars = charList.ToString(); - assertEquals("Test initialization fail #2.", n, chars.Length); - tmpVector.Insert(0, ""); - result = this.diff_linesToChars(lines, ""); - assertEquals("diff_linesToChars: More than 256 #1.", chars, (string)result[0]); - assertEquals("diff_linesToChars: More than 256 #2.", "", (string)result[1]); - assertEquals("diff_linesToChars: More than 256 #3.", tmpVector, (List)result[2]); - } - - public void diff_charsToLinesTest() { - // First check that Diff equality works. - assertTrue("diff_charsToLines: Equality #1.", new Diff(Operation.EQUAL, "a").Equals(new Diff(Operation.EQUAL, "a"))); - - assertEquals("diff_charsToLines: Equality #2.", new Diff(Operation.EQUAL, "a"), new Diff(Operation.EQUAL, "a")); - - // Convert chars up to lines. - List diffs = new List { - new Diff(Operation.EQUAL, "\u0001\u0002\u0001"), - new Diff(Operation.INSERT, "\u0002\u0001\u0002")}; - List tmpVector = new List(); - tmpVector.Add(""); - tmpVector.Add("alpha\n"); - tmpVector.Add("beta\n"); - this.diff_charsToLines(diffs, tmpVector); - assertEquals("diff_charsToLines: Shared lines.", new List { - new Diff(Operation.EQUAL, "alpha\nbeta\nalpha\n"), - new Diff(Operation.INSERT, "beta\nalpha\nbeta\n")}, diffs); - - // More than 256 to reveal any 8-bit limitations. - int n = 300; - tmpVector.Clear(); - StringBuilder lineList = new StringBuilder(); - StringBuilder charList = new StringBuilder(); - for (int i = 1; i < n + 1; i++) { - tmpVector.Add(i + "\n"); - lineList.Append(i + "\n"); - charList.Append(Convert.ToChar (i)); - } - assertEquals("Test initialization fail #3.", n, tmpVector.Count); - string lines = lineList.ToString(); - string chars = charList.ToString(); - assertEquals("Test initialization fail #4.", n, chars.Length); - tmpVector.Insert(0, ""); - diffs = new List {new Diff(Operation.DELETE, chars)}; - this.diff_charsToLines(diffs, tmpVector); - assertEquals("diff_charsToLines: More than 256.", new List - {new Diff(Operation.DELETE, lines)}, diffs); - - // More than 65536 to verify any 16-bit limitation. - lineList = new StringBuilder(); - for (int i = 0; i < 66000; i++) { - lineList.Append(i + "\n"); - } - chars = lineList.ToString(); - Object[] result = this.diff_linesToChars(chars, ""); - diffs = new List {new Diff(Operation.INSERT, (string)result[0])}; - this.diff_charsToLines(diffs, (List)result[2]); - assertEquals("diff_charsToLines: More than 65536.", chars, diffs[0].text); - } - - public void diff_cleanupMergeTest() { - // Cleanup a messy diff. - // Null case. - List diffs = new List(); - assertEquals("diff_cleanupMerge: Null case.", new List(), diffs); - - diffs = new List {new Diff(Operation.EQUAL, "a"), new Diff(Operation.DELETE, "b"), new Diff(Operation.INSERT, "c")}; - this.diff_cleanupMerge(diffs); - assertEquals("diff_cleanupMerge: No change case.", new List {new Diff(Operation.EQUAL, "a"), new Diff(Operation.DELETE, "b"), new Diff(Operation.INSERT, "c")}, diffs); - - diffs = new List {new Diff(Operation.EQUAL, "a"), new Diff(Operation.EQUAL, "b"), new Diff(Operation.EQUAL, "c")}; - this.diff_cleanupMerge(diffs); - assertEquals("diff_cleanupMerge: Merge equalities.", new List {new Diff(Operation.EQUAL, "abc")}, diffs); - - diffs = new List {new Diff(Operation.DELETE, "a"), new Diff(Operation.DELETE, "b"), new Diff(Operation.DELETE, "c")}; - this.diff_cleanupMerge(diffs); - assertEquals("diff_cleanupMerge: Merge deletions.", new List {new Diff(Operation.DELETE, "abc")}, diffs); - - diffs = new List {new Diff(Operation.INSERT, "a"), new Diff(Operation.INSERT, "b"), new Diff(Operation.INSERT, "c")}; - this.diff_cleanupMerge(diffs); - assertEquals("diff_cleanupMerge: Merge insertions.", new List {new Diff(Operation.INSERT, "abc")}, diffs); - - diffs = new List {new Diff(Operation.DELETE, "a"), new Diff(Operation.INSERT, "b"), new Diff(Operation.DELETE, "c"), new Diff(Operation.INSERT, "d"), new Diff(Operation.EQUAL, "e"), new Diff(Operation.EQUAL, "f")}; - this.diff_cleanupMerge(diffs); - assertEquals("diff_cleanupMerge: Merge interweave.", new List {new Diff(Operation.DELETE, "ac"), new Diff(Operation.INSERT, "bd"), new Diff(Operation.EQUAL, "ef")}, diffs); - - diffs = new List {new Diff(Operation.DELETE, "a"), new Diff(Operation.INSERT, "abc"), new Diff(Operation.DELETE, "dc")}; - this.diff_cleanupMerge(diffs); - assertEquals("diff_cleanupMerge: Prefix and suffix detection.", new List {new Diff(Operation.EQUAL, "a"), new Diff(Operation.DELETE, "d"), new Diff(Operation.INSERT, "b"), new Diff(Operation.EQUAL, "c")}, diffs); - - diffs = new List {new Diff(Operation.EQUAL, "x"), new Diff(Operation.DELETE, "a"), new Diff(Operation.INSERT, "abc"), new Diff(Operation.DELETE, "dc"), new Diff(Operation.EQUAL, "y")}; - this.diff_cleanupMerge(diffs); - assertEquals("diff_cleanupMerge: Prefix and suffix detection with equalities.", new List {new Diff(Operation.EQUAL, "xa"), new Diff(Operation.DELETE, "d"), new Diff(Operation.INSERT, "b"), new Diff(Operation.EQUAL, "cy")}, diffs); - - diffs = new List {new Diff(Operation.EQUAL, "a"), new Diff(Operation.INSERT, "ba"), new Diff(Operation.EQUAL, "c")}; - this.diff_cleanupMerge(diffs); - assertEquals("diff_cleanupMerge: Slide edit left.", new List {new Diff(Operation.INSERT, "ab"), new Diff(Operation.EQUAL, "ac")}, diffs); - - diffs = new List {new Diff(Operation.EQUAL, "c"), new Diff(Operation.INSERT, "ab"), new Diff(Operation.EQUAL, "a")}; - this.diff_cleanupMerge(diffs); - assertEquals("diff_cleanupMerge: Slide edit right.", new List {new Diff(Operation.EQUAL, "ca"), new Diff(Operation.INSERT, "ba")}, diffs); - - diffs = new List {new Diff(Operation.EQUAL, "a"), new Diff(Operation.DELETE, "b"), new Diff(Operation.EQUAL, "c"), new Diff(Operation.DELETE, "ac"), new Diff(Operation.EQUAL, "x")}; - this.diff_cleanupMerge(diffs); - assertEquals("diff_cleanupMerge: Slide edit left recursive.", new List {new Diff(Operation.DELETE, "abc"), new Diff(Operation.EQUAL, "acx")}, diffs); - - diffs = new List {new Diff(Operation.EQUAL, "x"), new Diff(Operation.DELETE, "ca"), new Diff(Operation.EQUAL, "c"), new Diff(Operation.DELETE, "b"), new Diff(Operation.EQUAL, "a")}; - this.diff_cleanupMerge(diffs); - assertEquals("diff_cleanupMerge: Slide edit right recursive.", new List {new Diff(Operation.EQUAL, "xca"), new Diff(Operation.DELETE, "cba")}, diffs); - - diffs = new List {new Diff(Operation.DELETE, "b"), new Diff(Operation.INSERT, "ab"), new Diff(Operation.EQUAL, "c")}; - this.diff_cleanupMerge(diffs); - assertEquals("diff_cleanupMerge: Empty merge.", new List {new Diff(Operation.INSERT, "a"), new Diff(Operation.EQUAL, "bc")}, diffs); - - diffs = new List {new Diff(Operation.EQUAL, ""), new Diff(Operation.INSERT, "a"), new Diff(Operation.EQUAL, "b")}; - this.diff_cleanupMerge(diffs); - assertEquals("diff_cleanupMerge: Empty equality.", new List {new Diff(Operation.INSERT, "a"), new Diff(Operation.EQUAL, "b")}, diffs); - } - - public void diff_cleanupSemanticLosslessTest() { - // Slide diffs to match logical boundaries. - List diffs = new List(); - this.diff_cleanupSemanticLossless(diffs); - assertEquals("diff_cleanupSemanticLossless: Null case.", new List(), diffs); - - diffs = new List { - new Diff(Operation.EQUAL, "AAA\r\n\r\nBBB"), - new Diff(Operation.INSERT, "\r\nDDD\r\n\r\nBBB"), - new Diff(Operation.EQUAL, "\r\nEEE") - }; - this.diff_cleanupSemanticLossless(diffs); - assertEquals("diff_cleanupSemanticLossless: Blank lines.", new List { - new Diff(Operation.EQUAL, "AAA\r\n\r\n"), - new Diff(Operation.INSERT, "BBB\r\nDDD\r\n\r\n"), - new Diff(Operation.EQUAL, "BBB\r\nEEE")}, diffs); - - diffs = new List { - new Diff(Operation.EQUAL, "AAA\r\nBBB"), - new Diff(Operation.INSERT, " DDD\r\nBBB"), - new Diff(Operation.EQUAL, " EEE")}; - this.diff_cleanupSemanticLossless(diffs); - assertEquals("diff_cleanupSemanticLossless: Line boundaries.", new List { - new Diff(Operation.EQUAL, "AAA\r\n"), - new Diff(Operation.INSERT, "BBB DDD\r\n"), - new Diff(Operation.EQUAL, "BBB EEE")}, diffs); - - diffs = new List { - new Diff(Operation.EQUAL, "The c"), - new Diff(Operation.INSERT, "ow and the c"), - new Diff(Operation.EQUAL, "at.")}; - this.diff_cleanupSemanticLossless(diffs); - assertEquals("diff_cleanupSemanticLossless: Word boundaries.", new List { - new Diff(Operation.EQUAL, "The "), - new Diff(Operation.INSERT, "cow and the "), - new Diff(Operation.EQUAL, "cat.")}, diffs); - - diffs = new List { - new Diff(Operation.EQUAL, "The-c"), - new Diff(Operation.INSERT, "ow-and-the-c"), - new Diff(Operation.EQUAL, "at.")}; - this.diff_cleanupSemanticLossless(diffs); - assertEquals("diff_cleanupSemanticLossless: Alphanumeric boundaries.", new List { - new Diff(Operation.EQUAL, "The-"), - new Diff(Operation.INSERT, "cow-and-the-"), - new Diff(Operation.EQUAL, "cat.")}, diffs); - - diffs = new List { - new Diff(Operation.EQUAL, "a"), - new Diff(Operation.DELETE, "a"), - new Diff(Operation.EQUAL, "ax")}; - this.diff_cleanupSemanticLossless(diffs); - assertEquals("diff_cleanupSemanticLossless: Hitting the start.", new List { - new Diff(Operation.DELETE, "a"), - new Diff(Operation.EQUAL, "aax")}, diffs); - - diffs = new List { - new Diff(Operation.EQUAL, "xa"), - new Diff(Operation.DELETE, "a"), - new Diff(Operation.EQUAL, "a")}; - this.diff_cleanupSemanticLossless(diffs); - assertEquals("diff_cleanupSemanticLossless: Hitting the end.", new List { - new Diff(Operation.EQUAL, "xaa"), - new Diff(Operation.DELETE, "a")}, diffs); - - diffs = new List { - new Diff(Operation.EQUAL, "The xxx. The "), - new Diff(Operation.INSERT, "zzz. The "), - new Diff(Operation.EQUAL, "yyy.")}; - this.diff_cleanupSemanticLossless(diffs); - assertEquals("diff_cleanupSemanticLossless: Sentence boundaries.", new List { - new Diff(Operation.EQUAL, "The xxx."), - new Diff(Operation.INSERT, " The zzz."), - new Diff(Operation.EQUAL, " The yyy.")}, diffs); - } - - public void diff_cleanupSemanticTest() { - // Cleanup semantically trivial equalities. - // Null case. - List diffs = new List(); - this.diff_cleanupSemantic(diffs); - assertEquals("diff_cleanupSemantic: Null case.", new List(), diffs); - - diffs = new List { - new Diff(Operation.DELETE, "ab"), - new Diff(Operation.INSERT, "cd"), - new Diff(Operation.EQUAL, "12"), - new Diff(Operation.DELETE, "e")}; - this.diff_cleanupSemantic(diffs); - assertEquals("diff_cleanupSemantic: No elimination #1.", new List { - new Diff(Operation.DELETE, "ab"), - new Diff(Operation.INSERT, "cd"), - new Diff(Operation.EQUAL, "12"), - new Diff(Operation.DELETE, "e")}, diffs); - - diffs = new List { - new Diff(Operation.DELETE, "abc"), - new Diff(Operation.INSERT, "ABC"), - new Diff(Operation.EQUAL, "1234"), - new Diff(Operation.DELETE, "wxyz")}; - this.diff_cleanupSemantic(diffs); - assertEquals("diff_cleanupSemantic: No elimination #2.", new List { - new Diff(Operation.DELETE, "abc"), - new Diff(Operation.INSERT, "ABC"), - new Diff(Operation.EQUAL, "1234"), - new Diff(Operation.DELETE, "wxyz")}, diffs); - - diffs = new List { - new Diff(Operation.DELETE, "a"), - new Diff(Operation.EQUAL, "b"), - new Diff(Operation.DELETE, "c")}; - this.diff_cleanupSemantic(diffs); - assertEquals("diff_cleanupSemantic: Simple elimination.", new List { - new Diff(Operation.DELETE, "abc"), - new Diff(Operation.INSERT, "b")}, diffs); - - diffs = new List { - new Diff(Operation.DELETE, "ab"), - new Diff(Operation.EQUAL, "cd"), - new Diff(Operation.DELETE, "e"), - new Diff(Operation.EQUAL, "f"), - new Diff(Operation.INSERT, "g")}; - this.diff_cleanupSemantic(diffs); - assertEquals("diff_cleanupSemantic: Backpass elimination.", new List { - new Diff(Operation.DELETE, "abcdef"), - new Diff(Operation.INSERT, "cdfg")}, diffs); - - diffs = new List { - new Diff(Operation.INSERT, "1"), - new Diff(Operation.EQUAL, "A"), - new Diff(Operation.DELETE, "B"), - new Diff(Operation.INSERT, "2"), - new Diff(Operation.EQUAL, "_"), - new Diff(Operation.INSERT, "1"), - new Diff(Operation.EQUAL, "A"), - new Diff(Operation.DELETE, "B"), - new Diff(Operation.INSERT, "2")}; - this.diff_cleanupSemantic(diffs); - assertEquals("diff_cleanupSemantic: Multiple elimination.", new List { - new Diff(Operation.DELETE, "AB_AB"), - new Diff(Operation.INSERT, "1A2_1A2")}, diffs); - - diffs = new List { - new Diff(Operation.EQUAL, "The c"), - new Diff(Operation.DELETE, "ow and the c"), - new Diff(Operation.EQUAL, "at.")}; - this.diff_cleanupSemantic(diffs); - assertEquals("diff_cleanupSemantic: Word boundaries.", new List { - new Diff(Operation.EQUAL, "The "), - new Diff(Operation.DELETE, "cow and the "), - new Diff(Operation.EQUAL, "cat.")}, diffs); - - diffs = new List { - new Diff(Operation.DELETE, "abcxx"), - new Diff(Operation.INSERT, "xxdef")}; - this.diff_cleanupSemantic(diffs); - assertEquals("diff_cleanupSemantic: No overlap elimination.", new List { - new Diff(Operation.DELETE, "abcxx"), - new Diff(Operation.INSERT, "xxdef")}, diffs); - - diffs = new List { - new Diff(Operation.DELETE, "abcxxx"), - new Diff(Operation.INSERT, "xxxdef")}; - this.diff_cleanupSemantic(diffs); - assertEquals("diff_cleanupSemantic: Overlap elimination.", new List { - new Diff(Operation.DELETE, "abc"), - new Diff(Operation.EQUAL, "xxx"), - new Diff(Operation.INSERT, "def")}, diffs); - - diffs = new List { - new Diff(Operation.DELETE, "xxxabc"), - new Diff(Operation.INSERT, "defxxx")}; - this.diff_cleanupSemantic(diffs); - assertEquals("diff_cleanupSemantic: Reverse overlap elimination.", new List { - new Diff(Operation.INSERT, "def"), - new Diff(Operation.EQUAL, "xxx"), - new Diff(Operation.DELETE, "abc")}, diffs); - - diffs = new List { - new Diff(Operation.DELETE, "abcd1212"), - new Diff(Operation.INSERT, "1212efghi"), - new Diff(Operation.EQUAL, "----"), - new Diff(Operation.DELETE, "A3"), - new Diff(Operation.INSERT, "3BC")}; - this.diff_cleanupSemantic(diffs); - assertEquals("diff_cleanupSemantic: Two overlap eliminations.", new List { - new Diff(Operation.DELETE, "abcd"), - new Diff(Operation.EQUAL, "1212"), - new Diff(Operation.INSERT, "efghi"), - new Diff(Operation.EQUAL, "----"), - new Diff(Operation.DELETE, "A"), - new Diff(Operation.EQUAL, "3"), - new Diff(Operation.INSERT, "BC")}, diffs); - } - - public void diff_cleanupEfficiencyTest() { - // Cleanup operationally trivial equalities. - this.Diff_EditCost = 4; - List diffs = new List (); - this.diff_cleanupEfficiency(diffs); - assertEquals("diff_cleanupEfficiency: Null case.", new List(), diffs); - - diffs = new List { - new Diff(Operation.DELETE, "ab"), - new Diff(Operation.INSERT, "12"), - new Diff(Operation.EQUAL, "wxyz"), - new Diff(Operation.DELETE, "cd"), - new Diff(Operation.INSERT, "34")}; - this.diff_cleanupEfficiency(diffs); - assertEquals("diff_cleanupEfficiency: No elimination.", new List { - new Diff(Operation.DELETE, "ab"), - new Diff(Operation.INSERT, "12"), - new Diff(Operation.EQUAL, "wxyz"), - new Diff(Operation.DELETE, "cd"), - new Diff(Operation.INSERT, "34")}, diffs); - - diffs = new List { - new Diff(Operation.DELETE, "ab"), - new Diff(Operation.INSERT, "12"), - new Diff(Operation.EQUAL, "xyz"), - new Diff(Operation.DELETE, "cd"), - new Diff(Operation.INSERT, "34")}; - this.diff_cleanupEfficiency(diffs); - assertEquals("diff_cleanupEfficiency: Four-edit elimination.", new List { - new Diff(Operation.DELETE, "abxyzcd"), - new Diff(Operation.INSERT, "12xyz34")}, diffs); - - diffs = new List { - new Diff(Operation.INSERT, "12"), - new Diff(Operation.EQUAL, "x"), - new Diff(Operation.DELETE, "cd"), - new Diff(Operation.INSERT, "34")}; - this.diff_cleanupEfficiency(diffs); - assertEquals("diff_cleanupEfficiency: Three-edit elimination.", new List { - new Diff(Operation.DELETE, "xcd"), - new Diff(Operation.INSERT, "12x34")}, diffs); - - diffs = new List { - new Diff(Operation.DELETE, "ab"), - new Diff(Operation.INSERT, "12"), - new Diff(Operation.EQUAL, "xy"), - new Diff(Operation.INSERT, "34"), - new Diff(Operation.EQUAL, "z"), - new Diff(Operation.DELETE, "cd"), - new Diff(Operation.INSERT, "56")}; - this.diff_cleanupEfficiency(diffs); - assertEquals("diff_cleanupEfficiency: Backpass elimination.", new List { - new Diff(Operation.DELETE, "abxyzcd"), - new Diff(Operation.INSERT, "12xy34z56")}, diffs); - - this.Diff_EditCost = 5; - diffs = new List { - new Diff(Operation.DELETE, "ab"), - new Diff(Operation.INSERT, "12"), - new Diff(Operation.EQUAL, "wxyz"), - new Diff(Operation.DELETE, "cd"), - new Diff(Operation.INSERT, "34")}; - this.diff_cleanupEfficiency(diffs); - assertEquals("diff_cleanupEfficiency: High cost elimination.", new List { - new Diff(Operation.DELETE, "abwxyzcd"), - new Diff(Operation.INSERT, "12wxyz34")}, diffs); - this.Diff_EditCost = 4; - } - - public void diff_prettyHtmlTest() { - // Pretty print. - List diffs = new List { - new Diff(Operation.EQUAL, "a\n"), - new Diff(Operation.DELETE, "b"), - new Diff(Operation.INSERT, "c&d")}; - assertEquals("diff_prettyHtml:", "
<B>b</B>c&d", - this.diff_prettyHtml(diffs)); - } - - public void diff_textTest() { - // Compute the source and destination texts. - List diffs = new List { - 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, " lazy")}; - assertEquals("diff_text1:", "jumps over the lazy", this.diff_text1(diffs)); - - assertEquals("diff_text2:", "jumped over a lazy", this.diff_text2(diffs)); - } - - public void diff_deltaTest() { - // Convert a diff into delta string. - List diffs = new List { - 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, " lazy"), - new Diff(Operation.INSERT, "old dog")}; - string text1 = this.diff_text1(diffs); - assertEquals("diff_text1: Base text.", "jumps over the lazy", text1); - - string delta = this.diff_toDelta(diffs); - assertEquals("diff_toDelta:", "=4\t-1\t+ed\t=6\t-3\t+a\t=5\t+old dog", delta); - - // Convert delta string into a diff. - assertEquals("diff_fromDelta: Normal.", diffs, this.diff_fromDelta(text1, delta)); - - // Generates error (19 < 20). - try { - this.diff_fromDelta(text1 + "x", delta); - assertFail("diff_fromDelta: Too long."); - } catch (ArgumentException) { - // Exception expected. - } - - // Generates error (19 > 18). - try { - this.diff_fromDelta(text1.Substring(1), delta); - assertFail("diff_fromDelta: Too short."); - } catch (ArgumentException) { - // Exception expected. - } - - // Generates error (%c3%xy invalid Unicode). - try { - this.diff_fromDelta("", "+%c3%xy"); - assertFail("diff_fromDelta: Invalid character."); - } catch (ArgumentException) { - // Exception expected. - } - - // Test deltas with special characters. - char zero = (char)0; - char one = (char)1; - char two = (char)2; - diffs = new List { - new Diff(Operation.EQUAL, "\u0680 " + zero + " \t %"), - new Diff(Operation.DELETE, "\u0681 " + one + " \n ^"), - new Diff(Operation.INSERT, "\u0682 " + two + " \\ |")}; - text1 = this.diff_text1(diffs); - assertEquals("diff_text1: Unicode text.", "\u0680 " + zero + " \t %\u0681 " + one + " \n ^", text1); - - delta = this.diff_toDelta(diffs); - // Lowercase, due to UrlEncode uses lower. - assertEquals("diff_toDelta: Unicode.", "=7\t-7\t+%da%82 %02 %5c %7c", delta); - - assertEquals("diff_fromDelta: Unicode.", diffs, this.diff_fromDelta(text1, delta)); - - // Verify pool of unchanged characters. - diffs = new List { - new Diff(Operation.INSERT, "A-Z a-z 0-9 - _ . ! ~ * ' ( ) ; / ? : @ & = + $ , # ")}; - string text2 = this.diff_text2(diffs); - assertEquals("diff_text2: Unchanged characters.", "A-Z a-z 0-9 - _ . ! ~ * \' ( ) ; / ? : @ & = + $ , # ", text2); - - delta = this.diff_toDelta(diffs); - assertEquals("diff_toDelta: Unchanged characters.", "+A-Z a-z 0-9 - _ . ! ~ * \' ( ) ; / ? : @ & = + $ , # ", delta); - - // Convert delta string into a diff. - assertEquals("diff_fromDelta: Unchanged characters.", diffs, this.diff_fromDelta("", delta)); - - // 160 kb string. - string a = "abcdefghij"; - for (int i = 0; i < 14; i++) { - a += a; - } - diffs = new List {new Diff(Operation.INSERT, a)}; - delta = this.diff_toDelta(diffs); - assertEquals("diff_toDelta: 160kb string.", "+" + a, delta); - - // Convert delta string into a diff. - assertEquals("diff_fromDelta: 160kb string.", diffs, this.diff_fromDelta("", delta)); - } - - public void diff_xIndexTest() { - // Translate a location in text1 to text2. - List diffs = new List { - new Diff(Operation.DELETE, "a"), - new Diff(Operation.INSERT, "1234"), - new Diff(Operation.EQUAL, "xyz")}; - assertEquals("diff_xIndex: Translation on equality.", 5, this.diff_xIndex(diffs, 2)); - - diffs = new List { - new Diff(Operation.EQUAL, "a"), - new Diff(Operation.DELETE, "1234"), - new Diff(Operation.EQUAL, "xyz")}; - assertEquals("diff_xIndex: Translation on deletion.", 1, this.diff_xIndex(diffs, 3)); - } - - public void diff_levenshteinTest() { - List diffs = new List { - new Diff(Operation.DELETE, "abc"), - new Diff(Operation.INSERT, "1234"), - new Diff(Operation.EQUAL, "xyz")}; - assertEquals("diff_levenshtein: Levenshtein with trailing equality.", 4, this.diff_levenshtein(diffs)); - - diffs = new List { - new Diff(Operation.EQUAL, "xyz"), - new Diff(Operation.DELETE, "abc"), - new Diff(Operation.INSERT, "1234")}; - assertEquals("diff_levenshtein: Levenshtein with leading equality.", 4, this.diff_levenshtein(diffs)); - - diffs = new List { - new Diff(Operation.DELETE, "abc"), - new Diff(Operation.EQUAL, "xyz"), - new Diff(Operation.INSERT, "1234")}; - assertEquals("diff_levenshtein: Levenshtein with middle equality.", 7, this.diff_levenshtein(diffs)); - } - - public void diff_bisectTest() { - // Normal. - string a = "cat"; - string b = "map"; - // Since the resulting diff hasn't been normalized, it would be ok if - // the insertion and deletion pairs are swapped. - // If the order changes, tweak this test as required. - List diffs = new List {new Diff(Operation.DELETE, "c"), new Diff(Operation.INSERT, "m"), new Diff(Operation.EQUAL, "a"), new Diff(Operation.DELETE, "t"), new Diff(Operation.INSERT, "p")}; - assertEquals("diff_bisect: Normal.", diffs, this.diff_bisect(a, b, DateTime.MaxValue)); - - // Timeout. - diffs = new List {new Diff(Operation.DELETE, "cat"), new Diff(Operation.INSERT, "map")}; - assertEquals("diff_bisect: Timeout.", diffs, this.diff_bisect(a, b, DateTime.MinValue)); - } - - public void diff_mainTest() { - // Perform a trivial diff. - List diffs = new List {}; - assertEquals("diff_main: Null case.", diffs, this.diff_main("", "", false)); - - diffs = new List {new Diff(Operation.EQUAL, "abc")}; - assertEquals("diff_main: Equality.", diffs, this.diff_main("abc", "abc", false)); - - diffs = new List {new Diff(Operation.EQUAL, "ab"), new Diff(Operation.INSERT, "123"), new Diff(Operation.EQUAL, "c")}; - assertEquals("diff_main: Simple insertion.", diffs, this.diff_main("abc", "ab123c", false)); - - diffs = new List {new Diff(Operation.EQUAL, "a"), new Diff(Operation.DELETE, "123"), new Diff(Operation.EQUAL, "bc")}; - assertEquals("diff_main: Simple deletion.", diffs, this.diff_main("a123bc", "abc", false)); - - diffs = new List {new Diff(Operation.EQUAL, "a"), new Diff(Operation.INSERT, "123"), new Diff(Operation.EQUAL, "b"), new Diff(Operation.INSERT, "456"), new Diff(Operation.EQUAL, "c")}; - assertEquals("diff_main: Two insertions.", diffs, this.diff_main("abc", "a123b456c", false)); - - diffs = new List {new Diff(Operation.EQUAL, "a"), new Diff(Operation.DELETE, "123"), new Diff(Operation.EQUAL, "b"), new Diff(Operation.DELETE, "456"), new Diff(Operation.EQUAL, "c")}; - assertEquals("diff_main: Two deletions.", diffs, this.diff_main("a123b456c", "abc", false)); - - // Perform a real diff. - // Switch off the timeout. - this.Diff_Timeout = 0; - diffs = new List {new Diff(Operation.DELETE, "a"), new Diff(Operation.INSERT, "b")}; - assertEquals("diff_main: Simple case #1.", diffs, this.diff_main("a", "b", false)); - - diffs = new List {new Diff(Operation.DELETE, "Apple"), new Diff(Operation.INSERT, "Banana"), new Diff(Operation.EQUAL, "s are a"), new Diff(Operation.INSERT, "lso"), new Diff(Operation.EQUAL, " fruit.")}; - assertEquals("diff_main: Simple case #2.", diffs, this.diff_main("Apples are a fruit.", "Bananas are also fruit.", false)); - - diffs = new List {new Diff(Operation.DELETE, "a"), new Diff(Operation.INSERT, "\u0680"), new Diff(Operation.EQUAL, "x"), new Diff(Operation.DELETE, "\t"), new Diff(Operation.INSERT, new string (new char[]{(char)0}))}; - assertEquals("diff_main: Simple case #3.", diffs, this.diff_main("ax\t", "\u0680x" + (char)0, false)); - - diffs = new List {new Diff(Operation.DELETE, "1"), new Diff(Operation.EQUAL, "a"), new Diff(Operation.DELETE, "y"), new Diff(Operation.EQUAL, "b"), new Diff(Operation.DELETE, "2"), new Diff(Operation.INSERT, "xab")}; - assertEquals("diff_main: Overlap #1.", diffs, this.diff_main("1ayb2", "abxab", false)); - - diffs = new List {new Diff(Operation.INSERT, "xaxcx"), new Diff(Operation.EQUAL, "abc"), new Diff(Operation.DELETE, "y")}; - assertEquals("diff_main: Overlap #2.", diffs, this.diff_main("abcy", "xaxcxabc", false)); - - diffs = new List {new Diff(Operation.DELETE, "ABCD"), new Diff(Operation.EQUAL, "a"), new Diff(Operation.DELETE, "="), new Diff(Operation.INSERT, "-"), new Diff(Operation.EQUAL, "bcd"), new Diff(Operation.DELETE, "="), new Diff(Operation.INSERT, "-"), new Diff(Operation.EQUAL, "efghijklmnopqrs"), new Diff(Operation.DELETE, "EFGHIJKLMNOefg")}; - assertEquals("diff_main: Overlap #3.", diffs, this.diff_main("ABCDa=bcd=efghijklmnopqrsEFGHIJKLMNOefg", "a-bcd-efghijklmnopqrs", false)); - - diffs = new List {new Diff(Operation.INSERT, " "), new Diff(Operation.EQUAL, "a"), new Diff(Operation.INSERT, "nd"), new Diff(Operation.EQUAL, " [[Pennsylvania]]"), new Diff(Operation.DELETE, " and [[New")}; - assertEquals("diff_main: Large equality.", diffs, this.diff_main("a [[Pennsylvania]] and [[New", " and [[Pennsylvania]]", false)); - - this.Diff_Timeout = 0.1f; // 100ms - string a = "`Twas brillig, and the slithy toves\nDid gyre and gimble in the wabe:\nAll mimsy were the borogoves,\nAnd the mome raths outgrabe.\n"; - string b = "I am the very model of a modern major general,\nI've information vegetable, animal, and mineral,\nI know the kings of England, and I quote the fights historical,\nFrom Marathon to Waterloo, in order categorical.\n"; - // Increase the text lengths by 1024 times to ensure a timeout. - for (int i = 0; i < 10; i++) { - a += a; - b += b; - } - DateTime startTime = DateTime.Now; - this.diff_main(a, b); - DateTime endTime = DateTime.Now; - // Test that we took at least the timeout period. - assertTrue("diff_main: Timeout min.", new TimeSpan(((long)(this.Diff_Timeout * 1000)) * 10000) <= endTime - startTime); - // Test that we didn't take forever (be forgiving). - // Theoretically this test could fail very occasionally if the - // OS task swaps or locks up for a second at the wrong moment. - assertTrue("diff_main: Timeout max.", new TimeSpan(((long)(this.Diff_Timeout * 1000)) * 10000 * 2) > endTime - startTime); - this.Diff_Timeout = 0; - - // Test the linemode speedup. - // Must be long to pass the 100 char cutoff. - a = "1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n"; - b = "abcdefghij\nabcdefghij\nabcdefghij\nabcdefghij\nabcdefghij\nabcdefghij\nabcdefghij\nabcdefghij\nabcdefghij\nabcdefghij\nabcdefghij\nabcdefghij\nabcdefghij\n"; - assertEquals("diff_main: Simple line-mode.", this.diff_main(a, b, true), this.diff_main(a, b, false)); - - a = "1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890"; - b = "abcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghij"; - assertEquals("diff_main: Single line-mode.", this.diff_main(a, b, true), this.diff_main(a, b, false)); - - a = "1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n"; - b = "abcdefghij\n1234567890\n1234567890\n1234567890\nabcdefghij\n1234567890\n1234567890\n1234567890\nabcdefghij\n1234567890\n1234567890\n1234567890\nabcdefghij\n"; - string[] texts_linemode = diff_rebuildtexts(this.diff_main(a, b, true)); - string[] texts_textmode = diff_rebuildtexts(this.diff_main(a, b, false)); - assertEquals("diff_main: Overlap line-mode.", texts_textmode, texts_linemode); - - // Test null inputs -- not needed because nulls can't be passed in C#. - } - - public void match_alphabetTest() { - // Initialise the bitmasks for Bitap. - Dictionary bitmask = new Dictionary(); - bitmask.Add('a', 4); bitmask.Add('b', 2); bitmask.Add('c', 1); - assertEquals("match_alphabet: Unique.", bitmask, this.match_alphabet("abc")); - - bitmask.Clear(); - bitmask.Add('a', 37); bitmask.Add('b', 18); bitmask.Add('c', 8); - assertEquals("match_alphabet: Duplicates.", bitmask, this.match_alphabet("abcaba")); - } - - public void match_bitapTest() { - // Bitap algorithm. - this.Match_Distance = 100; - this.Match_Threshold = 0.5f; - assertEquals("match_bitap: Exact match #1.", 5, this.match_bitap("abcdefghijk", "fgh", 5)); - - assertEquals("match_bitap: Exact match #2.", 5, this.match_bitap("abcdefghijk", "fgh", 0)); - - assertEquals("match_bitap: Fuzzy match #1.", 4, this.match_bitap("abcdefghijk", "efxhi", 0)); - - assertEquals("match_bitap: Fuzzy match #2.", 2, this.match_bitap("abcdefghijk", "cdefxyhijk", 5)); - - assertEquals("match_bitap: Fuzzy match #3.", -1, this.match_bitap("abcdefghijk", "bxy", 1)); - - assertEquals("match_bitap: Overflow.", 2, this.match_bitap("123456789xx0", "3456789x0", 2)); - - assertEquals("match_bitap: Before start match.", 0, this.match_bitap("abcdef", "xxabc", 4)); - - assertEquals("match_bitap: Beyond end match.", 3, this.match_bitap("abcdef", "defyy", 4)); - - assertEquals("match_bitap: Oversized pattern.", 0, this.match_bitap("abcdef", "xabcdefy", 0)); - - this.Match_Threshold = 0.4f; - assertEquals("match_bitap: Threshold #1.", 4, this.match_bitap("abcdefghijk", "efxyhi", 1)); - - this.Match_Threshold = 0.3f; - assertEquals("match_bitap: Threshold #2.", -1, this.match_bitap("abcdefghijk", "efxyhi", 1)); - - this.Match_Threshold = 0.0f; - assertEquals("match_bitap: Threshold #3.", 1, this.match_bitap("abcdefghijk", "bcdef", 1)); - - this.Match_Threshold = 0.5f; - assertEquals("match_bitap: Multiple select #1.", 0, this.match_bitap("abcdexyzabcde", "abccde", 3)); - - assertEquals("match_bitap: Multiple select #2.", 8, this.match_bitap("abcdexyzabcde", "abccde", 5)); - - this.Match_Distance = 10; // Strict location. - assertEquals("match_bitap: Distance test #1.", -1, this.match_bitap("abcdefghijklmnopqrstuvwxyz", "abcdefg", 24)); - - assertEquals("match_bitap: Distance test #2.", 0, this.match_bitap("abcdefghijklmnopqrstuvwxyz", "abcdxxefg", 1)); - - this.Match_Distance = 1000; // Loose location. - assertEquals("match_bitap: Distance test #3.", 0, this.match_bitap("abcdefghijklmnopqrstuvwxyz", "abcdefg", 24)); - } - - public void match_mainTest() { - // Full match. - assertEquals("match_main: Equality.", 0, this.match_main("abcdef", "abcdef", 1000)); - - assertEquals("match_main: Null text.", -1, this.match_main("", "abcdef", 1)); - - assertEquals("match_main: Null pattern.", 3, this.match_main("abcdef", "", 3)); - - assertEquals("match_main: Exact match.", 3, this.match_main("abcdef", "de", 3)); - - assertEquals("match_main: Beyond end match.", 3, this.match_main("abcdef", "defy", 4)); - - assertEquals("match_main: Oversized pattern.", 0, this.match_main("abcdef", "abcdefy", 0)); - - this.Match_Threshold = 0.7f; - assertEquals("match_main: Complex match.", 4, this.match_main("I am the very model of a modern major general.", " that berry ", 5)); - this.Match_Threshold = 0.5f; - - // Test null inputs -- not needed because nulls can't be passed in C#. - } - - 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 { - 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"; - assertEquals("Patch: toString.", strp, p.ToString()); - } - - public void patch_fromTextTest() { - assertTrue("patch_fromText: #0.", this.patch_fromText("").Count == 0); - - string strp = "@@ -21,18 +22,17 @@\n jump\n-s\n+ed\n over \n-the\n+a\n %0alaz\n"; - assertEquals("patch_fromText: #1.", strp, this.patch_fromText(strp)[0].ToString()); - - assertEquals("patch_fromText: #2.", "@@ -1 +1 @@\n-a\n+b\n", this.patch_fromText("@@ -1 +1 @@\n-a\n+b\n")[0].ToString()); - - assertEquals("patch_fromText: #3.", "@@ -1,3 +0,0 @@\n-abc\n", this.patch_fromText("@@ -1,3 +0,0 @@\n-abc\n") [0].ToString()); - - assertEquals("patch_fromText: #4.", "@@ -0,0 +1,3 @@\n+abc\n", this.patch_fromText("@@ -0,0 +1,3 @@\n+abc\n") [0].ToString()); - - // Generates error. - try { - this.patch_fromText("Bad\nPatch\n"); - assertFail("patch_fromText: #5."); - } catch (ArgumentException) { - // Exception expected. - } - } - - public void patch_toTextTest() { - string strp = "@@ -21,18 +22,17 @@\n jump\n-s\n+ed\n over \n-the\n+a\n laz\n"; - List patches; - patches = this.patch_fromText(strp); - string result = this.patch_toText(patches); - assertEquals("patch_toText: Single.", strp, result); - - strp = "@@ -1,9 +1,9 @@\n-f\n+F\n oo+fooba\n@@ -7,9 +7,9 @@\n obar\n-,\n+.\n tes\n"; - patches = this.patch_fromText(strp); - result = this.patch_toText(patches); - assertEquals("patch_toText: Dual.", strp, result); - } - - public void patch_addContextTest() { - this.Patch_Margin = 4; - Patch p; - p = this.patch_fromText("@@ -21,4 +21,10 @@\n-jump\n+somersault\n") [0]; - this.patch_addContext(p, "The quick brown fox jumps over the lazy dog."); - assertEquals("patch_addContext: Simple case.", "@@ -17,12 +17,18 @@\n fox \n-jump\n+somersault\n s ov\n", p.ToString()); - - p = this.patch_fromText("@@ -21,4 +21,10 @@\n-jump\n+somersault\n")[0]; - this.patch_addContext(p, "The quick brown fox jumps."); - assertEquals("patch_addContext: Not enough trailing context.", "@@ -17,10 +17,16 @@\n fox \n-jump\n+somersault\n s.\n", p.ToString()); - - p = this.patch_fromText("@@ -3 +3,2 @@\n-e\n+at\n")[0]; - this.patch_addContext(p, "The quick brown fox jumps."); - assertEquals("patch_addContext: Not enough leading context.", "@@ -1,7 +1,8 @@\n Th\n-e\n+at\n qui\n", p.ToString()); - - p = this.patch_fromText("@@ -3 +3,2 @@\n-e\n+at\n")[0]; - this.patch_addContext(p, "The quick brown fox jumps. The quick brown fox crashes."); - assertEquals("patch_addContext: Ambiguity.", "@@ -1,27 +1,28 @@\n Th\n-e\n+at\n quick brown fox jumps. \n", p.ToString()); - } - - public void patch_makeTest() { - List patches; - patches = this.patch_make("", ""); - assertEquals("patch_make: Null case.", "", this.patch_toText(patches)); - - string text1 = "The quick brown fox jumps over the lazy dog."; - string text2 = "That quick brown fox jumped over a lazy dog."; - string expectedPatch = "@@ -1,8 +1,7 @@\n Th\n-at\n+e\n qui\n@@ -21,17 +21,18 @@\n jump\n-ed\n+s\n over \n-a\n+the\n laz\n"; - // The second patch must be "-21,17 +21,18", not "-22,17 +21,18" due to rolling context. - patches = this.patch_make(text2, text1); - assertEquals("patch_make: Text2+Text1 inputs.", expectedPatch, this.patch_toText(patches)); - - expectedPatch = "@@ -1,11 +1,12 @@\n Th\n-e\n+at\n quick b\n@@ -22,18 +22,17 @@\n jump\n-s\n+ed\n over \n-the\n+a\n laz\n"; - patches = this.patch_make(text1, text2); - assertEquals("patch_make: Text1+Text2 inputs.", expectedPatch, this.patch_toText(patches)); - - List diffs = this.diff_main(text1, text2, false); - patches = this.patch_make(diffs); - assertEquals("patch_make: Diff input.", expectedPatch, this.patch_toText(patches)); - - patches = this.patch_make(text1, diffs); - assertEquals("patch_make: Text1+Diff inputs.", expectedPatch, this.patch_toText(patches)); - - patches = this.patch_make(text1, text2, diffs); - assertEquals("patch_make: Text1+Text2+Diff inputs (deprecated).", expectedPatch, this.patch_toText(patches)); - - patches = this.patch_make("`1234567890-=[]\\;',./", "~!@#$%^&*()_+{}|:\"<>?"); - assertEquals("patch_toText: Character encoding.", - "@@ -1,21 +1,21 @@\n-%601234567890-=%5b%5d%5c;',./\n+~!@#$%25%5e&*()_+%7b%7d%7c:%22%3c%3e?\n", - this.patch_toText(patches)); - - diffs = new List { - new Diff(Operation.DELETE, "`1234567890-=[]\\;',./"), - new Diff(Operation.INSERT, "~!@#$%^&*()_+{}|:\"<>?")}; - assertEquals("patch_fromText: Character decoding.", - diffs, - this.patch_fromText("@@ -1,21 +1,21 @@\n-%601234567890-=%5B%5D%5C;',./\n+~!@#$%25%5E&*()_+%7B%7D%7C:%22%3C%3E?\n") [0].diffs); - - text1 = ""; - for (int x = 0; x < 100; x++) { - text1 += "abcdef"; - } - text2 = text1 + "123"; - expectedPatch = "@@ -573,28 +573,31 @@\n cdefabcdefabcdefabcdefabcdef\n+123\n"; - patches = this.patch_make(text1, text2); - assertEquals("patch_make: Long string with repeats.", expectedPatch, this.patch_toText(patches)); - - // Test null inputs -- not needed because nulls can't be passed in C#. - } - - public void patch_splitMaxTest() { - // Assumes that Match_MaxBits is 32. - List patches; - - patches = this.patch_make("abcdefghijklmnopqrstuvwxyz01234567890", "XabXcdXefXghXijXklXmnXopXqrXstXuvXwxXyzX01X23X45X67X89X0"); - this.patch_splitMax(patches); - assertEquals("patch_splitMax: #1.", "@@ -1,32 +1,46 @@\n+X\n ab\n+X\n cd\n+X\n ef\n+X\n gh\n+X\n ij\n+X\n kl\n+X\n mn\n+X\n op\n+X\n qr\n+X\n st\n+X\n uv\n+X\n wx\n+X\n yz\n+X\n 012345\n@@ -25,13 +39,18 @@\n zX01\n+X\n 23\n+X\n 45\n+X\n 67\n+X\n 89\n+X\n 0\n", this.patch_toText(patches)); - - patches = this.patch_make("abcdef1234567890123456789012345678901234567890123456789012345678901234567890uvwxyz", "abcdefuvwxyz"); - string oldToText = this.patch_toText(patches); - this.patch_splitMax(patches); - assertEquals("patch_splitMax: #2.", oldToText, this.patch_toText(patches)); - - patches = this.patch_make("1234567890123456789012345678901234567890123456789012345678901234567890", "abc"); - this.patch_splitMax(patches); - assertEquals("patch_splitMax: #3.", "@@ -1,32 +1,4 @@\n-1234567890123456789012345678\n 9012\n@@ -29,32 +1,4 @@\n-9012345678901234567890123456\n 7890\n@@ -57,14 +1,3 @@\n-78901234567890\n+abc\n", this.patch_toText(patches)); - - patches = this.patch_make("abcdefghij , h : 0 , t : 1 abcdefghij , h : 0 , t : 1 abcdefghij , h : 0 , t : 1", "abcdefghij , h : 1 , t : 1 abcdefghij , h : 1 , t : 1 abcdefghij , h : 0 , t : 1"); - this.patch_splitMax(patches); - assertEquals("patch_splitMax: #4.", "@@ -2,32 +2,32 @@\n bcdefghij , h : \n-0\n+1\n , t : 1 abcdef\n@@ -29,32 +29,32 @@\n bcdefghij , h : \n-0\n+1\n , t : 1 abcdef\n", this.patch_toText(patches)); - } - - public void patch_addPaddingTest() { - List patches; - patches = this.patch_make("", "test"); - assertEquals("patch_addPadding: Both edges full.", - "@@ -0,0 +1,4 @@\n+test\n", - this.patch_toText(patches)); - this.patch_addPadding(patches); - assertEquals("patch_addPadding: Both edges full.", - "@@ -1,8 +1,12 @@\n %01%02%03%04\n+test\n %01%02%03%04\n", - this.patch_toText(patches)); - - patches = this.patch_make("XY", "XtestY"); - assertEquals("patch_addPadding: Both edges partial.", - "@@ -1,2 +1,6 @@\n X\n+test\n Y\n", - this.patch_toText(patches)); - this.patch_addPadding(patches); - assertEquals("patch_addPadding: Both edges partial.", - "@@ -2,8 +2,12 @@\n %02%03%04X\n+test\n Y%01%02%03\n", - this.patch_toText(patches)); - - patches = this.patch_make("XXXXYYYY", "XXXXtestYYYY"); - assertEquals("patch_addPadding: Both edges none.", - "@@ -1,8 +1,12 @@\n XXXX\n+test\n YYYY\n", - this.patch_toText(patches)); - this.patch_addPadding(patches); - assertEquals("patch_addPadding: Both edges none.", - "@@ -5,8 +5,12 @@\n XXXX\n+test\n YYYY\n", - this.patch_toText(patches)); - } - - public void patch_applyTest() { - this.Match_Distance = 1000; - this.Match_Threshold = 0.5f; - this.Patch_DeleteThreshold = 0.5f; - List patches; - patches = this.patch_make("", ""); - Object[] results = this.patch_apply(patches, "Hello world."); - bool[] boolArray = (bool[])results[1]; - string resultStr = results[0] + "\t" + boolArray.Length; - assertEquals("patch_apply: Null case.", "Hello world.\t0", resultStr); - - patches = this.patch_make("The quick brown fox jumps over the lazy dog.", "That quick brown fox jumped over a lazy dog."); - results = this.patch_apply(patches, "The quick brown fox jumps over the lazy dog."); - boolArray = (bool[])results[1]; - resultStr = results[0] + "\t" + boolArray[0] + "\t" + boolArray[1]; - assertEquals("patch_apply: Exact match.", "That quick brown fox jumped over a lazy dog.\tTrue\tTrue", resultStr); - - results = this.patch_apply(patches, "The quick red rabbit jumps over the tired tiger."); - boolArray = (bool[])results[1]; - resultStr = results[0] + "\t" + boolArray[0] + "\t" + boolArray[1]; - assertEquals("patch_apply: Partial match.", "That quick red rabbit jumped over a tired tiger.\tTrue\tTrue", resultStr); - - results = this.patch_apply(patches, "I am the very model of a modern major general."); - boolArray = (bool[])results[1]; - resultStr = results[0] + "\t" + boolArray[0] + "\t" + boolArray[1]; - assertEquals("patch_apply: Failed match.", "I am the very model of a modern major general.\tFalse\tFalse", resultStr); - - patches = this.patch_make("x1234567890123456789012345678901234567890123456789012345678901234567890y", "xabcy"); - results = this.patch_apply(patches, "x123456789012345678901234567890-----++++++++++-----123456789012345678901234567890y"); - boolArray = (bool[])results[1]; - resultStr = results[0] + "\t" + boolArray[0] + "\t" + boolArray[1]; - assertEquals("patch_apply: Big delete, small change.", "xabcy\tTrue\tTrue", resultStr); - - patches = this.patch_make("x1234567890123456789012345678901234567890123456789012345678901234567890y", "xabcy"); - results = this.patch_apply(patches, "x12345678901234567890---------------++++++++++---------------12345678901234567890y"); - boolArray = (bool[])results[1]; - resultStr = results[0] + "\t" + boolArray[0] + "\t" + boolArray[1]; - assertEquals("patch_apply: Big delete, big change 1.", "xabc12345678901234567890---------------++++++++++---------------12345678901234567890y\tFalse\tTrue", resultStr); - - this.Patch_DeleteThreshold = 0.6f; - patches = this.patch_make("x1234567890123456789012345678901234567890123456789012345678901234567890y", "xabcy"); - results = this.patch_apply(patches, "x12345678901234567890---------------++++++++++---------------12345678901234567890y"); - boolArray = (bool[])results[1]; - resultStr = results[0] + "\t" + boolArray[0] + "\t" + boolArray[1]; - assertEquals("patch_apply: Big delete, big change 2.", "xabcy\tTrue\tTrue", resultStr); - this.Patch_DeleteThreshold = 0.5f; - - this.Match_Threshold = 0.0f; - this.Match_Distance = 0; - patches = this.patch_make("abcdefghijklmnopqrstuvwxyz--------------------1234567890", "abcXXXXXXXXXXdefghijklmnopqrstuvwxyz--------------------1234567YYYYYYYYYY890"); - results = this.patch_apply(patches, "ABCDEFGHIJKLMNOPQRSTUVWXYZ--------------------1234567890"); - boolArray = (bool[])results[1]; - resultStr = results[0] + "\t" + boolArray[0] + "\t" + boolArray[1]; - assertEquals("patch_apply: Compensate for failed patch.", "ABCDEFGHIJKLMNOPQRSTUVWXYZ--------------------1234567YYYYYYYYYY890\tFalse\tTrue", resultStr); - this.Match_Threshold = 0.5f; - this.Match_Distance = 1000; - - patches = this.patch_make("", "test"); - string patchStr = this.patch_toText(patches); - this.patch_apply(patches, ""); - assertEquals("patch_apply: No side effects.", patchStr, this.patch_toText(patches)); - - patches = this.patch_make("The quick brown fox jumps over the lazy dog.", "Woof"); - patchStr = this.patch_toText(patches); - this.patch_apply(patches, "The quick brown fox jumps over the lazy dog."); - assertEquals("patch_apply: No side effects with major delete.", patchStr, this.patch_toText(patches)); - - patches = this.patch_make("", "test"); - results = this.patch_apply(patches, ""); - boolArray = (bool[])results[1]; - resultStr = results[0] + "\t" + boolArray[0]; - assertEquals("patch_apply: Edge exact match.", "test\tTrue", resultStr); - - patches = this.patch_make("XY", "XtestY"); - results = this.patch_apply(patches, "XY"); - boolArray = (bool[])results[1]; - resultStr = results[0] + "\t" + boolArray[0]; - assertEquals("patch_apply: Near edge exact match.", "XtestY\tTrue", resultStr); - - patches = this.patch_make("y", "y123"); - results = this.patch_apply(patches, "x"); - boolArray = (bool[])results[1]; - resultStr = results[0] + "\t" + boolArray[0]; - assertEquals("patch_apply: Edge partial match.", "x123\tTrue", resultStr); - } - - private string[] diff_rebuildtexts(List diffs) { - string[] text = { "", "" }; - foreach (Diff myDiff in diffs) { - if (myDiff.operation != Operation.INSERT) { - text[0] += myDiff.text; - } - if (myDiff.operation != Operation.DELETE) { - text[1] += myDiff.text; - } - } - return text; - } - - private static void assertEquals(string error_msg, string expected, string actual) { - if (expected != actual) { - throw new ArgumentException(String.Format("assertEquals (string, string) fail:\n Expected: {0}\n Actual: {1}\n{2}", expected, actual, error_msg)); - } - } - - private static void assertEquals(string error_msg, string[] expected, string[] actual) { - if (expected.Length != actual.Length) { - throw new ArgumentException(String.Format("assertEquals (string[], string[]) length fail:\n Expected: {0}\n Actual: {1}\n{2}", expected, actual, error_msg)); - } - for (int i = 0; i < expected.Length; i++) { - if (expected[i] != actual[i]) { - throw new ArgumentException(String.Format("assertEquals (string[], string[]) index {0} fail:\n Expected: {1}\n Actual: {2}\n{3}", i, expected, actual, error_msg)); - } - } - } - - private static void assertEquals(string error_msg, List expected, List actual) { - if (expected.Count != actual.Count) { - throw new ArgumentException(String.Format("assertEquals (List, List) length fail:\n Expected: {0}\n Actual: {1}\n{2}", expected, actual, error_msg)); - } - for (int i = 0; i < expected.Count; i++) { - if (expected[i] != actual[i]) { - throw new ArgumentException(String.Format("assertEquals (List, List) index {0} fail:\n Expected: {1}\n Actual: {2}\n{3}", i, expected, actual, error_msg)); - } - } - } - - private static void assertEquals(string error_msg, List expected, List actual) { - if (expected.Count != actual.Count) { - throw new ArgumentException(String.Format("assertEquals (List, List) length fail:\n Expected: {0}\n Actual: {1}\n{2}", expected, actual, error_msg)); - } - for (int i = 0; i < expected.Count; i++) { - if (!expected[i].Equals(actual[i])) { - throw new ArgumentException(String.Format("assertEquals (List, List) index {0} fail:\n Expected: {1}\n Actual: {2}\n{3}", i, expected, actual, error_msg)); - } - } - } - - private static void assertEquals(string error_msg, Diff expected, Diff actual) { - if (!expected.Equals(actual)) { - throw new ArgumentException(String.Format("assertEquals (Diff, Diff) fail:\n Expected: {0}\n Actual: {1}\n{2}", expected, actual, error_msg)); - } - } - - private static void assertEquals(string error_msg, Dictionary expected, Dictionary actual) { - foreach(char k in actual.Keys) { - if (!expected.ContainsKey(k)) { - throw new ArgumentException(String.Format("assertEquals (Dictionary, Dictionary) key {0} fail:\n Expected: {1}\n Actual: {2}\n{3}", k, expected, actual, error_msg)); - } - } - foreach(char k in expected.Keys) { - if (!actual.ContainsKey(k)) { - throw new ArgumentException(String.Format("assertEquals (Dictionary, Dictionary) key {0} fail:\n Expected: {1}\n Actual: {2}\n{3}", k, expected, actual, error_msg)); - } - if (actual[k] != expected[k]) { - throw new ArgumentException(String.Format("assertEquals (Dictionary, Dictionary) key {0} fail:\n Expected: {1}\n Actual: {2}\n{3}", k, expected, actual, error_msg)); - } - } - } - - private static void assertEquals(string error_msg, int expected, int actual) { - if (expected != actual) { - throw new ArgumentException(String.Format("assertEquals (int, int) fail:\n Expected: {0}\n Actual: {1}\n{2}", expected, actual, error_msg)); - } - } - - private static void assertTrue(string error_msg, bool expected) { - if (!expected) { - throw new ArgumentException(String.Format("assertTrue fail:\n{0}", error_msg)); - } - } - - private static void assertNull(string error_msg, object value) { - if (value != null) { - throw new ArgumentException(String.Format("assertNull fail:\n{0}", error_msg)); - } - } - - private static void assertFail(string error_msg) { - throw new ArgumentException(String.Format("assertFail fail:\n{0}", error_msg)); - } - - public static void Main(string[] args) { - diff_match_patchTest dmp = new diff_match_patchTest(); - - dmp.diff_commonPrefixTest(); - dmp.diff_commonSuffixTest(); - dmp.diff_commonOverlapTest(); - dmp.diff_halfmatchTest(); - dmp.diff_linesToCharsTest(); - dmp.diff_charsToLinesTest(); - dmp.diff_cleanupMergeTest(); - dmp.diff_cleanupSemanticLosslessTest(); - dmp.diff_cleanupSemanticTest(); - dmp.diff_cleanupEfficiencyTest(); - dmp.diff_prettyHtmlTest(); - dmp.diff_textTest(); - dmp.diff_deltaTest(); - dmp.diff_xIndexTest(); - dmp.diff_levenshteinTest(); - dmp.diff_bisectTest(); - dmp.diff_mainTest(); - - dmp.match_alphabetTest(); - dmp.match_bitapTest(); - dmp.match_mainTest(); - - dmp.patch_patchObjTest(); - dmp.patch_fromTextTest(); - dmp.patch_toTextTest(); - dmp.patch_addContextTest(); - dmp.patch_makeTest(); - dmp.patch_splitMaxTest(); - dmp.patch_addPaddingTest(); - dmp.patch_applyTest(); - - Console.WriteLine("All tests passed."); - } -} diff --git a/csharp/tests/Speedtest.cs b/csharp/tests/Speedtest.cs deleted file mode 100644 index 74c7563..0000000 --- a/csharp/tests/Speedtest.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright 2010 Google Inc. -// All Right Reserved. - -/* - * To compile with Mono: - * mcs Speedtest.cs ../DiffMatchPatch.cs - * To run with Mono: - * mono Speedtest.exe -*/ - -using DiffMatchPatch; -using System; -using System.Collections.Generic; - -public class Speedtest { - public static void Main(string[] args) { - string text1 = System.IO.File.ReadAllText("Speedtest1.txt"); - string text2 = System.IO.File.ReadAllText("Speedtest2.txt"); - - diff_match_patch dmp = new diff_match_patch(); - dmp.Diff_Timeout = 0; - - // Execute one reverse diff as a warmup. - dmp.diff_main(text2, text1); - GC.Collect(); - GC.WaitForPendingFinalizers(); - - DateTime ms_start = DateTime.Now; - dmp.diff_main(text1, text2); - DateTime ms_end = DateTime.Now; - - Console.WriteLine("Elapsed time: " + (ms_end - ms_start)); - } -}