From 3de9ef242177a8e46a392e1ec6704dc2f5c061d3 Mon Sep 17 00:00:00 2001 From: rolshevsky Date: Fri, 20 Jul 2018 17:08:35 +0300 Subject: [PATCH 1/8] [C#] move all files under Solution - Add DiffMatchPatch project - Add DiffMatchPatchTest project for xUnit tests --- csharp/.gitignore | 329 +++++ .../DiffMatchPatch.Tests.csproj | 16 + .../DiffMatchPatchTest.cs | 1234 +++++++++++++++++ .../Speedtest.cs | 4 +- .../Speedtest1.txt | 0 .../Speedtest2.txt | 0 csharp/DiffMatchPatch.sln | 22 + csharp/{ => DiffMatchPatch}/DiffMatchPatch.cs | 2 +- csharp/DiffMatchPatch/DiffMatchPatch.csproj | 6 + csharp/tests/DiffMatchPatchTest.cs | 1230 ---------------- 10 files changed, 1610 insertions(+), 1233 deletions(-) create mode 100644 csharp/.gitignore create mode 100644 csharp/DiffMatchPatch.Tests/DiffMatchPatch.Tests.csproj create mode 100644 csharp/DiffMatchPatch.Tests/DiffMatchPatchTest.cs rename csharp/{tests => DiffMatchPatch.Tests}/Speedtest.cs (94%) rename csharp/{tests => DiffMatchPatch.Tests}/Speedtest1.txt (100%) rename csharp/{tests => DiffMatchPatch.Tests}/Speedtest2.txt (100%) create mode 100644 csharp/DiffMatchPatch.sln rename csharp/{ => DiffMatchPatch}/DiffMatchPatch.cs (99%) create mode 100644 csharp/DiffMatchPatch/DiffMatchPatch.csproj delete mode 100644 csharp/tests/DiffMatchPatchTest.cs 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/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/DiffMatchPatchTest.cs b/csharp/DiffMatchPatch.Tests/DiffMatchPatchTest.cs new file mode 100644 index 0000000..f458bbe --- /dev/null +++ b/csharp/DiffMatchPatch.Tests/DiffMatchPatchTest.cs @@ -0,0 +1,1234 @@ +/* + * 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 System.Collections.Generic; +using System; +using System.Text; +using Xunit; + +namespace Google.DiffMatchPatch.Tests +{ + public class DiffMatchPatchTest : diff_match_patch { + [Fact] + public void Diff_CommonPrefixTest() { + // Detect any common suffix. + Assert.Equal(0, diff_commonPrefix("abc", "xyz")); + + 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/DiffMatchPatch.Tests/Speedtest.cs similarity index 94% rename from csharp/tests/Speedtest.cs rename to csharp/DiffMatchPatch.Tests/Speedtest.cs index 74c7563..4d823c6 100644 --- a/csharp/tests/Speedtest.cs +++ b/csharp/DiffMatchPatch.Tests/Speedtest.cs @@ -8,11 +8,11 @@ * mono Speedtest.exe */ -using DiffMatchPatch; using System; using System.Collections.Generic; public class Speedtest { + /*TODO Replace with BenchmarkDotNet public static void Main(string[] args) { string text1 = System.IO.File.ReadAllText("Speedtest1.txt"); string text2 = System.IO.File.ReadAllText("Speedtest2.txt"); @@ -30,5 +30,5 @@ public static void Main(string[] args) { DateTime ms_end = DateTime.Now; Console.WriteLine("Elapsed time: " + (ms_end - ms_start)); - } + }*/ } diff --git a/csharp/tests/Speedtest1.txt b/csharp/DiffMatchPatch.Tests/Speedtest1.txt similarity index 100% rename from csharp/tests/Speedtest1.txt rename to csharp/DiffMatchPatch.Tests/Speedtest1.txt diff --git a/csharp/tests/Speedtest2.txt b/csharp/DiffMatchPatch.Tests/Speedtest2.txt similarity index 100% rename from csharp/tests/Speedtest2.txt rename to csharp/DiffMatchPatch.Tests/Speedtest2.txt diff --git a/csharp/DiffMatchPatch.sln b/csharp/DiffMatchPatch.sln new file mode 100644 index 0000000..987a64e --- /dev/null +++ b/csharp/DiffMatchPatch.sln @@ -0,0 +1,22 @@ + +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 +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 + EndGlobalSection +EndGlobal diff --git a/csharp/DiffMatchPatch.cs b/csharp/DiffMatchPatch/DiffMatchPatch.cs similarity index 99% rename from csharp/DiffMatchPatch.cs rename to csharp/DiffMatchPatch/DiffMatchPatch.cs index 6c0ecb2..502e94c 100644 --- a/csharp/DiffMatchPatch.cs +++ b/csharp/DiffMatchPatch/DiffMatchPatch.cs @@ -22,7 +22,7 @@ using System.Text; using System.Text.RegularExpressions; -namespace DiffMatchPatch { +namespace Google.DiffMatchPatch { internal static class CompatibilityExtensions { // JScript splice function public static List Splice(this List input, int start, int count, 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."); - } -} From 7516b5cae63f81575fd25690692a62787adf9c32 Mon Sep 17 00:00:00 2001 From: rolshevsky Date: Fri, 20 Jul 2018 17:54:59 +0300 Subject: [PATCH 2/8] fix all tests --- .../DiffMatchPatchTest.cs | 1234 --------------- csharp/DiffMatchPatch.Tests/DiffTests.cs | 1092 +++++++++++++ csharp/DiffMatchPatch.Tests/MatchTests.cs | 110 ++ csharp/DiffMatchPatch.Tests/PatchTests.cs | 311 ++++ csharp/DiffMatchPatch.Tests/Speedtest.cs | 24 +- csharp/DiffMatchPatch/DiffMatchPatch.cs | 1382 +++++++++++------ 6 files changed, 2476 insertions(+), 1677 deletions(-) delete mode 100644 csharp/DiffMatchPatch.Tests/DiffMatchPatchTest.cs create mode 100644 csharp/DiffMatchPatch.Tests/DiffTests.cs create mode 100644 csharp/DiffMatchPatch.Tests/MatchTests.cs create mode 100644 csharp/DiffMatchPatch.Tests/PatchTests.cs diff --git a/csharp/DiffMatchPatch.Tests/DiffMatchPatchTest.cs b/csharp/DiffMatchPatch.Tests/DiffMatchPatchTest.cs deleted file mode 100644 index f458bbe..0000000 --- a/csharp/DiffMatchPatch.Tests/DiffMatchPatchTest.cs +++ /dev/null @@ -1,1234 +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 System.Collections.Generic; -using System; -using System.Text; -using Xunit; - -namespace Google.DiffMatchPatch.Tests -{ - public class DiffMatchPatchTest : diff_match_patch { - [Fact] - public void Diff_CommonPrefixTest() { - // Detect any common suffix. - Assert.Equal(0, diff_commonPrefix("abc", "xyz")); - - 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/DiffMatchPatch.Tests/DiffTests.cs b/csharp/DiffMatchPatch.Tests/DiffTests.cs new file mode 100644 index 0000000..4ece9db --- /dev/null +++ b/csharp/DiffMatchPatch.Tests/DiffTests.cs @@ -0,0 +1,1092 @@ +/* + * 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)); + + // Generates error (%c3%xy invalid Unicode). + //TODO:rolshevsky:Assert.Throws(() => diff_fromDelta("", "+%c3%xy")); + + // 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.Tests/Speedtest.cs b/csharp/DiffMatchPatch.Tests/Speedtest.cs index 4d823c6..6d6eb12 100644 --- a/csharp/DiffMatchPatch.Tests/Speedtest.cs +++ b/csharp/DiffMatchPatch.Tests/Speedtest.cs @@ -1,12 +1,20 @@ -// Copyright 2010 Google Inc. -// All Right Reserved. - /* - * To compile with Mono: - * mcs Speedtest.cs ../DiffMatchPatch.cs - * To run with Mono: - * mono Speedtest.exe -*/ + * 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; diff --git a/csharp/DiffMatchPatch/DiffMatchPatch.cs b/csharp/DiffMatchPatch/DiffMatchPatch.cs index 502e94c..36fb337 100644 --- a/csharp/DiffMatchPatch/DiffMatchPatch.cs +++ b/csharp/DiffMatchPatch/DiffMatchPatch.cs @@ -22,11 +22,14 @@ using System.Text; using System.Text.RegularExpressions; -namespace Google.DiffMatchPatch { - internal static class CompatibilityExtensions { +namespace Google.DiffMatchPatch +{ + internal static class CompatibilityExtensions + { // JScript splice function public static List Splice(this List input, int start, int count, - params T[] objects) { + params T[] objects) + { List deletedRange = input.GetRange(start, count); input.RemoveRange(start, count); input.InsertRange(start, objects); @@ -35,7 +38,8 @@ public static List Splice(this List input, int start, int count, } // Java substring function - public static string JavaSubstring(this string s, int begin, int end) { + public static string JavaSubstring(this string s, int begin, int end) + { return s.Substring(begin, end - begin); } } @@ -46,16 +50,21 @@ public static string JavaSubstring(this string s, int begin, int end) { * Diff(Operation.EQUAL, " world.")} * which means: delete "Hello", add "Goodbye" and keep " world." */ - public enum Operation { - DELETE, INSERT, EQUAL + public enum Operation + { + DELETE, + INSERT, + EQUAL } /** * Class representing one diff operation. */ - public class Diff { + public class Diff + { public Operation operation; + // One of: INSERT, DELETE or EQUAL. public string text; // The text associated with this diff operation. @@ -65,7 +74,8 @@ public class Diff { * @param operation One of INSERT, DELETE or EQUAL. * @param text The text being applied. */ - public Diff(Operation operation, string text) { + public Diff(Operation operation, string text) + { // Construct a diff with the specified operation and text. this.operation = operation; this.text = text; @@ -75,7 +85,8 @@ public Diff(Operation operation, string text) { * Display a human-readable version of this Diff. * @return text version. */ - public override string ToString() { + public override string ToString() + { string prettyText = this.text.Replace('\n', '\u00b6'); return "Diff(" + this.operation + ",\"" + prettyText + "\")"; } @@ -85,15 +96,18 @@ public override string ToString() { * @param d Another Diff to compare against. * @return true or false. */ - public override bool Equals(Object obj) { + public override bool Equals(Object obj) + { // If parameter is null return false. - if (obj == null) { + if (obj == null) + { return false; } // If parameter cannot be cast to Diff return false. Diff p = obj as Diff; - if ((System.Object)p == null) { + if ((System.Object) p == null) + { return false; } @@ -101,9 +115,11 @@ public override bool Equals(Object obj) { return p.operation == this.operation && p.text == this.text; } - public bool Equals(Diff obj) { + public bool Equals(Diff obj) + { // If parameter is null return false. - if (obj == null) { + if (obj == null) + { return false; } @@ -111,7 +127,8 @@ public bool Equals(Diff obj) { return obj.operation == this.operation && obj.text == this.text; } - public override int GetHashCode() { + public override int GetHashCode() + { return text.GetHashCode() ^ operation.GetHashCode(); } } @@ -120,7 +137,8 @@ public override int GetHashCode() { /** * Class representing one patch operation. */ - public class Patch { + public class Patch + { public List diffs = new List(); public int start1; public int start2; @@ -133,28 +151,43 @@ public class Patch { * Indices are printed as 1-based, not 0-based. * @return The GNU diff string. */ - public override string ToString() { + public override string ToString() + { string coords1, coords2; - if (this.length1 == 0) { + if (this.length1 == 0) + { coords1 = this.start1 + ",0"; - } else if (this.length1 == 1) { + } + else if (this.length1 == 1) + { coords1 = Convert.ToString(this.start1 + 1); - } else { + } + else + { coords1 = (this.start1 + 1) + "," + this.length1; } - if (this.length2 == 0) { + + if (this.length2 == 0) + { coords2 = this.start2 + ",0"; - } else if (this.length2 == 1) { + } + else if (this.length2 == 1) + { coords2 = Convert.ToString(this.start2 + 1); - } else { + } + else + { coords2 = (this.start2 + 1) + "," + this.length2; } + StringBuilder text = new StringBuilder(); text.Append("@@ -").Append(coords1).Append(" +").Append(coords2) - .Append(" @@\n"); + .Append(" @@\n"); // Escape the body of the patch with %xx notation. - foreach (Diff aDiff in this.diffs) { - switch (aDiff.operation) { + foreach (Diff aDiff in this.diffs) + { + switch (aDiff.operation) + { case Operation.INSERT: text.Append('+'); break; @@ -168,6 +201,7 @@ public override string ToString() { text.Append(diff_match_patch.encodeURI(aDiff.text)).Append("\n"); } + return text.ToString(); } } @@ -177,25 +211,31 @@ public override string ToString() { * Class containing the diff, match and patch methods. * Also Contains the behaviour settings. */ - public class diff_match_patch { + 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; @@ -215,7 +255,8 @@ public class diff_match_patch { * @param text2 New string to be diffed. * @return List of Diff objects. */ - public List diff_main(string text1, string text2) { + public List diff_main(string text1, string text2) + { return diff_main(text1, text2, true); } @@ -228,15 +269,20 @@ public List diff_main(string text1, string text2) { * 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) { + 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) { + if (this.Diff_Timeout <= 0) + { deadline = DateTime.MaxValue; - } else { + } + else + { deadline = DateTime.Now + - new TimeSpan(((long)(Diff_Timeout * 1000)) * 10000); + new TimeSpan(((long) (Diff_Timeout * 1000)) * 10000); } + return diff_main(text1, text2, checklines, deadline); } @@ -254,16 +300,20 @@ public List diff_main(string text1, string text2, bool checklines) { * @return List of Diff objects. */ private List diff_main(string text1, string text2, bool checklines, - DateTime deadline) { + 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) { + if (text1 == text2) + { diffs = new List(); - if (text1.Length != 0) { + if (text1.Length != 0) + { diffs.Add(new Diff(Operation.EQUAL, text1)); } + return diffs; } @@ -283,10 +333,13 @@ private List diff_main(string text1, string text2, bool checklines, diffs = diff_compute(text1, text2, checklines, deadline); // Restore the prefix and suffix. - if (commonprefix.Length != 0) { + if (commonprefix.Length != 0) + { diffs.Insert(0, (new Diff(Operation.EQUAL, commonprefix))); } - if (commonsuffix.Length != 0) { + + if (commonsuffix.Length != 0) + { diffs.Add(new Diff(Operation.EQUAL, commonsuffix)); } @@ -306,16 +359,19 @@ private List diff_main(string text1, string text2, bool checklines, * @return List of Diff objects. */ private List diff_compute(string text1, string text2, - bool checklines, DateTime deadline) { + bool checklines, DateTime deadline) + { List diffs = new List(); - if (text1.Length == 0) { + if (text1.Length == 0) + { // Just add some text (speedup). diffs.Add(new Diff(Operation.INSERT, text2)); return diffs; } - if (text2.Length == 0) { + if (text2.Length == 0) + { // Just delete some text (speedup). diffs.Add(new Diff(Operation.DELETE, text1)); return diffs; @@ -324,17 +380,18 @@ private List diff_compute(string text1, string text2, 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) { + if (i != -1) + { // Shorter text is inside the longer text (speedup). - Operation op = (text1.Length > text2.Length) ? - Operation.DELETE : Operation.INSERT; + 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) { + 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)); @@ -344,7 +401,8 @@ private List diff_compute(string text1, string text2, // Check to see if the problem can be split in two. string[] hm = diff_halfMatch(text1, text2); - if (hm != null) { + if (hm != null) + { // A half-match was found, sort out the return data. string text1_a = hm[0]; string text1_b = hm[1]; @@ -361,7 +419,8 @@ private List diff_compute(string text1, string text2, return diffs; } - if (checklines && text1.Length > 100 && text2.Length > 100) { + if (checklines && text1.Length > 100 && text2.Length > 100) + { return diff_lineMode(text1, text2, deadline); } @@ -378,12 +437,13 @@ private List diff_compute(string text1, string text2, * @return List of Diff objects. */ private List diff_lineMode(string text1, string text2, - DateTime deadline) { + 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]; + text1 = (string) a[0]; + text2 = (string) a[1]; + List linearray = (List) a[2]; List diffs = diff_main(text1, text2, false, deadline); @@ -400,8 +460,10 @@ private List diff_lineMode(string text1, string text2, int count_insert = 0; string text_delete = string.Empty; string text_insert = string.Empty; - while (pointer < diffs.Count) { - switch (diffs[pointer].operation) { + while (pointer < diffs.Count) + { + switch (diffs[pointer].operation) + { case Operation.INSERT: count_insert++; text_insert += diffs[pointer].text; @@ -412,25 +474,29 @@ private List diff_lineMode(string text1, string text2, break; case Operation.EQUAL: // Upon reaching an equality, check for prior redundancies. - if (count_delete >= 1 && count_insert >= 1) { + 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); + count_delete + count_insert); pointer = pointer - count_delete - count_insert; List subDiff = - this.diff_main(text_delete, text_insert, false, deadline); + 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. + + diffs.RemoveAt(diffs.Count - 1); // Remove the dummy entry at the end. return diffs; } @@ -445,7 +511,8 @@ private List diff_lineMode(string text1, string text2, * @return List of Diff objects. */ protected List diff_bisect(string text1, string text2, - DateTime deadline) { + DateTime deadline) + { // Cache the text lengths to prevent multiple calls. int text1_length = text1.Length; int text2_length = text2.Length; @@ -454,10 +521,12 @@ protected List diff_bisect(string text1, string text2, 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++) { + 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; @@ -470,40 +539,56 @@ protected List diff_bisect(string text1, string text2, int k1end = 0; int k2start = 0; int k2end = 0; - for (int d = 0; d < max_d; d++) { + for (int d = 0; d < max_d; d++) + { // Bail out if deadline is reached. - if (DateTime.Now > deadline) { + if (DateTime.Now > deadline) + { break; } // Walk the front path one step. - for (int k1 = -d + k1start; k1 <= d - k1end; k1 += 2) { + 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]) { + if (k1 == -d || k1 != d && v1[k1_offset - 1] < v1[k1_offset + 1]) + { x1 = v1[k1_offset + 1]; - } else { + } + else + { x1 = v1[k1_offset - 1] + 1; } + int y1 = x1 - k1; while (x1 < text1_length && y1 < text2_length - && text1[x1] == text2[y1]) { + && text1[x1] == text2[y1]) + { x1++; y1++; } + v1[k1_offset] = x1; - if (x1 > text1_length) { + if (x1 > text1_length) + { // Ran off the right of the graph. k1end += 2; - } else if (y1 > text2_length) { + } + else if (y1 > text2_length) + { // Ran off the bottom of the graph. k1start += 2; - } else if (front) { + } + else if (front) + { int k2_offset = v_offset + delta - k1; - if (k2_offset >= 0 && k2_offset < v_length && v2[k2_offset] != -1) { + 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) { + if (x1 >= x2) + { // Overlap detected. return diff_bisectSplit(text1, text2, x1, y1, deadline); } @@ -512,36 +597,50 @@ protected List diff_bisect(string text1, string text2, } // Walk the reverse path one step. - for (int k2 = -d + k2start; k2 <= d - k2end; k2 += 2) { + 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]) { + if (k2 == -d || k2 != d && v2[k2_offset - 1] < v2[k2_offset + 1]) + { x2 = v2[k2_offset + 1]; - } else { + } + 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]) { + && text1[text1_length - x2 - 1] + == text2[text2_length - y2 - 1]) + { x2++; y2++; } + v2[k2_offset] = x2; - if (x2 > text1_length) { + if (x2 > text1_length) + { // Ran off the left of the graph. k2end += 2; - } else if (y2 > text2_length) { + } + else if (y2 > text2_length) + { // Ran off the top of the graph. k2start += 2; - } else if (!front) { + } + else if (!front) + { int k1_offset = v_offset + delta - k2; - if (k1_offset >= 0 && k1_offset < v_length && v1[k1_offset] != -1) { + 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) { + if (x1 >= x2) + { // Overlap detected. return diff_bisectSplit(text1, text2, x1, y1, deadline); } @@ -549,6 +648,7 @@ protected List diff_bisect(string text1, string text2, } } } + // Diff took too long and hit the deadline or // number of diffs equals number of characters, no commonality at all. List diffs = new List(); @@ -568,7 +668,8 @@ protected List diff_bisect(string text1, string text2, * @return LinkedList of Diff objects. */ private List diff_bisectSplit(string text1, string text2, - int x, int y, DateTime deadline) { + int x, int y, DateTime deadline) + { string text1a = text1.Substring(0, x); string text2a = text2.Substring(0, y); string text1b = text1.Substring(x); @@ -591,7 +692,8 @@ private List diff_bisectSplit(string text1, string text2, * 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) { + protected Object[] diff_linesToChars(string text1, string text2) + { List lineArray = new List(); Dictionary lineHash = new Dictionary(); // e.g. linearray[4] == "Hello\n" @@ -604,7 +706,7 @@ protected Object[] diff_linesToChars(string text1, string text2) { // 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 }; + return new Object[] {chars1, chars2, lineArray}; } /** @@ -617,7 +719,8 @@ protected Object[] diff_linesToChars(string text1, string text2) { * @return Encoded string. */ private string diff_linesToCharsMunge(string text, List lineArray, - Dictionary lineHash, int maxLines) { + Dictionary lineHash, int maxLines) + { int lineStart = 0; int lineEnd = -1; string line; @@ -625,27 +728,37 @@ private string diff_linesToCharsMunge(string text, List lineArray, // 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) { + while (lineEnd < text.Length - 1) + { lineEnd = text.IndexOf('\n', lineStart); - if (lineEnd == -1) { + 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) { + 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))); + chars.Append(((char) (lineArray.Count - 1))); } + lineStart = lineEnd + 1; } + return chars.ToString(); } @@ -656,13 +769,17 @@ private string diff_linesToCharsMunge(string text, List lineArray, * @param lineArray List of unique strings. */ protected void diff_charsToLines(ICollection diffs, - IList lineArray) { + IList lineArray) + { StringBuilder text; - foreach (Diff diff in diffs) { + foreach (Diff diff in diffs) + { text = new StringBuilder(); - for (int j = 0; j < diff.text.Length; j++) { + for (int j = 0; j < diff.text.Length; j++) + { text.Append(lineArray[diff.text[j]]); } + diff.text = text.ToString(); } } @@ -673,14 +790,18 @@ protected void diff_charsToLines(ICollection diffs, * @param text2 Second string. * @return The number of characters common to the start of each string. */ - public int diff_commonPrefix(string text1, string text2) { + 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]) { + for (int i = 0; i < n; i++) + { + if (text1[i] != text2[i]) + { return i; } } + return n; } @@ -690,16 +811,20 @@ public int diff_commonPrefix(string text1, string text2) { * @param text2 Second string. * @return The number of characters common to the end of each string. */ - public int diff_commonSuffix(string text1, string text2) { + 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]) { + for (int i = 1; i <= n; i++) + { + if (text1[text1_length - i] != text2[text2_length - i]) + { return i - 1; } } + return n; } @@ -710,23 +835,31 @@ public int diff_commonSuffix(string text1, string text2) { * @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) { + 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) { + if (text1_length == 0 || text2_length == 0) + { return 0; } + // Truncate the longer string. - if (text1_length > text2_length) { + if (text1_length > text2_length) + { text1 = text1.Substring(text1_length - text2_length); - } else if (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) { + if (text1 == text2) + { return text_length; } @@ -735,15 +868,19 @@ protected int diff_commonOverlap(string text1, string text2) { // Performance analysis: https://neil.fraser.name/news/2010/11/04/ int best = 0; int length = 1; - while (true) { + while (true) + { string pattern = text1.Substring(text_length - length); int found = text2.IndexOf(pattern, StringComparison.Ordinal); - if (found == -1) { + if (found == -1) + { return best; } + length += found; if (found == 0 || text1.Substring(text_length - length) == - text2.Substring(0, length)) { + text2.Substring(0, length)) + { best = length; length++; } @@ -761,41 +898,55 @@ protected int diff_commonOverlap(string text1, string text2) { * common middle. Or null if there was no match. */ - protected string[] diff_halfMatch(string text1, string text2) { - if (this.Diff_Timeout <= 0) { + 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. + 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); + (longtext.Length + 3) / 4); // Check again based on the third quarter. string[] hm2 = diff_halfMatchI(longtext, shorttext, - (longtext.Length + 1) / 2); + (longtext.Length + 1) / 2); string[] hm; - if (hm1 == null && hm2 == null) { + if (hm1 == null && hm2 == null) + { return null; - } else if (hm2 == null) { + } + else if (hm2 == null) + { hm = hm1; - } else if (hm1 == null) { + } + else if (hm1 == null) + { hm = hm2; - } else { + } + 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) { + 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] }; + } + else + { + return new string[] {hm[2], hm[3], hm[0], hm[1], hm[4]}; } } @@ -809,7 +960,8 @@ protected string[] diff_halfMatch(string text1, string text2) { * 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) { + 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; @@ -817,24 +969,33 @@ private string[] diff_halfMatchI(string longtext, string shorttext, int i) { 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) { + StringComparison.Ordinal)) != -1) + { int prefixLength = diff_commonPrefix(longtext.Substring(i), - shorttext.Substring(j)); + shorttext.Substring(j)); int suffixLength = diff_commonSuffix(longtext.Substring(0, i), - shorttext.Substring(0, j)); - if (best_common.Length < suffixLength + prefixLength) { + shorttext.Substring(0, j)); + if (best_common.Length < suffixLength + prefixLength) + { best_common = shorttext.Substring(j - suffixLength, suffixLength) - + shorttext.Substring(j, prefixLength); + + 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 { + + 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; } } @@ -844,51 +1005,65 @@ private string[] diff_halfMatchI(string longtext, string shorttext, int i) { * equalities. * @param diffs List of Diff objects. */ - public void diff_cleanupSemantic(List diffs) { + 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. + 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. + 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) { + } + else + { + // an insertion or deletion + if (diffs[pointer].operation == Operation.INSERT) + { length_insertions2 += diffs[pointer].text.Length; - } else { + } + 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))) { + <= 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)); + 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) { + if (equalities.Count > 0) + { equalities.Pop(); } + pointer = equalities.Count > 0 ? equalities.Peek() : -1; - length_insertions1 = 0; // Reset the counters. + length_insertions1 = 0; // Reset the counters. length_deletions1 = 0; length_insertions2 = 0; length_deletions2 = 0; @@ -896,13 +1071,16 @@ public void diff_cleanupSemantic(List diffs) { changes = true; } } + pointer++; } // Normalize the diff. - if (changes) { + if (changes) + { diff_cleanupMerge(diffs); } + diff_cleanupSemanticLossless(diffs); // Find any overlaps between deletions and insertions. @@ -912,42 +1090,51 @@ public void diff_cleanupSemantic(List diffs) { // -> defxxxabc // Only extract an overlap if it is as big as the edit ahead or behind it. pointer = 1; - while (pointer < diffs.Count) { + while (pointer < diffs.Count) + { if (diffs[pointer - 1].operation == Operation.DELETE && - diffs[pointer].operation == Operation.INSERT) { + 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 >= overlap_length2) + { if (overlap_length1 >= deletion.Length / 2.0 || - overlap_length1 >= insertion.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))); + insertion.Substring(0, overlap_length1))); diffs[pointer - 1].text = - deletion.Substring(0, deletion.Length - overlap_length1); + deletion.Substring(0, deletion.Length - overlap_length1); diffs[pointer + 1].text = insertion.Substring(overlap_length1); pointer++; } - } else { + } + else + { if (overlap_length2 >= deletion.Length / 2.0 || - overlap_length2 >= insertion.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))); + deletion.Substring(0, overlap_length2))); diffs[pointer - 1].operation = Operation.INSERT; diffs[pointer - 1].text = - insertion.Substring(0, insertion.Length - overlap_length2); + insertion.Substring(0, insertion.Length - overlap_length2); diffs[pointer + 1].operation = Operation.DELETE; diffs[pointer + 1].text = deletion.Substring(overlap_length2); pointer++; } } + pointer++; } + pointer++; } } @@ -958,12 +1145,15 @@ public void diff_cleanupSemantic(List diffs) { * e.g: The cat came. -> The cat came. * @param diffs List of Diff objects. */ - public void diff_cleanupSemanticLossless(List diffs) { + 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) { + while (pointer < diffs.Count - 1) + { if (diffs[pointer - 1].operation == Operation.EQUAL && - 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; @@ -971,7 +1161,8 @@ public void diff_cleanupSemanticLossless(List diffs) { // First, shift the edit as far left as possible. int commonOffset = this.diff_commonSuffix(equality1, edit); - if (commonOffset > 0) { + 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); @@ -984,17 +1175,19 @@ public void diff_cleanupSemanticLossless(List diffs) { string bestEdit = edit; string bestEquality2 = equality2; int bestScore = diff_cleanupSemanticScore(equality1, edit) + - diff_cleanupSemanticScore(edit, equality2); + diff_cleanupSemanticScore(edit, equality2); while (edit.Length != 0 && equality2.Length != 0 - && edit[0] == equality2[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); + diff_cleanupSemanticScore(edit, equality2); // The >= encourages trailing rather than leading whitespace on // edits. - if (score >= bestScore) { + if (score >= bestScore) + { bestScore = score; bestEquality1 = equality1; bestEdit = edit; @@ -1002,23 +1195,32 @@ public void diff_cleanupSemanticLossless(List diffs) { } } - if (diffs[pointer - 1].text != bestEquality1) { + if (diffs[pointer - 1].text != bestEquality1) + { // We have an improvement, save it back to the diff. - if (bestEquality1.Length != 0) { + if (bestEquality1.Length != 0) + { diffs[pointer - 1].text = bestEquality1; - } else { + } + else + { diffs.RemoveAt(pointer - 1); pointer--; } + diffs[pointer].text = bestEdit; - if (bestEquality2.Length != 0) { + if (bestEquality2.Length != 0) + { diffs[pointer + 1].text = bestEquality2; - } else { + } + else + { diffs.RemoveAt(pointer + 1); pointer--; } } } + pointer++; } } @@ -1031,8 +1233,10 @@ public void diff_cleanupSemanticLossless(List diffs) { * @param two Second string. * @return The score. */ - private int diff_cleanupSemanticScore(string one, string two) { - if (one.Length == 0 || two.Length == 0) { + private int diff_cleanupSemanticScore(string one, string two) + { + if (one.Length == 0 || two.Length == 0) + { // Edges are the best. return 6; } @@ -1053,22 +1257,32 @@ private int diff_cleanupSemanticScore(string one, string two) { bool blankLine1 = lineBreak1 && BLANKLINEEND.IsMatch(one); bool blankLine2 = lineBreak2 && BLANKLINESTART.IsMatch(two); - if (blankLine1 || blankLine2) { + if (blankLine1 || blankLine2) + { // Five points for blank lines. return 5; - } else if (lineBreak1 || lineBreak2) { + } + else if (lineBreak1 || lineBreak2) + { // Four points for line breaks. return 4; - } else if (nonAlphaNumeric1 && !whitespace1 && whitespace2) { + } + else if (nonAlphaNumeric1 && !whitespace1 && whitespace2) + { // Three points for end of sentences. return 3; - } else if (whitespace1 || whitespace2) { + } + else if (whitespace1 || whitespace2) + { // Two points for whitespace. return 2; - } else if (nonAlphaNumeric1 || nonAlphaNumeric2) { + } + else if (nonAlphaNumeric1 || nonAlphaNumeric2) + { // One point for non-alphanumeric. return 1; } + return 0; } @@ -1081,13 +1295,14 @@ private int diff_cleanupSemanticScore(string one, string two) { * equalities. * @param diffs List of Diff objects. */ - public void diff_cleanupEfficiency(List diffs) { + 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. + 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. @@ -1096,27 +1311,41 @@ public void diff_cleanupEfficiency(List diffs) { 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. + while (pointer < diffs.Count) + { + if (diffs[pointer].operation == Operation.EQUAL) + { + // Equality found. if (diffs[pointer].text.Length < this.Diff_EditCost - && (post_ins || post_del)) { + && (post_ins || post_del)) + { // Candidate found. equalities.Push(pointer); pre_ins = post_ins; pre_del = post_del; lastEquality = diffs[pointer].text; - } else { + } + 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) { + } + else + { + // An insertion or deletion. + if (diffs[pointer].operation == Operation.DELETE) + { post_del = true; - } else { + } + else + { post_ins = true; } + /* * Five types to be split: * ABXYCD @@ -1127,35 +1356,43 @@ public void diff_cleanupEfficiency(List diffs) { */ 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))) { + || ((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)); + 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. + equalities.Pop(); // Throw away the equality we just deleted. lastEquality = string.Empty; - if (pre_ins && pre_del) { + 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) { + } + else + { + if (equalities.Count > 0) + { equalities.Pop(); } pointer = equalities.Count > 0 ? equalities.Peek() : -1; post_ins = post_del = false; } + changes = true; } } + pointer++; } - if (changes) { + if (changes) + { diff_cleanupMerge(diffs); } } @@ -1165,7 +1402,8 @@ public void diff_cleanupEfficiency(List diffs) { * 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) { + public void diff_cleanupMerge(List diffs) + { // Add a dummy entry at the end. diffs.Add(new Diff(Operation.EQUAL, string.Empty)); int pointer = 0; @@ -1174,8 +1412,10 @@ public void diff_cleanupMerge(List diffs) { string text_delete = string.Empty; string text_insert = string.Empty; int commonlength; - while (pointer < diffs.Count) { - switch (diffs[pointer].operation) { + while (pointer < diffs.Count) + { + switch (diffs[pointer].operation) + { case Operation.INSERT: count_insert++; text_insert += diffs[pointer].text; @@ -1188,57 +1428,76 @@ public void diff_cleanupMerge(List diffs) { 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) { + 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 (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].operation + == Operation.EQUAL) + { diffs[pointer - count_delete - count_insert - 1].text - += text_insert.Substring(0, commonlength); - } else { + += text_insert.Substring(0, commonlength); + } + else + { diffs.Insert(0, new Diff(Operation.EQUAL, - text_insert.Substring(0, commonlength))); + 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) { + if (commonlength != 0) + { diffs[pointer].text = text_insert.Substring(text_insert.Length - - commonlength) + diffs[pointer].text; + - commonlength) + diffs[pointer].text; text_insert = text_insert.Substring(0, text_insert.Length - - commonlength); + - commonlength); text_delete = text_delete.Substring(0, text_delete.Length - - commonlength); + - 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) { + if (text_delete.Length != 0) + { diffs.Splice(pointer, 0, - new Diff(Operation.DELETE, text_delete)); + new Diff(Operation.DELETE, text_delete)); pointer++; } - if (text_insert.Length != 0) { + + if (text_insert.Length != 0) + { diffs.Splice(pointer, 0, - new Diff(Operation.INSERT, text_insert)); + new Diff(Operation.INSERT, text_insert)); pointer++; } + pointer++; - } else if (pointer != 0 - && diffs[pointer - 1].operation == Operation.EQUAL) { + } + 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 { + } + else + { pointer++; } + count_insert = 0; count_delete = 0; text_delete = string.Empty; @@ -1246,8 +1505,10 @@ public void diff_cleanupMerge(List diffs) { break; } } - if (diffs[diffs.Count - 1].text.Length == 0) { - diffs.RemoveAt(diffs.Count - 1); // Remove the dummy entry at the end. + + 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 @@ -1256,35 +1517,43 @@ public void diff_cleanupMerge(List diffs) { bool changes = false; pointer = 1; // Intentionally ignore the first and last element (don't need checking). - while (pointer < (diffs.Count - 1)) { + while (pointer < (diffs.Count - 1)) + { if (diffs[pointer - 1].operation == Operation.EQUAL && - 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)) { + 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].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[pointer + 1].text; diffs.Splice(pointer - 1, 1); changes = true; - } else if (diffs[pointer].text.StartsWith(diffs[pointer + 1].text, - StringComparison.Ordinal)) { + } + 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[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) { + if (changes) + { this.diff_cleanupMerge(diffs); } } @@ -1297,33 +1566,44 @@ public void diff_cleanupMerge(List diffs) { * @param loc Location within text1. * @return Location within text2. */ - public int diff_xIndex(List diffs, int loc) { + 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) { + foreach (Diff aDiff in diffs) + { + if (aDiff.operation != Operation.INSERT) + { // Equality or deletion. chars1 += aDiff.text.Length; } - if (aDiff.operation != Operation.DELETE) { + + if (aDiff.operation != Operation.DELETE) + { // Equality or insertion. chars2 += aDiff.text.Length; } - if (chars1 > loc) { + + if (chars1 > loc) + { // Overshot the location. lastDiff = aDiff; break; } + last_chars1 = chars1; last_chars2 = chars2; } - if (lastDiff != null && lastDiff.operation == Operation.DELETE) { + + 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); } @@ -1333,25 +1613,29 @@ public int diff_xIndex(List diffs, int loc) { * @param diffs List of Diff objects. * @return HTML representation. */ - public string diff_prettyHtml(List diffs) { + public string diff_prettyHtml(List diffs) + { StringBuilder html = new StringBuilder(); - foreach (Diff aDiff in diffs) { + foreach (Diff aDiff in diffs) + { string text = aDiff.text.Replace("&", "&").Replace("<", "<") .Replace(">", ">").Replace("\n", "¶
"); - switch (aDiff.operation) { + switch (aDiff.operation) + { case Operation.INSERT: html.Append("").Append(text) - .Append(""); + .Append(""); break; case Operation.DELETE: html.Append("").Append(text) - .Append(""); + .Append(""); break; case Operation.EQUAL: html.Append("").Append(text).Append(""); break; } } + return html.ToString(); } @@ -1360,13 +1644,17 @@ public string diff_prettyHtml(List diffs) { * @param diffs List of Diff objects. * @return Source text. */ - public string diff_text1(List diffs) { + public string diff_text1(List diffs) + { StringBuilder text = new StringBuilder(); - foreach (Diff aDiff in diffs) { - if (aDiff.operation != Operation.INSERT) { + foreach (Diff aDiff in diffs) + { + if (aDiff.operation != Operation.INSERT) + { text.Append(aDiff.text); } } + return text.ToString(); } @@ -1375,13 +1663,17 @@ public string diff_text1(List diffs) { * @param diffs List of Diff objects. * @return Destination text. */ - public string diff_text2(List diffs) { + public string diff_text2(List diffs) + { StringBuilder text = new StringBuilder(); - foreach (Diff aDiff in diffs) { - if (aDiff.operation != Operation.DELETE) { + foreach (Diff aDiff in diffs) + { + if (aDiff.operation != Operation.DELETE) + { text.Append(aDiff.text); } } + return text.ToString(); } @@ -1391,12 +1683,15 @@ public string diff_text2(List diffs) { * @param diffs List of Diff objects. * @return Number of changes. */ - public int diff_levenshtein(List diffs) { + public int diff_levenshtein(List diffs) + { int levenshtein = 0; int insertions = 0; int deletions = 0; - foreach (Diff aDiff in diffs) { - switch (aDiff.operation) { + foreach (Diff aDiff in diffs) + { + switch (aDiff.operation) + { case Operation.INSERT: insertions += aDiff.text.Length; break; @@ -1411,6 +1706,7 @@ public int diff_levenshtein(List diffs) { break; } } + levenshtein += Math.Max(insertions, deletions); return levenshtein; } @@ -1424,10 +1720,13 @@ public int diff_levenshtein(List diffs) { * @param diffs Array of Diff objects. * @return Delta text. */ - public string diff_toDelta(List diffs) { + public string diff_toDelta(List diffs) + { StringBuilder text = new StringBuilder(); - foreach (Diff aDiff in diffs) { - switch (aDiff.operation) { + foreach (Diff aDiff in diffs) + { + switch (aDiff.operation) + { case Operation.INSERT: text.Append("+").Append(encodeURI(aDiff.text)).Append("\t"); break; @@ -1439,11 +1738,14 @@ public string diff_toDelta(List diffs) { break; } } + string delta = text.ToString(); - if (delta.Length != 0) { + if (delta.Length != 0) + { // Strip off trailing tab character. delta = delta.Substring(0, delta.Length - 1); } + return delta; } @@ -1455,20 +1757,25 @@ public string diff_toDelta(List diffs) { * @return Array of Diff objects or null if invalid. * @throws ArgumentException If invalid input. */ - public List diff_fromDelta(string text1, string delta) { + 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) { + 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]) { + switch (token[0]) + { case '+': // decode would change all "+" to " " param = param.Replace("+", "%2b"); @@ -1485,44 +1792,61 @@ public List diff_fromDelta(string text1, string delta) { diffs.Add(new Diff(Operation.INSERT, param)); break; case '-': - // Fall through. + // Fall through. case '=': int n; - try { + try + { n = Convert.ToInt32(param); - } catch (FormatException e) { + } + catch (FormatException e) + { throw new ArgumentException( - "Invalid number in diff_fromDelta: " + param, e); + "Invalid number in diff_fromDelta: " + param, e); } - if (n < 0) { + + if (n < 0) + { throw new ArgumentException( - "Negative number in diff_fromDelta: " + param); + "Negative number in diff_fromDelta: " + param); } + string text; - try { + try + { text = text1.Substring(pointer, n); pointer += n; - } catch (ArgumentOutOfRangeException e) { + } + catch (ArgumentOutOfRangeException e) + { throw new ArgumentException("Delta length (" + pointer - + ") larger than source text length (" + text1.Length - + ").", e); + + ") larger than source text length (" + text1.Length + + ").", e); } - if (token[0] == '=') { + + if (token[0] == '=') + { diffs.Add(new Diff(Operation.EQUAL, text)); - } else { + } + 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]); + "Invalid diff operation in diff_fromDelta: " + token[0]); } } - if (pointer != text1.Length) { + + if (pointer != text1.Length) + { throw new ArgumentException("Delta length (" + pointer - + ") smaller than source text length (" + text1.Length + ")."); + + ") smaller than source text length (" + text1.Length + ")."); } + return diffs; } @@ -1538,21 +1862,29 @@ public List diff_fromDelta(string text1, string delta) { * @param loc The location to search around. * @return Best match index or -1. */ - public int match_main(string text, string pattern, int loc) { + 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) { + if (text == pattern) + { // Shortcut (potentially not guaranteed by the algorithm) return 0; - } else if (text.Length == 0) { + } + else if (text.Length == 0) + { // Nothing to match. return -1; - } else if (loc + pattern.Length <= text.Length - && text.Substring(loc, pattern.Length) == pattern) { + } + 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 { + } + else + { // Do a fuzzy compare. return match_bitap(text, pattern, loc); } @@ -1566,7 +1898,8 @@ public int match_main(string text, string pattern, int loc) { * @param loc The location to search around. * @return Best match index or -1. */ - protected int match_bitap(string text, string pattern, int loc) { + protected int match_bitap(string text, string pattern, int loc) + { // assert (Match_MaxBits == 0 || pattern.Length <= Match_MaxBits) // : "Pattern too long for this application."; @@ -1577,16 +1910,18 @@ protected int match_bitap(string text, string pattern, int loc) { 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) { + if (best_loc != -1) + { score_threshold = Math.Min(match_bitapScore(0, best_loc, loc, - pattern), score_threshold); + 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) { + 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); + pattern), score_threshold); } } @@ -1598,21 +1933,28 @@ protected int match_bitap(string text, string pattern, int loc) { 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++) { + 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) { + while (bin_min < bin_mid) + { if (match_bitapScore(d, loc + bin_mid, loc, pattern) - <= score_threshold) { + <= score_threshold) + { bin_min = bin_mid; - } else { + } + 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); @@ -1620,46 +1962,64 @@ protected int match_bitap(string text, string pattern, int loc) { int[] rd = new int[finish + 2]; rd[finish + 1] = (1 << d) - 1; - for (int j = finish; j >= start; j--) { + for (int j = finish; j >= start; j--) + { int charMatch; - if (text.Length <= j - 1 || !s.ContainsKey(text[j - 1])) { + if (text.Length <= j - 1 || !s.ContainsKey(text[j - 1])) + { // Out of range. charMatch = 0; - } else { + } + else + { charMatch = s[text[j - 1]]; } - if (d == 0) { + + if (d == 0) + { // First pass: exact match. rd[j] = ((rd[j + 1] << 1) | 1) & charMatch; - } else { + } + 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]; + | (((last_rd[j + 1] | last_rd[j]) << 1) | 1) | last_rd[j + 1]; } - if ((rd[j] & matchmask) != 0) { + + 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) { + if (score <= score_threshold) + { // Told you so. score_threshold = score; best_loc = j - 1; - if (best_loc > loc) { + if (best_loc > loc) + { // When passing loc, don't exceed our current distance from loc. start = Math.Max(1, 2 * loc - best_loc); - } else { + } + else + { // Already passed loc, downhill from here on in. break; } } } } - if (match_bitapScore(d + 1, loc, loc, pattern) > score_threshold) { + + 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; } @@ -1671,13 +2031,16 @@ protected int match_bitap(string text, string pattern, int loc) { * @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; + 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) { + if (Match_Distance == 0) + { // Dodge divide by zero error. return proximity == 0 ? accuracy : 1.0; } + return accuracy + (proximity / (float) Match_Distance); } @@ -1686,20 +2049,26 @@ private double match_bitapScore(int e, int x, int loc, string pattern) { * @param pattern The text to encode. * @return Hash of character locations. */ - protected Dictionary match_alphabet(string pattern) { + 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)) { + foreach (char c in char_pattern) + { + if (!s.ContainsKey(c)) + { s.Add(c, 0); } } + int i = 0; - foreach (char c in char_pattern) { + foreach (char c in char_pattern) + { int value = s[c] | (1 << (pattern.Length - i - 1)); s[c] = value; i++; } + return s; } @@ -1713,35 +2082,43 @@ protected Dictionary match_alphabet(string pattern) { * @param patch The patch to grow. * @param text Source text. */ - protected void patch_addContext(Patch patch, string text) { - if (text.Length == 0) { + 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) { + != 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)); + 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) { + 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) { + Math.Min(text.Length, patch.start2 + patch.length1 + padding)); + if (suffix.Length != 0) + { patch.diffs.Add(new Diff(Operation.EQUAL, suffix)); } @@ -1760,14 +2137,17 @@ protected void patch_addContext(Patch patch, string text) { * @param text2 New text. * @return List of Patch objects. */ - public List patch_make(string text1, string text2) { + 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) { + if (diffs.Count > 2) + { diff_cleanupSemantic(diffs); diff_cleanupEfficiency(diffs); } + return patch_make(text1, diffs); } @@ -1777,7 +2157,8 @@ public List patch_make(string text1, string text2) { * @param diffs Array of Diff objects for text1 to text2. * @return List of Patch objects. */ - public List patch_make(List diffs) { + 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); @@ -1794,7 +2175,8 @@ public List patch_make(List diffs) { * @deprecated Prefer patch_make(string text1, List diffs). */ public List patch_make(string text1, string text2, - List diffs) { + List diffs) + { return patch_make(text1, diffs); } @@ -1805,28 +2187,34 @@ public List patch_make(string text1, string text2, * @param diffs Array of Diff objects for text1 to text2. * @return List of Patch objects. */ - public List patch_make(string text1, List diffs) { + 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. + 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. + 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) { + 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) { + switch (aDiff.operation) + { case Operation.INSERT: patch.diffs.Add(aDiff); patch.length2 += aDiff.text.Length; @@ -1836,20 +2224,23 @@ public List patch_make(string text1, List diffs) { patch.length1 += aDiff.text.Length; patch.diffs.Add(aDiff); postpatch_text = postpatch_text.Remove(char_count2, - aDiff.text.Length); + aDiff.text.Length); break; case Operation.EQUAL: if (aDiff.text.Length <= 2 * Patch_Margin - && patch.diffs.Count() != 0 && aDiff != diffs.Last()) { + && 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) { + if (aDiff.text.Length >= 2 * Patch_Margin) + { // Time for a new patch. - if (patch.diffs.Count != 0) { + if (patch.diffs.Count != 0) + { patch_addContext(patch, prepatch_text); patches.Add(patch); patch = new Patch(); @@ -1861,19 +2252,25 @@ public List patch_make(string text1, List diffs) { char_count1 = char_count2; } } + break; } // Update the current character count. - if (aDiff.operation != Operation.INSERT) { + if (aDiff.operation != Operation.INSERT) + { char_count1 += aDiff.text.Length; } - if (aDiff.operation != Operation.DELETE) { + + if (aDiff.operation != Operation.DELETE) + { char_count2 += aDiff.text.Length; } } + // Pick up the leftover patch if not empty. - if (patch.diffs.Count != 0) { + if (patch.diffs.Count != 0) + { patch_addContext(patch, prepatch_text); patches.Add(patch); } @@ -1886,20 +2283,25 @@ public List patch_make(string text1, List diffs) { * @param patches Array of Patch objects. * @return Array of Patch objects. */ - public List patch_deepCopy(List patches) { + public List patch_deepCopy(List patches) + { List patchesCopy = new List(); - foreach (Patch aPatch in patches) { + foreach (Patch aPatch in patches) + { Patch patchCopy = new Patch(); - foreach (Diff aDiff in aPatch.diffs) { + 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; } @@ -1911,9 +2313,11 @@ public List patch_deepCopy(List patches) { * @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] }; + 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. @@ -1930,86 +2334,115 @@ public Object[] patch_apply(List patches, string text) { // 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) { + 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) { + 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) { + 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) { + 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 { + } + else + { start_loc = this.match_main(text, text1, expected_loc); } - if (start_loc == -1) { + + 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 { + } + else + { // Found a match. :) results[x] = true; delta = start_loc - expected_loc; string text2; - if (end_loc == -1) { + if (end_loc == -1) + { text2 = text.JavaSubstring(start_loc, - Math.Min(start_loc + text1.Length, text.Length)); - } else { + Math.Min(start_loc + text1.Length, text.Length)); + } + else + { text2 = text.JavaSubstring(start_loc, - Math.Min(end_loc + this.Match_MaxBits, text.Length)); + Math.Min(end_loc + this.Match_MaxBits, text.Length)); } - if (text1 == text2) { + + 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 { + + 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) { + > this.Patch_DeleteThreshold) + { // The end points match, but the content is unacceptably bad. results[x] = false; - } else { + } + else + { diff_cleanupSemanticLossless(diffs); int index1 = 0; - foreach (Diff aDiff in aPatch.diffs) { - if (aDiff.operation != Operation.EQUAL) { + foreach (Diff aDiff in aPatch.diffs) + { + if (aDiff.operation != Operation.EQUAL) + { int index2 = diff_xIndex(diffs, index1); - if (aDiff.operation == Operation.INSERT) { + if (aDiff.operation == Operation.INSERT) + { // Insertion text = text.Insert(start_loc + index2, aDiff.text); - } else if (aDiff.operation == Operation.DELETE) { + } + else if (aDiff.operation == Operation.DELETE) + { // Deletion text = text.Remove(start_loc + index2, diff_xIndex(diffs, - index1 + aDiff.text.Length) - index2); + index1 + aDiff.text.Length) - index2); } } - if (aDiff.operation != Operation.DELETE) { + + 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 }; + - 2 * nullPadding.Length); + return new Object[] {text, results}; } /** @@ -2018,15 +2451,18 @@ public Object[] patch_apply(List patches, string text) { * @param patches Array of Patch objects. * @return The padding string added to each side. */ - public string patch_addPadding(List patches) { + 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; + for (short x = 1; x <= paddingLength; x++) + { + nullPadding += (char) x; } // Bump all the patches forward. - foreach (Patch aPatch in patches) { + foreach (Patch aPatch in patches) + { aPatch.start1 += paddingLength; aPatch.start2 += paddingLength; } @@ -2034,19 +2470,22 @@ public string patch_addPadding(List patches) { // 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) { + 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.start1 -= paddingLength; // Should be 0. + patch.start2 -= paddingLength; // Should be 0. patch.length1 += paddingLength; patch.length2 += paddingLength; - } else if (paddingLength > diffs.First().text.Length) { + } + 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; + + firstDiff.text; patch.start1 -= extraLength; patch.start2 -= extraLength; patch.length1 += extraLength; @@ -2056,12 +2495,15 @@ public string patch_addPadding(List patches) { // Add some padding on end of last diff. patch = patches.Last(); diffs = patch.diffs; - if (diffs.Count == 0 || diffs.Last().operation != Operation.EQUAL) { + 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) { + } + else if (paddingLength > diffs.Last().text.Length) + { // Grow last equality. Diff lastDiff = diffs.Last(); int extraLength = paddingLength - lastDiff.text.Length; @@ -2079,95 +2521,125 @@ public string patch_addPadding(List patches) { * Intended to be called only from within patch_apply. * @param patches List of Patch objects. */ - public void patch_splitMax(List patches) { + 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) { + 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) { + 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) { + 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) { + && 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) { + 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) { + } + 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 { + } + 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_size - patch.length1 - Patch_Margin)); patch.length1 += diff_text.Length; start1 += diff_text.Length; - if (diff_type == Operation.EQUAL) { + if (diff_type == Operation.EQUAL) + { patch.length2 += diff_text.Length; start2 += diff_text.Length; - } else { + } + else + { empty = false; } + patch.diffs.Add(new Diff(diff_type, diff_text)); - if (diff_text == bigpatch.diffs[0].text) { + if (diff_text == bigpatch.diffs[0].text) + { bigpatch.diffs.RemoveAt(0); - } else { + } + else + { bigpatch.diffs[0].text = - bigpatch.diffs[0].text.Substring(diff_text.Length); + 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)); + precontext.Length - this.Patch_Margin)); string postcontext = null; // Append the end context for this patch. - if (diff_text1(bigpatch.diffs).Length > Patch_Margin) { + if (diff_text1(bigpatch.diffs).Length > Patch_Margin) + { postcontext = diff_text1(bigpatch.diffs) - .Substring(0, Patch_Margin); - } else { + .Substring(0, Patch_Margin); + } + else + { postcontext = diff_text1(bigpatch.diffs); } - if (postcontext.Length != 0) { + 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) { + == Operation.EQUAL) + { patch.diffs[patch.diffs.Count - 1].text += postcontext; - } else { + } + else + { patch.diffs.Add(new Diff(Operation.EQUAL, postcontext)); } } - if (!empty) { + + if (!empty) + { patches.Splice(++x, 0, patch); } } @@ -2179,11 +2651,14 @@ public void patch_splitMax(List patches) { * @param patches List of Patch objects. * @return Text representation of patches. */ - public string patch_toText(List patches) { + public string patch_toText(List patches) + { StringBuilder text = new StringBuilder(); - foreach (Patch aPatch in patches) { + foreach (Patch aPatch in patches) + { text.Append(aPatch); } + return text.ToString(); } @@ -2194,81 +2669,114 @@ public string patch_toText(List patches) { * @return List of Patch objects. * @throws ArgumentException If invalid input. */ - public List patch_fromText(string textline) { + public List patch_fromText(string textline) + { List patches = new List(); - if (textline.Length == 0) { + if (textline.Length == 0) + { return patches; } + string[] text = textline.Split('\n'); int textPointer = 0; Patch patch; Regex patchHeader - = new Regex("^@@ -(\\d+),?(\\d*) \\+(\\d+),?(\\d*) @@$"); + = new Regex("^@@ -(\\d+),?(\\d*) \\+(\\d+),?(\\d*) @@$"); Match m; char sign; string line; - while (textPointer < text.Length) { + while (textPointer < text.Length) + { m = patchHeader.Match(text[textPointer]); - if (!m.Success) { + if (!m.Success) + { throw new ArgumentException("Invalid patch string: " - + text[textPointer]); + + text[textPointer]); } + patch = new Patch(); patches.Add(patch); patch.start1 = Convert.ToInt32(m.Groups[1].Value); - if (m.Groups[2].Length == 0) { + if (m.Groups[2].Length == 0) + { patch.start1--; patch.length1 = 1; - } else if (m.Groups[2].Value == "0") { + } + else if (m.Groups[2].Value == "0") + { patch.length1 = 0; - } else { + } + 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) { + if (m.Groups[4].Length == 0) + { patch.start2--; patch.length2 = 1; - } else if (m.Groups[4].Value == "0") { + } + else if (m.Groups[4].Value == "0") + { patch.length2 = 0; - } else { + } + else + { patch.start2--; patch.length2 = Convert.ToInt32(m.Groups[4].Value); } + textPointer++; - while (textPointer < text.Length) { - try { + while (textPointer < text.Length) + { + try + { sign = text[textPointer][0]; - } catch (IndexOutOfRangeException) { + } + catch (IndexOutOfRangeException) + { // Blank line? Whatever. textPointer++; continue; } + line = text[textPointer].Substring(1); line = line.Replace("+", "%2b"); line = Uri.UnescapeDataString(line); - if (sign == '-') { + if (sign == '-') + { // Deletion. patch.diffs.Add(new Diff(Operation.DELETE, line)); - } else if (sign == '+') { + } + else if (sign == '+') + { // Insertion. patch.diffs.Add(new Diff(Operation.INSERT, line)); - } else if (sign == ' ') { + } + else if (sign == ' ') + { // Minor equality. patch.diffs.Add(new Diff(Operation.EQUAL, line)); - } else if (sign == '@') { + } + else if (sign == '@') + { // Start of next patch. break; - } else { + } + else + { // WTF? throw new ArgumentException( - "Invalid patch mode '" + sign + "' in: " + line); + "Invalid patch mode '" + sign + "' in: " + line); } + textPointer++; } } + return patches; } @@ -2281,29 +2789,33 @@ Regex patchHeader * @param str The string to encode. * @return The encoded string. */ - public static string encodeURI(string str) { + public static string encodeURI(string str) + { int MAX_LENGTH = 65520 - 1; // C# throws a System.UriFormatException if string is too long. // Split the string into 64kb chunks. StringBuilder sb = new StringBuilder(); - while (str.Length > MAX_LENGTH) { + while (str.Length > MAX_LENGTH) + { sb.Append(Uri.EscapeDataString(str.Substring(0, MAX_LENGTH))); str = str.Substring(MAX_LENGTH); } + sb.Append(Uri.EscapeDataString(str)); str = sb.ToString(); // C# is overzealous in the replacements. Walk back on a few. str = 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("%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", "#"); // C# uses uppercase hex codes, JavaScript uses lowercase. return HEXCODE.Replace(str, new MatchEvaluator(lowerHex)); } - private static string lowerHex(Match m) { + private static string lowerHex(Match m) + { return m.ToString().ToLower(); } } From a9db7b77d9cc9aedbd51fe6718b38978701bfcb6 Mon Sep 17 00:00:00 2001 From: rolshevsky Date: Fri, 20 Jul 2018 18:01:37 +0300 Subject: [PATCH 3/8] add DiffMatchPatch.Tests.Performance project --- .../DiffMatchPatch.Tests.Performance.csproj | 11 +++++++++++ .../Speedtest.cs | 11 ++++++----- .../Speedtest1.txt | 0 .../Speedtest2.txt | 0 csharp/DiffMatchPatch.sln | 6 ++++++ 5 files changed, 23 insertions(+), 5 deletions(-) create mode 100644 csharp/DiffMatchPatch.Tests.Performance/DiffMatchPatch.Tests.Performance.csproj rename csharp/{DiffMatchPatch.Tests => DiffMatchPatch.Tests.Performance}/Speedtest.cs (90%) rename csharp/{DiffMatchPatch.Tests => DiffMatchPatch.Tests.Performance}/Speedtest1.txt (100%) rename csharp/{DiffMatchPatch.Tests => DiffMatchPatch.Tests.Performance}/Speedtest2.txt (100%) 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..119b0a7 --- /dev/null +++ b/csharp/DiffMatchPatch.Tests.Performance/DiffMatchPatch.Tests.Performance.csproj @@ -0,0 +1,11 @@ + + + netstandard2.0 + + + + + + + + \ No newline at end of file diff --git a/csharp/DiffMatchPatch.Tests/Speedtest.cs b/csharp/DiffMatchPatch.Tests.Performance/Speedtest.cs similarity index 90% rename from csharp/DiffMatchPatch.Tests/Speedtest.cs rename to csharp/DiffMatchPatch.Tests.Performance/Speedtest.cs index 6d6eb12..d457a33 100644 --- a/csharp/DiffMatchPatch.Tests/Speedtest.cs +++ b/csharp/DiffMatchPatch.Tests.Performance/Speedtest.cs @@ -16,11 +16,11 @@ * limitations under the License. */ -using System; -using System.Collections.Generic; - -public class Speedtest { - /*TODO Replace with BenchmarkDotNet +namespace DiffMatchPatch.Tests.Performance +{ + public class Speedtest + { + /*TODO Replace with BenchmarkDotNet public static void Main(string[] args) { string text1 = System.IO.File.ReadAllText("Speedtest1.txt"); string text2 = System.IO.File.ReadAllText("Speedtest2.txt"); @@ -39,4 +39,5 @@ public static void Main(string[] args) { Console.WriteLine("Elapsed time: " + (ms_end - ms_start)); }*/ + } } diff --git a/csharp/DiffMatchPatch.Tests/Speedtest1.txt b/csharp/DiffMatchPatch.Tests.Performance/Speedtest1.txt similarity index 100% rename from csharp/DiffMatchPatch.Tests/Speedtest1.txt rename to csharp/DiffMatchPatch.Tests.Performance/Speedtest1.txt diff --git a/csharp/DiffMatchPatch.Tests/Speedtest2.txt b/csharp/DiffMatchPatch.Tests.Performance/Speedtest2.txt similarity index 100% rename from csharp/DiffMatchPatch.Tests/Speedtest2.txt rename to csharp/DiffMatchPatch.Tests.Performance/Speedtest2.txt diff --git a/csharp/DiffMatchPatch.sln b/csharp/DiffMatchPatch.sln index 987a64e..2d5d512 100644 --- a/csharp/DiffMatchPatch.sln +++ b/csharp/DiffMatchPatch.sln @@ -4,6 +4,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DiffMatchPatch", "DiffMatch 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", "{469BF78D-612C-49FA-B308-B5DEDB3138C2}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -18,5 +20,9 @@ Global {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 + {469BF78D-612C-49FA-B308-B5DEDB3138C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {469BF78D-612C-49FA-B308-B5DEDB3138C2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {469BF78D-612C-49FA-B308-B5DEDB3138C2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {469BF78D-612C-49FA-B308-B5DEDB3138C2}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal From 889e61ddbf8392b4c3ec72bc98b5a40f36bd3328 Mon Sep 17 00:00:00 2001 From: rolshevsky Date: Fri, 20 Jul 2018 21:42:22 +0300 Subject: [PATCH 4/8] add BenchmarkDotNet perf test --- .../{Speedtest.cs => DiffMainTest.cs} | 37 ++++++++++--------- .../DiffMatchPatch.Tests.Performance.csproj | 13 ++++++- .../Program.cs | 30 +++++++++++++++ csharp/DiffMatchPatch.Tests/DiffTests.cs | 3 -- csharp/DiffMatchPatch.sln | 10 ++--- 5 files changed, 66 insertions(+), 27 deletions(-) rename csharp/DiffMatchPatch.Tests.Performance/{Speedtest.cs => DiffMainTest.cs} (54%) create mode 100644 csharp/DiffMatchPatch.Tests.Performance/Program.cs diff --git a/csharp/DiffMatchPatch.Tests.Performance/Speedtest.cs b/csharp/DiffMatchPatch.Tests.Performance/DiffMainTest.cs similarity index 54% rename from csharp/DiffMatchPatch.Tests.Performance/Speedtest.cs rename to csharp/DiffMatchPatch.Tests.Performance/DiffMainTest.cs index d457a33..28b192b 100644 --- a/csharp/DiffMatchPatch.Tests.Performance/Speedtest.cs +++ b/csharp/DiffMatchPatch.Tests.Performance/DiffMainTest.cs @@ -16,28 +16,31 @@ * limitations under the License. */ +using System.Collections.Generic; +using System.IO; +using BenchmarkDotNet.Attributes; +using Google.DiffMatchPatch; + namespace DiffMatchPatch.Tests.Performance { - public class Speedtest + public class DiffMainTest { - /*TODO Replace with BenchmarkDotNet - 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; + private string _text1; + private string _text2; - // Execute one reverse diff as a warmup. - dmp.diff_main(text2, text1); - GC.Collect(); - GC.WaitForPendingFinalizers(); + [GlobalSetup] + public void Init() + { + _text1 = File.ReadAllText("Speedtest1.txt"); + _text2 = File.ReadAllText("Speedtest2.txt"); + } - DateTime ms_start = DateTime.Now; - dmp.diff_main(text1, text2); - DateTime ms_end = DateTime.Now; + [Benchmark] + public List DiffMain() + { + var dmp = new diff_match_patch {Diff_Timeout = 0}; - Console.WriteLine("Elapsed time: " + (ms_end - ms_start)); - }*/ + 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 index 119b0a7..3bfd344 100644 --- a/csharp/DiffMatchPatch.Tests.Performance/DiffMatchPatch.Tests.Performance.csproj +++ b/csharp/DiffMatchPatch.Tests.Performance/DiffMatchPatch.Tests.Performance.csproj @@ -1,11 +1,20 @@  - netstandard2.0 + Exe + netcoreapp2.1 + + + - + + 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..0f674fe --- /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 DiffMatchPatch.Tests.Performance +{ + internal static class Program + { + private static void Main(string[] args) + { + var summary = BenchmarkRunner.Run(); + } + } +} diff --git a/csharp/DiffMatchPatch.Tests/DiffTests.cs b/csharp/DiffMatchPatch.Tests/DiffTests.cs index 4ece9db..9b1a0c4 100644 --- a/csharp/DiffMatchPatch.Tests/DiffTests.cs +++ b/csharp/DiffMatchPatch.Tests/DiffTests.cs @@ -778,9 +778,6 @@ public void DeltaTest() // Generates error (19 > 18). Assert.Throws(() => diff_fromDelta(text1.Substring(1), delta)); - // Generates error (%c3%xy invalid Unicode). - //TODO:rolshevsky:Assert.Throws(() => diff_fromDelta("", "+%c3%xy")); - // Test deltas with special characters. const char zero = (char) 0; const char one = (char) 1; diff --git a/csharp/DiffMatchPatch.sln b/csharp/DiffMatchPatch.sln index 2d5d512..e719ac8 100644 --- a/csharp/DiffMatchPatch.sln +++ b/csharp/DiffMatchPatch.sln @@ -4,7 +4,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DiffMatchPatch", "DiffMatch 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", "{469BF78D-612C-49FA-B308-B5DEDB3138C2}" +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 @@ -20,9 +20,9 @@ Global {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 - {469BF78D-612C-49FA-B308-B5DEDB3138C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {469BF78D-612C-49FA-B308-B5DEDB3138C2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {469BF78D-612C-49FA-B308-B5DEDB3138C2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {469BF78D-612C-49FA-B308-B5DEDB3138C2}.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 From 6723e99ca400ce99602a40584e9a7097b958bfb2 Mon Sep 17 00:00:00 2001 From: rolshevsky Date: Fri, 20 Jul 2018 21:57:34 +0300 Subject: [PATCH 5/8] fix namespace --- csharp/DiffMatchPatch.Tests.Performance/DiffMainTest.cs | 3 +-- .../DiffMatchPatch.Tests.Performance.csproj | 1 + csharp/DiffMatchPatch.Tests.Performance/Program.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/csharp/DiffMatchPatch.Tests.Performance/DiffMainTest.cs b/csharp/DiffMatchPatch.Tests.Performance/DiffMainTest.cs index 28b192b..11c6ac3 100644 --- a/csharp/DiffMatchPatch.Tests.Performance/DiffMainTest.cs +++ b/csharp/DiffMatchPatch.Tests.Performance/DiffMainTest.cs @@ -19,9 +19,8 @@ using System.Collections.Generic; using System.IO; using BenchmarkDotNet.Attributes; -using Google.DiffMatchPatch; -namespace DiffMatchPatch.Tests.Performance +namespace Google.DiffMatchPatch.Tests.Performance { public class DiffMainTest { diff --git a/csharp/DiffMatchPatch.Tests.Performance/DiffMatchPatch.Tests.Performance.csproj b/csharp/DiffMatchPatch.Tests.Performance/DiffMatchPatch.Tests.Performance.csproj index 3bfd344..ee6a5e4 100644 --- a/csharp/DiffMatchPatch.Tests.Performance/DiffMatchPatch.Tests.Performance.csproj +++ b/csharp/DiffMatchPatch.Tests.Performance/DiffMatchPatch.Tests.Performance.csproj @@ -2,6 +2,7 @@ Exe netcoreapp2.1 + Google.DiffMatchPatch.Tests.Performance diff --git a/csharp/DiffMatchPatch.Tests.Performance/Program.cs b/csharp/DiffMatchPatch.Tests.Performance/Program.cs index 0f674fe..0dcd0a8 100644 --- a/csharp/DiffMatchPatch.Tests.Performance/Program.cs +++ b/csharp/DiffMatchPatch.Tests.Performance/Program.cs @@ -18,7 +18,7 @@ using BenchmarkDotNet.Running; -namespace DiffMatchPatch.Tests.Performance +namespace Google.DiffMatchPatch.Tests.Performance { internal static class Program { From ac07277dd5e0d5b289052f136ee63f397dcf887a Mon Sep 17 00:00:00 2001 From: rolshevsky Date: Tue, 2 Oct 2018 18:08:22 +0300 Subject: [PATCH 6/8] restore latest DiffMatchPatch.cs --- csharp/DiffMatchPatch.cs | 2296 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 2296 insertions(+) create mode 100644 csharp/DiffMatchPatch.cs diff --git a/csharp/DiffMatchPatch.cs b/csharp/DiffMatchPatch.cs new file mode 100644 index 0000000..6e19641 --- /dev/null +++ b/csharp/DiffMatchPatch.cs @@ -0,0 +1,2296 @@ +/* + * 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 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(); + } + } +} From 5279c0bbfea2e6642446643ecb5ded82a012d419 Mon Sep 17 00:00:00 2001 From: rolshevsky Date: Tue, 2 Oct 2018 18:14:58 +0300 Subject: [PATCH 7/8] allign DiffMatchPatch.cs with latest changes --- csharp/DiffMatchPatch/DiffMatchPatch.cs | 41 ++++++++----------------- 1 file changed, 12 insertions(+), 29 deletions(-) diff --git a/csharp/DiffMatchPatch/DiffMatchPatch.cs b/csharp/DiffMatchPatch/DiffMatchPatch.cs index 36fb337..15a4e9a 100644 --- a/csharp/DiffMatchPatch/DiffMatchPatch.cs +++ b/csharp/DiffMatchPatch/DiffMatchPatch.cs @@ -21,6 +21,7 @@ using System.Linq; using System.Text; using System.Text.RegularExpressions; +using System.Web; namespace Google.DiffMatchPatch { @@ -1780,7 +1781,7 @@ public List diff_fromDelta(string text1, string delta) // decode would change all "+" to " " param = param.Replace("+", "%2b"); - param = Uri.UnescapeDataString(param); + param = HttpUtility.UrlDecode(param); //} catch (UnsupportedEncodingException e) { // // Not likely on modern system. // throw new Error("This system does not support UTF-8.", e); @@ -2745,7 +2746,7 @@ Regex patchHeader line = text[textPointer].Substring(1); line = line.Replace("+", "%2b"); - line = Uri.UnescapeDataString(line); + line = HttpUtility.UrlDecode(line); if (sign == '-') { // Deletion. @@ -2780,8 +2781,6 @@ Regex patchHeader return patches; } - private static Regex HEXCODE = new Regex("%[0-9A-F][0-9A-F]"); - /** * Encodes a string with URI-style % escaping. * Compatible with JavaScript's encodeURI function. @@ -2791,32 +2790,16 @@ Regex patchHeader */ public static string encodeURI(string str) { - int MAX_LENGTH = 65520 - 1; - // C# throws a System.UriFormatException if string is too long. - // Split the string into 64kb chunks. - StringBuilder sb = new StringBuilder(); - while (str.Length > MAX_LENGTH) - { - sb.Append(Uri.EscapeDataString(str.Substring(0, MAX_LENGTH))); - str = str.Substring(MAX_LENGTH); - } - - sb.Append(Uri.EscapeDataString(str)); - str = sb.ToString(); // C# is overzealous in the replacements. Walk back on a few. - str = 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", "#"); - // C# uses uppercase hex codes, JavaScript uses lowercase. - return HEXCODE.Replace(str, new MatchEvaluator(lowerHex)); - } - - private static string lowerHex(Match m) - { - return m.ToString().ToLower(); + 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(); } } } From f2fd64db15bfc658c8b4f4aa0afe4c07767469d8 Mon Sep 17 00:00:00 2001 From: rolshevsky Date: Tue, 2 Oct 2018 18:28:18 +0300 Subject: [PATCH 8/8] mark csharp/DiffMatchPath.cs as "Obsolete" --- csharp/DiffMatchPatch.cs | 4 ++++ 1 file changed, 4 insertions(+) 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.