Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Diff opeation with lines context #22

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions HtmlDiff.core/HtmlDiff.core.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>net45;netcoreapp2.2</TargetFrameworks>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<PackageProjectUrl>https://github.com/brianpos/htmldiff.net</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/brianpos/htmldiff.net/blob/master/license.txt</PackageLicenseUrl>
<Description>A library for comparing two HTML files/snippets and highlighting the differences using simple HTML.
(this is a fork of Rohland de Charmoy's HtmlDiff library, with lines of context to include, and compiled for netcore2/net45)</Description>
<Authors>Brian Postlethwaite</Authors>
<RepositoryUrl>https://github.com/brianpos/htmldiff.net</RepositoryUrl>
<RepositoryType>github</RepositoryType>
<PackageReleaseNotes>This is a slightly updated version that better supports XML diffs, with a context number of lines</PackageReleaseNotes>
<Company />
<Version>1.2.0</Version>
<Product>HtmlDiff</Product>
</PropertyGroup>

<ItemGroup>
<Compile Include="..\HtmlDiff\Action.cs" Link="Action.cs" />
<Compile Include="..\HtmlDiff\Diff.cs" Link="Diff.cs" />
<Compile Include="..\HtmlDiff\Match.cs" Link="Match.cs" />
<Compile Include="..\HtmlDiff\MatchFinder.cs" Link="MatchFinder.cs" />
<Compile Include="..\HtmlDiff\MatchOptions.cs" Link="MatchOptions.cs" />
<Compile Include="..\HtmlDiff\Mode.cs" Link="Mode.cs" />
<Compile Include="..\HtmlDiff\Operation.cs" Link="Operation.cs" />
<Compile Include="..\HtmlDiff\Utils.cs" Link="Utils.cs" />
<Compile Include="..\HtmlDiff\WordSplitter.cs" Link="WordSplitter.cs" />
</ItemGroup>

</Project>
21 changes: 16 additions & 5 deletions HtmlDiff.sln
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 11
# Visual Studio 15
VisualStudioVersion = 15.0.27004.2006
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HtmlDiff", "HtmlDiff\HtmlDiff.csproj", "{186B7471-867E-4AE8-9BD2-1DDA8BF76BC0}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Demo", "Demo\Demo.csproj", "{8827AB5E-7C08-49DE-BF3C-C2D5B7AD4990}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Test.HtmlDiff", "Test.HtmlDiff\Test.HtmlDiff.csproj", "{87C1F5ED-A79B-410F-9BBF-4D113A1A0090}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HtmlDiff.core", "HtmlDiff.core\HtmlDiff.core.csproj", "{61B0724C-D7DE-485C-895F-4D651D1D186D}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -17,16 +21,23 @@ Global
{186B7471-867E-4AE8-9BD2-1DDA8BF76BC0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{186B7471-867E-4AE8-9BD2-1DDA8BF76BC0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{186B7471-867E-4AE8-9BD2-1DDA8BF76BC0}.Release|Any CPU.Build.0 = Release|Any CPU
{87C1F5ED-A79B-410F-9BBF-4D113A1A0090}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{87C1F5ED-A79B-410F-9BBF-4D113A1A0090}.Debug|Any CPU.Build.0 = Debug|Any CPU
{87C1F5ED-A79B-410F-9BBF-4D113A1A0090}.Release|Any CPU.ActiveCfg = Release|Any CPU
{87C1F5ED-A79B-410F-9BBF-4D113A1A0090}.Release|Any CPU.Build.0 = Release|Any CPU
{8827AB5E-7C08-49DE-BF3C-C2D5B7AD4990}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8827AB5E-7C08-49DE-BF3C-C2D5B7AD4990}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8827AB5E-7C08-49DE-BF3C-C2D5B7AD4990}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8827AB5E-7C08-49DE-BF3C-C2D5B7AD4990}.Release|Any CPU.Build.0 = Release|Any CPU
{87C1F5ED-A79B-410F-9BBF-4D113A1A0090}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{87C1F5ED-A79B-410F-9BBF-4D113A1A0090}.Debug|Any CPU.Build.0 = Debug|Any CPU
{87C1F5ED-A79B-410F-9BBF-4D113A1A0090}.Release|Any CPU.ActiveCfg = Release|Any CPU
{87C1F5ED-A79B-410F-9BBF-4D113A1A0090}.Release|Any CPU.Build.0 = Release|Any CPU
{61B0724C-D7DE-485C-895F-4D651D1D186D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{61B0724C-D7DE-485C-895F-4D651D1D186D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{61B0724C-D7DE-485C-895F-4D651D1D186D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{61B0724C-D7DE-485C-895F-4D651D1D186D}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {BDE3B067-BFB3-4D49-AD39-C7A0D4491B59}
EndGlobalSection
EndGlobal
103 changes: 95 additions & 8 deletions HtmlDiff/Diff.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,23 @@ public class HtmlDiff
/// </summary>
public bool IgnoreWhitespaceDifferences { get; set; }

/// <summary>
/// If greater than 0, the text will be broken into div sections that contain
/// the number of lines before and after each change.
/// Where changes are closer than the context, a new section is not started, and all content output
/// If there are no changes, then a null string is returned.
/// </summary>
/// <example>LinesContext = 1 // Only the line with the change is returned</example>
/// <example>LinesContext = 4 // The line with the change is returned and up to 3 lines before and after</example>
public int LinesContext { get; set; }

/// <summary>
/// When using the Context option, the name of the class that is injected into the groupers
/// </summary>
/// <remarks>If LinesContext is provided, and no ContextSelectionClass is </remarks>
/// <example></example>
public string ContextSectionClass { get; set; }

/// <summary>
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if it would be neater to drop these properties in favour of a dedicated public method to process the summary.

Example:

public string BuildSummary(int numberOfLinesToIncludeAroundChange = 0, string diffContainerCSSClass = "diffsection")
{
  // ...
}

This makes it easier for callers to understand the configuration options available to them when building a summary of changes.

/// If some match is too small and located far from its neighbors then it is considered as orphan
/// and removed. For example:
Expand Down Expand Up @@ -92,6 +109,7 @@ public class HtmlDiff
public HtmlDiff(string oldText, string newText)
{
RepeatingWordsAccuracy = 1d; //by default all repeating words should be compared
ContextSectionClass = "diffsection";

_oldText = oldText;
_newText = newText;
Expand All @@ -107,14 +125,16 @@ public static string Execute(string oldText, string newText)
}

/// <summary>
/// Builds the HTML diff output
/// Builds the HTML diff output (Note: If there is a context specified and the content is equal, then null will be returned)
/// </summary>
/// <returns>HTML diff markup</returns>
public string Build()
{
// If there is no difference, don't bother checking for differences
if (_oldText == _newText)
{
if (LinesContext > 0)
return null;
Copy link
Owner

@Rohland Rohland Sep 3, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure returning null is the best option. Perhaps an empty string would be better. This goes for the other null return values in the method.

return _newText;
}

Expand All @@ -124,14 +144,37 @@ public string Build()

List<Operation> operations = Operations();

foreach (Operation item in operations)
if ((operations.Count == 0 || operations.Count == 1 && operations[0].Action == Action.Equal) && LinesContext > 0)
return null;

if (operations.Count > 0 && operations[0].Action != Action.Equal && LinesContext > 0)
{
_startSectionAdded = true;
_content.AppendFormat("<div class=\"{0}\"> <!-- start diff section -->\r\n", ContextSectionClass);
}

bool changesAfter = false;
for (int nItem = 0; nItem < operations.Count; nItem++)
{
PerformOperation(item);
Operation item = operations[nItem];
changesAfter = false;
if (nItem + 1 < operations.Count && operations[nItem + 1].Action != Action.Equal)
changesAfter = true;
PerformOperation(item, changesAfter);
}

// Put in an end section
if (LinesContext > 0 && _startSectionAdded)
_content.Append("</div> <!-- end diff section -->\r\n\r\n");

// Remove any empty sections
if (LinesContext > 0)
_content.Replace(String.Format("<div class=\"{0}\"> <!-- start diff section -->\r\n</div> <!-- end diff section -->\r\n\r\n", ContextSectionClass), "");
return _content.ToString();
}

bool _startSectionAdded = false;

/// <summary>
/// Uses <paramref name="expression"/> to group text together so that any change detected within the group is treated as a single block
/// </summary>
Expand All @@ -154,16 +197,16 @@ private void SplitInputsToWords()
_newText = null;
}

private void PerformOperation(Operation operation)
private void PerformOperation(Operation operation, bool changesAfter)
{
#if DEBUG
operation.PrintDebugInfo(_oldWords, _newWords);
// operation.PrintDebugInfo(_oldWords, _newWords);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was this commented out by mistake?

#endif

switch (operation.Action)
{
case Action.Equal:
ProcessEqualOperation(operation);
ProcessEqualOperation(operation, changesAfter);
break;
case Action.Delete:
ProcessDeleteOperation(operation, "diffdel");
Expand Down Expand Up @@ -197,11 +240,55 @@ private void ProcessDeleteOperation(Operation operation, string cssClass)
InsertTag("del", cssClass, text);
}

private void ProcessEqualOperation(Operation operation)
private void ProcessEqualOperation(Operation operation, bool changesAfter)
{
string[] result =
_newWords.Where((s, pos) => pos >= operation.StartInNew && pos < operation.EndInNew).ToArray();
_content.Append(String.Join("", result));
if (LinesContext > 0)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider moving to a separate method.

if (LinesContext == 0)
{
  _content.Append(String.Join("", result)); 
}
else
{
  _content.Append(string.Join("", ExtractDiffSections(result));
}

{
int linesInSection = result.Count(m => m.StartsWith("\r\n"));
if (linesInSection > LinesContext * 2) // Only put breaks in if the gap is bigger than the size of the content
{
// Need to split the context down.
int lines=0;
foreach (var item in result)
{
if (lines < LinesContext || lines > linesInSection-LinesContext)
_content.Append(item);
if (item.StartsWith("\r\n"))
lines++;

// Close the previous context
if (lines == LinesContext && _startSectionAdded)
{
_startSectionAdded = false;
_content.Append("</div> <!-- end diff section -->\r\n\r\n");
if (!changesAfter)
return;
}

// Now start the new context
if (lines == linesInSection - LinesContext && !_startSectionAdded)
{
_startSectionAdded = true;
_content.AppendFormat("<div class=\"{0}\"> <!-- start diff section -->\r\n", ContextSectionClass);
}
}
}
else
{
if (!_startSectionAdded)
{
_startSectionAdded = true;
_content.AppendFormat("<div class=\"{0}\"> <!-- start diff section -->\r\n", ContextSectionClass);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is duplicated a few times. Perhaps introduce a static method to return the template for the start/end of sections. Even better would be to allow callers to specify their own template mechanism. i.e. Introduce an IDiffSummaryTemplate with a default implementation as per the original code above.

}
_content.Append(String.Join("", result));
}
}
else
{
_content.Append(String.Join("", result));
}
}


Expand Down
11 changes: 11 additions & 0 deletions HtmlDiff/HtmlDiff.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,11 @@
<Compile Include="Mode.cs" />
<Compile Include="Operation.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
<Compile Include="Utils.cs" />
<Compile Include="WordSplitter.cs" />
</ItemGroup>
Expand All @@ -67,6 +72,12 @@
<SubType>Designer</SubType>
</None>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
Expand Down
83 changes: 83 additions & 0 deletions HtmlDiff/Properties/Resources.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading