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

Make iiif-net parse all the Manifests in the Cookbook #51

Merged
merged 4 commits into from
Oct 28, 2024
Merged
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
17 changes: 17 additions & 0 deletions src/IIIF/IIIF.Tests/Serialisation/CookbookDeserialization.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using IIIF.Presentation.V3;
using IIIF.Tests.Serialisation.Data;

namespace IIIF.Tests.Serialisation;

[Trait("Category", "Cookbook")]
public class CookbookDeserialization
{
[Theory]
[ClassData(typeof(CookbookManifestData))]
public void Can_Deserialize_Cookbook_Manifest(string manifestId, Manifest manifest)
{
// perfunctory assertion
manifest.Should().NotBeNull($"{manifestId} is a valid cookbook manifest");
manifest.Id.Should().Be(manifestId);
}
}
64 changes: 64 additions & 0 deletions src/IIIF/IIIF.Tests/Serialisation/Data/CookbookManifestData.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Net.Http;
using IIIF.Presentation.V3;
using IIIF.Serialisation;

namespace IIIF.Tests.Serialisation.Data;

/// <summary>
/// Used as [ClassData] - contains Manifests from IIIF Cookbook to validate deserialisation
/// </summary>
public class CookbookManifestData : IEnumerable<object[]>
{
// This will store { manifest-id, deserialized-manifest }
private readonly List<object[]> data = new();

// these have bugs in the cookbook, see https://github.com/IIIF/cookbook-recipes/pull/546
private List<string> skip = new()
{
"https://iiif.io/api/cookbook/recipe/0219-using-caption-file/manifest.json",
"https://iiif.io/api/cookbook/recipe/0040-image-rotation-service/manifest-service.json"
};

public CookbookManifestData()
{
using var httpClient = new HttpClient();
var theseusCollection =
GetIIIFResource<Collection>("https://theseus-viewer.netlify.app/cookbook-collection.json", true);

foreach (var item in theseusCollection.Items!)
{
if (item is Manifest manifestRef)
{
if (skip.Contains(manifestRef.Id)) continue;

var iiif = GetIIIFResource<Manifest>(manifestRef.Id);
data.Add(new object[] { manifestRef.Id, iiif });
}
}

T GetIIIFResource<T>(string url, bool mustSucceed = false) where T : JsonLdBase
{
var resource = httpClient.GetAsync(url).Result;
if (mustSucceed) resource.EnsureSuccessStatusCode();
if (!resource.IsSuccessStatusCode) return null;

try
{
var iiif = resource.Content.ReadAsStream().FromJsonStream<T>();
return iiif;
}
catch (Exception)
{
if (mustSucceed) throw;
return null;
}
}
}

public IEnumerator<object[]> GetEnumerator() => data.GetEnumerator();

IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@

namespace IIIF.Presentation.V3.Content;

public class Audio : ExternalResource, ITemporal, IPaintable
public class Sound : ExternalResource, ITemporal, IPaintable
{
public double? Duration { get; set; }

public Audio() : base("Sound")
public Sound() : base(nameof(Sound))
{
}
}
7 changes: 7 additions & 0 deletions src/IIIF/IIIF/Presentation/V3/Selectors/SvgSelector.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace IIIF.Presentation.V3.Selectors;

public class SvgSelector : ISelector
donaldgray marked this conversation as resolved.
Show resolved Hide resolved
{
public string? Type => nameof(SvgSelector);
public string? Value { get; set; }
}
10 changes: 6 additions & 4 deletions src/IIIF/IIIF/Presentation/V3/SpecificResource.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
using IIIF.Presentation.V3.Selectors;
using Newtonsoft.Json;
using IIIF.Presentation.V3.Annotation;
using IIIF.Presentation.V3.Selectors;
using IIIF.Serialisation;

namespace IIIF.Presentation.V3;

public class SpecificResource : ResourceBase, IStructuralLocation
public class SpecificResource : ResourceBase, IStructuralLocation, IPaintable
{
public override string Type => nameof(SpecificResource);

[JsonProperty(Order = 101)] public string Source { get; set; }
[JsonConverter(typeof(SourceConverter))]
[JsonProperty(Order = 101)] public IPaintable Source { get; set; }

[JsonProperty(Order = 102)] public ISelector Selector { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public class ExternalResourceConverter : ReadOnlyConverter<ExternalResource>
var type = jsonObject["type"].Value<string>();
var externalResource = type switch
{
nameof(Audio) => new Audio(),
nameof(Sound) => new Sound(),
nameof(Video) => new Video(),
nameof(Image) => new Image(),
_ => new ExternalResource(type)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,12 @@ public class PaintableConverter : ReadOnlyConverter<IPaintable>

IPaintable? paintable = jsonObject["type"].Value<string>() switch
{
nameof(Audio) => new Audio(),
nameof(Sound) => new Sound(),
nameof(Video) => new Video(),
nameof(Image) => new Image(),
nameof(Canvas) => new Canvas(),
"Choice" => new PaintingChoice(),
nameof(SpecificResource) => new SpecificResource(),
_ => null
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public class ResourceBaseV3Converter : ReadOnlyConverter<ResourceBase>
nameof(Annotation) => new Annotation(),
nameof(AnnotationCollection) => new AnnotationCollection(),
nameof(AnnotationPage) => new AnnotationPage(),
nameof(Audio) => new Audio(),
nameof(Sound) => new Sound(),
nameof(Canvas) => new Canvas(),
nameof(Collection) => new Collection(),
nameof(Image) => new Image(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public class ResourceConverter : ReadOnlyConverter<IResource>
nameof(AuthAccessTokenService2) => new AuthAccessTokenService2(),
nameof(AuthLogoutService2) => new AuthLogoutService2(),
nameof(AuthProbeService2) => new AuthProbeService2(),
nameof(Audio) => new Audio(),
nameof(Sound) => new Sound(),
nameof(Video) => new Video(),
nameof(Image) => new Image(),
_ => null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,10 @@ public class SelectorConverter : ReadOnlyConverter<ISelector>
{
nameof(AudioContentSelector) => new AudioContentSelector(),
nameof(ImageApiSelector) => new ImageApiSelector(),
"iiif:ImageApiSelector" => new ImageApiSelector(),
nameof(PointSelector) => new PointSelector(),
nameof(VideoContentSelector) => new VideoContentSelector()
nameof(VideoContentSelector) => new VideoContentSelector(),
nameof(SvgSelector) => new SvgSelector()
};

serializer.Populate(jsonObject.CreateReader(), selector);
Expand Down
57 changes: 57 additions & 0 deletions src/IIIF/IIIF/Serialisation/SourceConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
using System;
using IIIF.Presentation.V3;
using IIIF.Presentation.V3.Annotation;
using IIIF.Presentation.V3.Content;
using IIIF.Utils;
using Newtonsoft.Json.Linq;

namespace IIIF.Serialisation;

public class SourceConverter : JsonConverter<IPaintable>
donaldgray marked this conversation as resolved.
Show resolved Hide resolved
{
public override IPaintable? ReadJson(JsonReader reader, Type objectType, IPaintable? existingValue, bool hasExistingValue,
JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.String)
{
// We do not know that this is a Canvas...
// We would need knowledge of the rest of the IIIF Resource
return new Canvas { Id = reader.Value.ToString() };
}
else if (reader.TokenType == JsonToken.StartObject)
{
var obj = JObject.Load(reader);
var type = obj["type"].Value<string>();
IPaintable paintable = type switch
{
nameof(Sound) => new Sound(),
nameof(Video) => new Video(),
nameof(Image) => new Image(),
nameof(Canvas) => new Canvas(),
nameof(SpecificResource) => new SpecificResource()
};
serializer.Populate(obj.CreateReader(), paintable);
return paintable;
}

return null;
}

public override void WriteJson(JsonWriter writer, IPaintable? value, JsonSerializer serializer)
{
if (value is Canvas canvas && (canvas.SerialiseTargetAsId || IsSimpleCanvas(canvas)))
{
writer.WriteValue(canvas.Id);
return;
}

// Default, pass through behaviour:
JObject.FromObject(value, serializer).WriteTo(writer);
}

private static bool IsSimpleCanvas(Canvas canvas)
{
return canvas.Width == null && canvas.Duration == null && canvas.Items.IsNullOrEmpty();
}

}
10 changes: 6 additions & 4 deletions src/IIIF/IIIF/Serialisation/TargetConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,14 @@ public class TargetConverter : JsonConverter<IStructuralLocation>
var obj = JObject.Load(reader);

var type = obj["type"].Value<string>();
return type switch
IStructuralLocation structuralLocation = type switch
{
nameof(Canvas) => obj.ToObject<Canvas>(),
nameof(Range) => obj.ToObject<Range>(),
nameof(SpecificResource) => obj.ToObject<SpecificResource>()
nameof(Canvas) => new Canvas(),
nameof(Range) => new Range(),
nameof(SpecificResource) => new SpecificResource()
};
serializer.Populate(obj.CreateReader(), structuralLocation);
donaldgray marked this conversation as resolved.
Show resolved Hide resolved
return structuralLocation;
}

return null;
Expand Down
Loading