Browse Source

More robust equals implementation

pull/1918/head
Ynse Hoornenborg 4 years ago
parent
commit
fa3e6ff180
  1. 21
      src/ImageSharp/Metadata/Profiles/XMP/XElementEqualityComparer.cs
  2. 56
      src/ImageSharp/Metadata/Profiles/XMP/XmpProfile.cs
  3. 53
      tests/ImageSharp.Tests/Metadata/Profiles/XMP/XmpProfileTests.cs

21
src/ImageSharp/Metadata/Profiles/XMP/XElementEqualityComparer.cs

@ -0,0 +1,21 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Xml.Linq;
namespace SixLabors.ImageSharp.Metadata.Profiles.Xmp
{
/// <summary>
/// Compare <see cref="XElement"/> objects for Name and Value equality.
/// </summary>
public class XElementEqualityComparer : IEqualityComparer<XElement>
{
/// <inheritdoc />
public bool Equals([AllowNull] XElement x, [AllowNull] XElement y) => x.Name == y.Name && x.Value == y.Value;
/// <inheritdoc />
public int GetHashCode([DisallowNull] XElement obj) => obj.GetHashCode();
}
}

56
src/ImageSharp/Metadata/Profiles/XMP/XmpProfile.cs

@ -3,6 +3,7 @@
using System;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml.Linq;
@ -10,6 +11,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Xmp
{
/// <summary>
/// Represents an XMP profile, providing access to the raw XML.
/// See <seealso href="https://www.adobe.com/devnet/xmp.html"/> for the full specification.
/// </summary>
public sealed class XmpProfile : IDeepCloneable<XmpProfile>
{
@ -29,6 +31,16 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Xmp
/// <param name="data">The UTF8 encoded byte array to read the XMP profile from.</param>
public XmpProfile(byte[] data) => this.Data = data;
/// <summary>
/// Initializes a new instance of the <see cref="XmpProfile"/> class, based on the speicief <see cref="XDocument"/>.
/// </summary>
/// <param name="doc">The document to base this instance on.</param>
public XmpProfile(XDocument doc)
{
this.document = doc;
this.UpdateData();
}
/// <summary>
/// Initializes a new instance of the <see cref="XmpProfile"/> class
/// by making a copy from another XMP profile.
@ -78,7 +90,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Xmp
return true;
}
if (ReferenceEquals(left, null))
if (left is null)
{
return false;
}
@ -95,10 +107,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Xmp
/// True if the <paramref name="left"/> parameter is not equal to the <paramref name="right"/> parameter;
/// otherwise, false.
/// </returns>
public static bool operator !=(XmpProfile left, XmpProfile right)
{
return !(left == right);
}
public static bool operator !=(XmpProfile left, XmpProfile right) => !(left == right);
/// <inheritdoc/>
public XmpProfile DeepClone() => new(this);
@ -113,7 +122,13 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Xmp
return;
}
using var stream = new MemoryStream(this.Data.Length);
int initialLength = 256;
if (this.Data != null)
{
initialLength = this.Data.Length;
}
using var stream = new MemoryStream(initialLength);
using var writer = new StreamWriter(stream, Encoding.UTF8);
this.document.Save(writer);
this.Data = stream.ToArray();
@ -125,28 +140,37 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Xmp
/// <inheritdoc />
public override bool Equals(object obj)
{
XmpProfile other = obj as XmpProfile;
if (ReferenceEquals(other, null))
if (obj is not XmpProfile other)
{
return false;
}
if (ReferenceEquals(this.Data, null))
XElement thisRoot = this.Document.Root;
XElement otherRoot = other.Document.Root;
return this.CompareElements(thisRoot, otherRoot);
}
private bool CompareElements(XElement left, XElement right)
{
var comparer = new XElementEqualityComparer();
bool result = comparer.Equals(left, right);
if (result)
{
return false;
result |= !left.Elements().Except(right.Elements(), comparer).Any();
}
return this.Data.Equals(other.Data);
return result;
}
private void InitializeDocument()
{
if (this.document != null)
if (!(this.document is null))
{
return;
}
if (this.Data == null)
if (this.Data is null)
{
return;
}
@ -164,6 +188,12 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Xmp
using var stream = new MemoryStream(this.Data, 0, count);
using var reader = new StreamReader(stream, Encoding.UTF8);
this.document = XDocument.Load(reader);
// In case we removed any trailing bytes, update the Data property accordingly.
if (count != this.Data.Length)
{
this.UpdateData();
}
}
}
}

53
tests/ImageSharp.Tests/Metadata/Profiles/XMP/XmpProfileTests.cs

@ -1,6 +1,7 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.IO;
using System.Text;
using System.Xml.Linq;
@ -108,22 +109,36 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Xmp
}
[Fact]
public void XmpProfile_EqualalityIsByValue()
public void XmpProfile_EqualityIsByValue()
{
// arrange
byte[] content = new byte[0];
XmpProfile original = new XmpProfile(content);
XmpProfile other = new XmpProfile(content);
XmpProfile original = CreateMinimalXmlProfile();
var other = new XmpProfile(original.Data);
// act
var equals = original.Equals(other);
var equality = original == other;
var inequality = original != other;
bool equals = original.Equals(other);
bool equality = original == other;
bool inequality = original != other;
// assert
Assert.True(equals);
Assert.True(equality);
Assert.False(inequality);
Assert.Equal(original, other);
}
[Fact]
public void XmpProfile_DocumentConstructor()
{
// arrange
XmpProfile original = CreateMinimalXmlProfile();
// act
var actual = new XmpProfile(original.Document);
// assert
XmpProfileContainsExpectedValues(actual);
Assert.Equal(original, actual);
}
[Fact]
@ -147,7 +162,8 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Xmp
{
// arrange
var image = new Image<Rgba32>(1, 1);
image.Metadata.XmpProfile = CreateMinimalXmlProfile();
XmpProfile original = CreateMinimalXmlProfile();
image.Metadata.XmpProfile = original;
var encoder = new GifEncoder();
// act
@ -156,6 +172,7 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Xmp
// assert
XmpProfile actual = reloadedImage.Metadata.XmpProfile ?? reloadedImage.Frames.RootFrame.Metadata.XmpProfile;
XmpProfileContainsExpectedValues(actual);
Assert.Equal(original, actual);
}
[Fact]
@ -163,7 +180,8 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Xmp
{
// arrange
var image = new Image<Rgba32>(1, 1);
image.Metadata.XmpProfile = CreateMinimalXmlProfile();
XmpProfile original = CreateMinimalXmlProfile();
image.Metadata.XmpProfile = original;
var encoder = new JpegEncoder();
// act
@ -172,6 +190,7 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Xmp
// assert
XmpProfile actual = reloadedImage.Metadata.XmpProfile ?? reloadedImage.Frames.RootFrame.Metadata.XmpProfile;
XmpProfileContainsExpectedValues(actual);
Assert.Equal(original, actual);
}
[Fact]
@ -180,6 +199,7 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Xmp
// arrange
var provider = TestImageProvider<Rgba32>.File(TestImages.Jpeg.Baseline.ExtendedXmp);
using Image<Rgba32> image = await provider.GetImageAsync(JpegDecoder);
XmpProfile original = image.Metadata.XmpProfile;
var encoder = new JpegEncoder();
// act
@ -188,6 +208,7 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Xmp
// assert
XmpProfile actual = reloadedImage.Metadata.XmpProfile ?? reloadedImage.Frames.RootFrame.Metadata.XmpProfile;
XmpProfileContainsExpectedValues(actual);
Assert.Equal(original, actual);
}
[Fact]
@ -195,7 +216,8 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Xmp
{
// arrange
var image = new Image<Rgba32>(1, 1);
image.Metadata.XmpProfile = CreateMinimalXmlProfile();
XmpProfile original = CreateMinimalXmlProfile();
image.Metadata.XmpProfile = original;
var encoder = new PngEncoder();
// act
@ -204,6 +226,7 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Xmp
// assert
XmpProfile actual = reloadedImage.Metadata.XmpProfile ?? reloadedImage.Frames.RootFrame.Metadata.XmpProfile;
XmpProfileContainsExpectedValues(actual);
Assert.Equal(original, actual);
}
[Fact]
@ -211,7 +234,8 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Xmp
{
// arrange
var image = new Image<Rgba32>(1, 1);
image.Frames.RootFrame.Metadata.XmpProfile = CreateMinimalXmlProfile();
XmpProfile original = CreateMinimalXmlProfile();
image.Frames.RootFrame.Metadata.XmpProfile = original;
var encoder = new TiffEncoder();
// act
@ -220,6 +244,7 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Xmp
// assert
XmpProfile actual = reloadedImage.Metadata.XmpProfile ?? reloadedImage.Frames.RootFrame.Metadata.XmpProfile;
XmpProfileContainsExpectedValues(actual);
Assert.Equal(original, actual);
}
[Fact]
@ -227,7 +252,8 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Xmp
{
// arrange
var image = new Image<Rgba32>(1, 1);
image.Metadata.XmpProfile = CreateMinimalXmlProfile();
XmpProfile original = CreateMinimalXmlProfile();
image.Metadata.XmpProfile = original;
var encoder = new WebpEncoder();
// act
@ -236,6 +262,7 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Xmp
// assert
XmpProfile actual = reloadedImage.Metadata.XmpProfile ?? reloadedImage.Frames.RootFrame.Metadata.XmpProfile;
XmpProfileContainsExpectedValues(actual);
Assert.Equal(original, actual);
}
private static void XmpProfileContainsExpectedValues(XmpProfile xmp)
@ -249,7 +276,7 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Xmp
private static XmpProfile CreateMinimalXmlProfile()
{
string content = "<x:xmpmeta xmlns:x='adobe:ns:meta/'></x:xmpmeta><?xpacket end='w'?>";
string content = $"<?xpacket begin='' id='{Guid.NewGuid()}'?><x:xmpmeta xmlns:x='adobe:ns:meta/'></x:xmpmeta><?xpacket end='w'?>";
byte[] data = Encoding.UTF8.GetBytes(content);
var profile = new XmpProfile(data);
return profile;

Loading…
Cancel
Save