Browse Source

More robust equals implementation

pull/1918/head
Ynse Hoornenborg 5 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;
using System.IO; using System.IO;
using System.Linq;
using System.Text; using System.Text;
using System.Xml.Linq; using System.Xml.Linq;
@ -10,6 +11,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Xmp
{ {
/// <summary> /// <summary>
/// Represents an XMP profile, providing access to the raw XML. /// 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> /// </summary>
public sealed class XmpProfile : IDeepCloneable<XmpProfile> 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> /// <param name="data">The UTF8 encoded byte array to read the XMP profile from.</param>
public XmpProfile(byte[] data) => this.Data = data; 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> /// <summary>
/// Initializes a new instance of the <see cref="XmpProfile"/> class /// Initializes a new instance of the <see cref="XmpProfile"/> class
/// by making a copy from another XMP profile. /// by making a copy from another XMP profile.
@ -78,7 +90,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Xmp
return true; return true;
} }
if (ReferenceEquals(left, null)) if (left is null)
{ {
return false; 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; /// True if the <paramref name="left"/> parameter is not equal to the <paramref name="right"/> parameter;
/// otherwise, false. /// otherwise, false.
/// </returns> /// </returns>
public static bool operator !=(XmpProfile left, XmpProfile right) public static bool operator !=(XmpProfile left, XmpProfile right) => !(left == right);
{
return !(left == right);
}
/// <inheritdoc/> /// <inheritdoc/>
public XmpProfile DeepClone() => new(this); public XmpProfile DeepClone() => new(this);
@ -113,7 +122,13 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Xmp
return; 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); using var writer = new StreamWriter(stream, Encoding.UTF8);
this.document.Save(writer); this.document.Save(writer);
this.Data = stream.ToArray(); this.Data = stream.ToArray();
@ -125,28 +140,37 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Xmp
/// <inheritdoc /> /// <inheritdoc />
public override bool Equals(object obj) public override bool Equals(object obj)
{ {
XmpProfile other = obj as XmpProfile; if (obj is not XmpProfile other)
if (ReferenceEquals(other, null))
{ {
return false; 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() private void InitializeDocument()
{ {
if (this.document != null) if (!(this.document is null))
{ {
return; return;
} }
if (this.Data == null) if (this.Data is null)
{ {
return; return;
} }
@ -164,6 +188,12 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Xmp
using var stream = new MemoryStream(this.Data, 0, count); using var stream = new MemoryStream(this.Data, 0, count);
using var reader = new StreamReader(stream, Encoding.UTF8); using var reader = new StreamReader(stream, Encoding.UTF8);
this.document = XDocument.Load(reader); 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. // Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System;
using System.IO; using System.IO;
using System.Text; using System.Text;
using System.Xml.Linq; using System.Xml.Linq;
@ -108,22 +109,36 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Xmp
} }
[Fact] [Fact]
public void XmpProfile_EqualalityIsByValue() public void XmpProfile_EqualityIsByValue()
{ {
// arrange // arrange
byte[] content = new byte[0]; XmpProfile original = CreateMinimalXmlProfile();
XmpProfile original = new XmpProfile(content); var other = new XmpProfile(original.Data);
XmpProfile other = new XmpProfile(content);
// act // act
var equals = original.Equals(other); bool equals = original.Equals(other);
var equality = original == other; bool equality = original == other;
var inequality = original != other; bool inequality = original != other;
// assert // assert
Assert.True(equals); Assert.True(equals);
Assert.True(equality); Assert.True(equality);
Assert.False(inequality); 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] [Fact]
@ -147,7 +162,8 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Xmp
{ {
// arrange // arrange
var image = new Image<Rgba32>(1, 1); var image = new Image<Rgba32>(1, 1);
image.Metadata.XmpProfile = CreateMinimalXmlProfile(); XmpProfile original = CreateMinimalXmlProfile();
image.Metadata.XmpProfile = original;
var encoder = new GifEncoder(); var encoder = new GifEncoder();
// act // act
@ -156,6 +172,7 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Xmp
// assert // assert
XmpProfile actual = reloadedImage.Metadata.XmpProfile ?? reloadedImage.Frames.RootFrame.Metadata.XmpProfile; XmpProfile actual = reloadedImage.Metadata.XmpProfile ?? reloadedImage.Frames.RootFrame.Metadata.XmpProfile;
XmpProfileContainsExpectedValues(actual); XmpProfileContainsExpectedValues(actual);
Assert.Equal(original, actual);
} }
[Fact] [Fact]
@ -163,7 +180,8 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Xmp
{ {
// arrange // arrange
var image = new Image<Rgba32>(1, 1); var image = new Image<Rgba32>(1, 1);
image.Metadata.XmpProfile = CreateMinimalXmlProfile(); XmpProfile original = CreateMinimalXmlProfile();
image.Metadata.XmpProfile = original;
var encoder = new JpegEncoder(); var encoder = new JpegEncoder();
// act // act
@ -172,6 +190,7 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Xmp
// assert // assert
XmpProfile actual = reloadedImage.Metadata.XmpProfile ?? reloadedImage.Frames.RootFrame.Metadata.XmpProfile; XmpProfile actual = reloadedImage.Metadata.XmpProfile ?? reloadedImage.Frames.RootFrame.Metadata.XmpProfile;
XmpProfileContainsExpectedValues(actual); XmpProfileContainsExpectedValues(actual);
Assert.Equal(original, actual);
} }
[Fact] [Fact]
@ -180,6 +199,7 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Xmp
// arrange // arrange
var provider = TestImageProvider<Rgba32>.File(TestImages.Jpeg.Baseline.ExtendedXmp); var provider = TestImageProvider<Rgba32>.File(TestImages.Jpeg.Baseline.ExtendedXmp);
using Image<Rgba32> image = await provider.GetImageAsync(JpegDecoder); using Image<Rgba32> image = await provider.GetImageAsync(JpegDecoder);
XmpProfile original = image.Metadata.XmpProfile;
var encoder = new JpegEncoder(); var encoder = new JpegEncoder();
// act // act
@ -188,6 +208,7 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Xmp
// assert // assert
XmpProfile actual = reloadedImage.Metadata.XmpProfile ?? reloadedImage.Frames.RootFrame.Metadata.XmpProfile; XmpProfile actual = reloadedImage.Metadata.XmpProfile ?? reloadedImage.Frames.RootFrame.Metadata.XmpProfile;
XmpProfileContainsExpectedValues(actual); XmpProfileContainsExpectedValues(actual);
Assert.Equal(original, actual);
} }
[Fact] [Fact]
@ -195,7 +216,8 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Xmp
{ {
// arrange // arrange
var image = new Image<Rgba32>(1, 1); var image = new Image<Rgba32>(1, 1);
image.Metadata.XmpProfile = CreateMinimalXmlProfile(); XmpProfile original = CreateMinimalXmlProfile();
image.Metadata.XmpProfile = original;
var encoder = new PngEncoder(); var encoder = new PngEncoder();
// act // act
@ -204,6 +226,7 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Xmp
// assert // assert
XmpProfile actual = reloadedImage.Metadata.XmpProfile ?? reloadedImage.Frames.RootFrame.Metadata.XmpProfile; XmpProfile actual = reloadedImage.Metadata.XmpProfile ?? reloadedImage.Frames.RootFrame.Metadata.XmpProfile;
XmpProfileContainsExpectedValues(actual); XmpProfileContainsExpectedValues(actual);
Assert.Equal(original, actual);
} }
[Fact] [Fact]
@ -211,7 +234,8 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Xmp
{ {
// arrange // arrange
var image = new Image<Rgba32>(1, 1); 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(); var encoder = new TiffEncoder();
// act // act
@ -220,6 +244,7 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Xmp
// assert // assert
XmpProfile actual = reloadedImage.Metadata.XmpProfile ?? reloadedImage.Frames.RootFrame.Metadata.XmpProfile; XmpProfile actual = reloadedImage.Metadata.XmpProfile ?? reloadedImage.Frames.RootFrame.Metadata.XmpProfile;
XmpProfileContainsExpectedValues(actual); XmpProfileContainsExpectedValues(actual);
Assert.Equal(original, actual);
} }
[Fact] [Fact]
@ -227,7 +252,8 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Xmp
{ {
// arrange // arrange
var image = new Image<Rgba32>(1, 1); var image = new Image<Rgba32>(1, 1);
image.Metadata.XmpProfile = CreateMinimalXmlProfile(); XmpProfile original = CreateMinimalXmlProfile();
image.Metadata.XmpProfile = original;
var encoder = new WebpEncoder(); var encoder = new WebpEncoder();
// act // act
@ -236,6 +262,7 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Xmp
// assert // assert
XmpProfile actual = reloadedImage.Metadata.XmpProfile ?? reloadedImage.Frames.RootFrame.Metadata.XmpProfile; XmpProfile actual = reloadedImage.Metadata.XmpProfile ?? reloadedImage.Frames.RootFrame.Metadata.XmpProfile;
XmpProfileContainsExpectedValues(actual); XmpProfileContainsExpectedValues(actual);
Assert.Equal(original, actual);
} }
private static void XmpProfileContainsExpectedValues(XmpProfile xmp) private static void XmpProfileContainsExpectedValues(XmpProfile xmp)
@ -249,7 +276,7 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Xmp
private static XmpProfile CreateMinimalXmlProfile() 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); byte[] data = Encoding.UTF8.GetBytes(content);
var profile = new XmpProfile(data); var profile = new XmpProfile(data);
return profile; return profile;

Loading…
Cancel
Save