diff --git a/__tests__/image-3-parser/backlog-coverage.test.ts b/__tests__/image-3-parser/backlog-coverage.test.ts new file mode 100644 index 0000000..48a5ffd --- /dev/null +++ b/__tests__/image-3-parser/backlog-coverage.test.ts @@ -0,0 +1,55 @@ +import { + extractFixedSizeScales, + fixedSizesFromScales, + parseImageServiceRequest, + imageServiceRequestToString, + parseRotationParameter, + rotationParameterToString, + parseImageServiceUrl, + parseRegionParameter, +} from "../../src/image-3"; + +describe("Image API coverage gaps (#29)", () => { + test.each(["!90", "45.5", "0"])("parses and serializes rotation %s", (value) => { + expect(rotationParameterToString(parseRotationParameter(value))).toBe(value); + }); + + test("normalizes angles outside one revolution and rejects invalid rotation", () => { + expect(parseRotationParameter("!450")).toEqual({ mirror: true, angle: 90 }); + expect(() => parseRotationParameter("invalid")).toThrow("Invalid rotation"); + }); + + test("round-trips prefixed info and image requests from the existing Munch examples", () => { + const prefix = "apis/iiif/image/v2"; + for (const url of [ + "https://munch.emuseum.com/apis/iiif/image/v2/17261/info.json", + "https://munch.emuseum.com/apis/iiif/image/v2/17261/full/max/!90/default.jpg", + ]) { + expect(imageServiceRequestToString(parseImageServiceRequest(url, prefix))).toBe(url); + } + expect(() => parseImageServiceUrl("https://example.org/image", "other")).toThrow("Path does not start with prefix"); + expect(() => parseImageServiceRequest("https://example.org/image/invalid")).toThrow("Invalid image service URL"); + expect(() => parseRegionParameter(null as any)).toThrow("Expected"); + }); + + test("extracts and regenerates advertised fixed sizes", () => { + const sizes = [ + { width: 2000, height: 1000 }, + { width: 1000, height: 500 }, + { width: 500, height: 250 }, + ]; + expect(extractFixedSizeScales(2000, 1000, sizes)).toEqual([1, 2, 4]); + expect(fixedSizesFromScales(2000, 1000, [1, 2, 4])).toEqual(sizes); + expect(fixedSizesFromScales(2000, 1000, [0, 2])).toEqual([sizes[1]]); + }); + test("keeps the identifier on base-service URLs with and without prefixes", () => { + for (const [url, prefix] of [ + ["https://example.org/identifier", ""], + ["https://example.org/iiif/identifier", "iiif"], + ]) { + const parsed = parseImageServiceRequest(url!, prefix); + expect(parsed).toMatchObject({ type: "base", identifier: "identifier" }); + expect(imageServiceRequestToString(parsed)).toBe(url); + } + }); +}); diff --git a/__tests__/presentation-2-parser/__snapshots__/upgrade.test.ts.snap b/__tests__/presentation-2-parser/__snapshots__/upgrade.test.ts.snap index 103a8d6..5b8dcb0 100644 --- a/__tests__/presentation-2-parser/__snapshots__/upgrade.test.ts.snap +++ b/__tests__/presentation-2-parser/__snapshots__/upgrade.test.ts.snap @@ -60,6 +60,18 @@ exports[`Presentation 2 to 3 > goettingen manifest 1`] = ` "paged", "paged", ], + "homepage": [ + { + "format": "text/html", + "id": "http://kalliope-verbund.info/DE-611-HS-3216958", + "label": { + "none": [ + "Kalliope", + ], + }, + "type": "Text", + }, + ], "id": "https://manifests.sub.uni-goettingen.de/id/DE-611-HS-3216958", "items": [ { @@ -2960,18 +2972,6 @@ exports[`Presentation 2 to 3 > goettingen manifest 1`] = ` "navDate": "1902-01-01T00:00:00+01:00", "provider": [ { - "homepage": [ - { - "format": "text/html", - "id": "http://kalliope-verbund.info/DE-611-HS-3216958", - "label": { - "none": [ - "Kalliope", - ], - }, - "type": "Text", - }, - ], "id": "http://example.org/provider", "label": { "none": [ @@ -40346,7 +40346,6 @@ exports[`Presentation 2 to 3 > nested ranges 1`] = ` "navDate": "1623-01-01T00:00:00Z", "provider": [ { - "homepage": undefined, "id": "http://example.org/provider", "label": { "none": [ diff --git a/__tests__/presentation-2-parser/image-api-selector.test.ts b/__tests__/presentation-2-parser/image-api-selector.test.ts new file mode 100644 index 0000000..770a701 --- /dev/null +++ b/__tests__/presentation-2-parser/image-api-selector.test.ts @@ -0,0 +1,70 @@ +import { presentation2to3 } from "../../src/presentation-2"; +import type { ContentResourceSelector } from "../../src/presentation-2/types"; +import { normalize, serialize, serializeConfigPresentation3 } from "../../src"; + +const selector = { + "@context": "http://iiif.io/api/annex/openannotation/context.json", + "@type": "iiif:ImageApiSelector", + region: "50,50,100,100", + size: "200,", + rotation: "!90", + quality: "gray", + format: "png", +} satisfies ContentResourceSelector; + +describe("Image API selector fields (#27)", () => { + test("preserves every image parameter through upgrade, normalization and serialization", () => { + const annotation = presentation2to3.traverseAnnotation({ + "@id": "https://example.org/annotation", + "@type": "oa:Annotation", + motivation: "sc:painting", + on: { + "@type": "oa:SpecificResource", + full: "https://example.org/canvas", + selector, + }, + resource: { "@id": "https://example.org/image.png", "@type": "dctypes:Image" }, + } as any); + const expected = { + type: "ImageApiSelector", + region: selector.region, + size: selector.size, + rotation: selector.rotation, + quality: selector.quality, + format: selector.format, + }; + expect((annotation.target as any).selector).toEqual(expected); + const result = normalize(annotation); + const output = serialize({ ...result, requests: {} }, result.resource, serializeConfigPresentation3); + expect(output.target.selector).toEqual(expected); + }); + + test("allows selectors that rely on default parameters", () => { + const defaults = { "@context": selector["@context"], "@type": selector["@type"] } satisfies ContentResourceSelector; + const annotation = presentation2to3.traverseAnnotation({ + "@id": "https://example.org/defaults", + "@type": "oa:Annotation", + on: { "@type": "oa:SpecificResource", full: "https://example.org/canvas", selector: defaults }, + resource: { "@id": "https://example.org/image", "@type": "dctypes:Image" }, + } as any); + expect(JSON.parse(JSON.stringify((annotation.target as any).selector))).toEqual({ type: "ImageApiSelector" }); + }); +}); + +// Existing consumers can still narrow required region/rotation values with `in`. +function readParameter(input: ContentResourceSelector): string | undefined { + if (input["@type"] === "iiif:ImageApiSelector") { + if ("region" in input) { + const region: string = input.region; + return region; + } + if ("rotation" in input) { + const rotation: string = input.rotation; + return rotation; + } + } +} + +test("preserves existing selector parameter narrowing", () => { + expect(readParameter(selector)).toBe(selector.region); +}); diff --git a/__tests__/presentation-2-parser/invalid-label.test.ts b/__tests__/presentation-2-parser/invalid-label.test.ts new file mode 100644 index 0000000..f8ae065 --- /dev/null +++ b/__tests__/presentation-2-parser/invalid-label.test.ts @@ -0,0 +1,32 @@ +import collection from "../../fixtures/presentation-2/leipzig-invalid-label-collection.json"; +import { convertPresentation2, presentation2to3 } from "../../src/presentation-2"; + +describe("Presentation 2 @label compatibility (#43)", () => { + test("retains the Leipzig collection's manifest labels", () => { + // Source: https://iiif.ub.uni-leipzig.de/static/collections/misc/cdvost2018.json + const result = convertPresentation2(structuredClone(collection)) as any; + expect(result.items).toHaveLength(collection.manifests.length); + result.items.forEach((item: any, index: number) => { + expect(item.label).toEqual({ none: [collection.manifests[index]!["@label"]] }); + }); + }); + + test("preserves empty-label behavior and falls back to @label", () => { + const input = { "@id": "https://example.org/canvas", "@type": "sc:Canvas", label: "" }; + expect(presentation2to3.traverseCanvas({ ...input } as any).label).toBeUndefined(); + expect(presentation2to3.traverseCanvas({ ...input, "@label": "Fallback" } as any).label).toEqual({ + none: ["Fallback"], + }); + }); + + test("prefers label when both spellings are supplied", () => { + expect( + presentation2to3.traverseCanvas({ + "@id": "https://example.org/canvas", + "@type": "sc:Canvas", + label: "Correct", + "@label": "Fallback", + } as any).label + ).toEqual({ none: ["Correct"] }); + }); +}); diff --git a/__tests__/presentation-2-parser/isolated-canvas.test.ts b/__tests__/presentation-2-parser/isolated-canvas.test.ts new file mode 100644 index 0000000..b9e7420 --- /dev/null +++ b/__tests__/presentation-2-parser/isolated-canvas.test.ts @@ -0,0 +1,27 @@ +import canvas from "../../fixtures/presentation-2/tpen-isolated-canvas.json"; +import { normalize } from "../../src"; +import { upgrade } from "../../src/upgrader"; +import { convertPresentation2, presentation2to3 } from "../../src/presentation-2"; + +describe("isolated Presentation 2 canvases (#53)", () => { + test("upgrades the context-free TPEN canvas through the public entrypoints", () => { + // Source: https://t-pen.org/TPEN/canvas/13250383 + for (const convert of [convertPresentation2, upgrade]) { + const result = convert(structuredClone(canvas)) as any; + expect(result).toMatchObject({ + id: canvas["@id"], + type: "Canvas", + label: { none: [canvas.label] }, + width: 666, + height: 1000, + }); + expect(result.annotations[0].type).toBe("AnnotationPage"); + expect(normalize(result).resource).toEqual({ id: canvas["@id"], type: "Canvas" }); + } + }); + + test("preserves already upgraded resources", () => { + const result = presentation2to3.traverseCanvas(structuredClone(canvas) as any); + expect(convertPresentation2(result)).toBe(result); + }); +}); diff --git a/__tests__/presentation-2-parser/language-mapping.test.ts b/__tests__/presentation-2-parser/language-mapping.test.ts new file mode 100644 index 0000000..585a347 --- /dev/null +++ b/__tests__/presentation-2-parser/language-mapping.test.ts @@ -0,0 +1,24 @@ +import manifest from "../../fixtures/presentation-2/iiif-multiple-descriptions.json"; +import { convertPresentation2, convertLanguageMapping } from "../../src/presentation-2"; + +describe("mixed Presentation 2 language values (#39)", () => { + test("retains both descriptions from the official Presentation 2 fixture", () => { + // Source: https://iiif.io/api/presentation/2.1/example/fixtures/6/manifest.json + expect(convertPresentation2(structuredClone(manifest)).summary).toEqual({ + none: ["This is one description"], + en: ["This is another"], + }); + }); + + test("retains repeated languages and untagged values in their original order", () => { + expect( + convertLanguageMapping([ + "First", + { "@value": "English", "@language": "en" }, + "Second", + { "@value": "Another English", "@language": "en" }, + { "@value": "Untagged" } as any, + ]) + ).toEqual({ none: ["First", "Second", "Untagged"], en: ["English", "Another English"] }); + }); +}); diff --git a/__tests__/presentation-2-parser/legacy-av.test.ts b/__tests__/presentation-2-parser/legacy-av.test.ts new file mode 100644 index 0000000..ee9da51 --- /dev/null +++ b/__tests__/presentation-2-parser/legacy-av.test.ts @@ -0,0 +1,54 @@ +import audio from "../../fixtures/presentation-2/wellcome-ixif-audio.json"; +import video from "../../fixtures/presentation-2/wellcome-ixif-video.json"; +import { convertPresentation2 } from "../../src/presentation-2"; +import { normalize, serialize, serializeConfigPresentation3 } from "../../src"; + +describe("legacy IxIF audio and video (#14)", () => { + test.each([ + [audio, "Sound", 74.057144], + [video, "Video", 1180], + ] as const)("upgrades the real Wellcome $1 manifest", (input, type, duration) => { + const upgraded = convertPresentation2(structuredClone(input)) as any; + expect(upgraded.items).toHaveLength(1); + const canvas = upgraded.items[0]; + expect(canvas).toMatchObject({ type: "Canvas", duration }); + const annotation = canvas.items[0].items[0]; + expect(annotation).toMatchObject({ type: "Annotation", motivation: "painting", target: canvas.id }); + const bodies = annotation.body.type === "Choice" ? annotation.body.items : [annotation.body]; + expect(bodies.every((body: any) => body.type === type && body.duration === duration)).toBe(true); + expect(bodies.map((body: any) => body.id)).toEqual( + [input.mediaSequences[0]!.elements[0]!.rendering].flat().map((body) => body["@id"]) + ); + expect(bodies.every((body: any) => body.id !== canvas.id)).toBe(true); + expect(canvas.thumbnail[0].id).toBe(input.mediaSequences[0]!.elements[0]!.thumbnail); + if (type === "Video") expect(canvas).toMatchObject({ width: 720, height: 720 }); + const result = normalize(structuredClone(upgraded)); + const output = serialize({ ...result, requests: {} }, result.resource, serializeConfigPresentation3); + expect(output.items[0].duration).toBe(duration); + expect(output.items[0].items[0].items[0].body).toMatchObject({ type: annotation.body.type }); + }); + + test("retains real image sequences and keeps the placeholder for unsupported media", () => { + const input = structuredClone(audio) as any; + input.sequences.push({ + "@type": "sc:Sequence", + canvases: [{ "@id": "https://example.org/cover", "@type": "sc:Canvas", label: "Cover", width: 1, height: 1 }], + }); + expect((convertPresentation2(structuredClone(input)) as any).items.map((item: any) => item.id)).toContain( + "https://example.org/cover" + ); + input.mediaSequences[0].elements[0]["@type"] = "foaf:Document"; + input.mediaSequences[0].elements[0].format = "application/pdf"; + const unsupported = convertPresentation2(input) as any; + expect(unsupported.items).toHaveLength(2); + expect(unsupported.items[0].id).toContain("ixif-message"); + }); + + test("does not invent a duration when the media length is unavailable", () => { + const input = structuredClone(audio) as any; + input.mediaSequences[0].elements[0].metadata = []; + const output = convertPresentation2(input) as any; + expect(output.items[0].id).toContain("ixif-message"); + expect(output.items[0].duration).toBeUndefined(); + }); +}); diff --git a/__tests__/presentation-2-parser/linking-string.test.ts b/__tests__/presentation-2-parser/linking-string.test.ts new file mode 100644 index 0000000..ca235d6 --- /dev/null +++ b/__tests__/presentation-2-parser/linking-string.test.ts @@ -0,0 +1,32 @@ +import manifest from "../../fixtures/presentation-2/iiif-string-seealso.json"; +import { convertPresentation2, presentation2to3 } from "../../src/presentation-2"; + +describe("Presentation 2 linking strings (#40)", () => { + test("retains the seeAlso URI from the official Presentation 2 fixture", () => { + // Source: https://iiif.io/api/presentation/2.1/example/fixtures/8/manifest.json + expect(convertPresentation2(structuredClone(manifest)).seeAlso).toEqual([ + { id: manifest.seeAlso, type: "unknown" }, + ]); + }); + + test("handles mixed links without losing object metadata", () => { + const result = presentation2to3.traverseManifest({ + "@id": "https://example.org/manifest", + "@type": "sc:Manifest", + seeAlso: [ + "https://example.org/metadata", + { + "@id": "https://example.org/data.json", + format: "application/json", + label: "Data", + }, + ], + rendering: "https://example.org/book.pdf", + } as any); + expect(result.seeAlso).toEqual([ + { id: "https://example.org/metadata", type: "unknown" }, + { id: "https://example.org/data.json", type: "Dataset", format: "application/json", label: { none: ["Data"] } }, + ]); + expect(result.rendering).toEqual([{ id: "https://example.org/book.pdf", type: "unknown" }]); + }); +}); diff --git a/__tests__/presentation-2-parser/related-links.test.ts b/__tests__/presentation-2-parser/related-links.test.ts new file mode 100644 index 0000000..ee4c97f --- /dev/null +++ b/__tests__/presentation-2-parser/related-links.test.ts @@ -0,0 +1,46 @@ +import manifest from "../../fixtures/presentation-2/wellcome-ixif-audio.json"; +import { convertPresentation2, presentation2to3 } from "../../src/presentation-2"; +import { normalize, serialize, serializeConfigPresentation3 } from "../../src"; + +describe("Presentation 2 related links (#52)", () => { + test("puts the Wellcome catalogue link on the manifest homepage", () => { + // Source: https://wellcomelibrary.org/iiif/b17307922/manifest + const upgraded = convertPresentation2(structuredClone(manifest)); + const expected = [ + { + id: manifest.related["@id"], + type: "Text", + format: "text/html", + label: { none: [manifest.related.label] }, + }, + ]; + expect(upgraded.homepage).toEqual(expected); + expect(upgraded.provider?.[0]?.logo?.[0]).toMatchObject({ id: manifest.logo, type: "Image" }); + expect(upgraded.provider?.[0]?.homepage).toBeUndefined(); + const result = normalize(upgraded); + expect(serialize({ ...result, requests: {} }, result.resource, serializeConfigPresentation3).homepage).toEqual( + expected + ); + }); + + test("retains every related link and avoids a provider when no logo exists", () => { + const upgraded = presentation2to3.traverseManifest({ + "@id": "https://example.org/manifest", + "@type": "sc:Manifest", + label: "Manifest", + related: [ + "https://example.org/one", + { + "@id": "https://example.org/two", + format: "text/html", + label: "Two", + }, + ], + } as any); + expect(upgraded.homepage).toEqual([ + { id: "https://example.org/one", type: "Text" }, + { id: "https://example.org/two", type: "Text", format: "text/html", label: { none: ["Two"] } }, + ]); + expect(upgraded.provider).toBeUndefined(); + }); +}); diff --git a/__tests__/presentation-2-parser/specific-resource-body.test.ts b/__tests__/presentation-2-parser/specific-resource-body.test.ts new file mode 100644 index 0000000..bdf6128 --- /dev/null +++ b/__tests__/presentation-2-parser/specific-resource-body.test.ts @@ -0,0 +1,37 @@ +import annotation from "../../fixtures/regressions/p2-image-selector-body.json"; +import { convertPresentation2 } from "../../src/presentation-2"; +import { normalize, serialize, serializeConfigPresentation3 } from "../../src"; + +// P2 form of the rotation example: https://iiif.io/api/annex/openannotation/#iiif-image-api-selector +test("upgrades specific resource bodies without losing the source or selector (#27)", () => { + const upgraded = convertPresentation2(structuredClone(annotation)) as any; + expect(upgraded.body).toMatchObject({ + type: "SpecificResource", + source: { + id: annotation.resource.full["@id"], + type: "Image", + label: { none: ["Image to rotate"] }, + service: [{ "@id": annotation.resource.full.service["@id"], "@type": "ImageService2" }], + }, + selector: { + type: "ImageApiSelector", + region: "pct:0,0,10,10", + size: "200,", + rotation: "90", + quality: "gray", + format: "png", + }, + }); + const result = normalize(structuredClone(upgraded)); + const output = serialize({ ...result, requests: {} }, result.resource, serializeConfigPresentation3); + expect(output.body.source.id).toBe(annotation.resource.full["@id"]); + expect(output.body.selector).toEqual(upgraded.body.selector); +}); + +test("retains extension selectors in bodies without rejecting the document", () => { + const input = structuredClone(annotation) as any; + input.resource.selector = { "@type": "oa:TextQuoteSelector", exact: "A quotation" }; + const output = convertPresentation2(input) as any; + expect(output.body.source.id).toBe(annotation.resource.full["@id"]); + expect(output.body.selector).toEqual({ type: "TextQuoteSelector", exact: "A quotation" }); +}); diff --git a/__tests__/presentation-2-parser/upgrade.test.ts b/__tests__/presentation-2-parser/upgrade.test.ts index dd3ea6f..0c24d50 100644 --- a/__tests__/presentation-2-parser/upgrade.test.ts +++ b/__tests__/presentation-2-parser/upgrade.test.ts @@ -178,7 +178,7 @@ describe("Presentation 2 to 3", () => { const body = results.items[0]!.items[0]!.items[0].body as any; expect(body.type).toEqual("Choice"); - expect(body.id).toEqual("http://example.org/oa:Choice/335"); + expect(body.id).toMatch(/^http:\/\/example\.org\/oa:Choice\/\d+$/); expect(body.items.length).toEqual(4); expect(validator.validators.manifest!.errors).toEqual(null); @@ -449,10 +449,10 @@ describe("Presentation 2 to 3", () => { { "body": { "chars": "Top of First Page to Display", - "id": "http://example.org/cnt:ContentAsText/438", + "id": "http://example.org/cnt:ContentAsText/382", "type": "TextualBody", }, - "id": "http://example.org/oa:Annotation/439", + "id": "http://example.org/oa:Annotation/383", "motivation": "painting", "target": { "selector": { @@ -471,18 +471,18 @@ describe("Presentation 2 to 3", () => { "body": [ { "chars": "character", - "id": "http://example.org/oa:Tag/440", + "id": "http://example.org/oa:Tag/384", "type": "Tag", }, { "chars": "

万

", "format": "text/html", - "id": "http://example.org/dctypes:Text/441", + "id": "http://example.org/dctypes:Text/385", "type": "Text", }, ], - "id": "http://example.org/oa:Annotation/442", + "id": "http://example.org/oa:Annotation/386", "motivation": [ "tagging", "commenting", @@ -526,17 +526,17 @@ describe("Presentation 2 to 3", () => { { "chars": "

Zone of interest
Place
Cauca of Huila

", "format": "text/html", - "id": "http://example.org/dctypes:Text/443", + "id": "http://example.org/dctypes:Text/387", "type": "Text", }, { "chars": "Colombia", - "id": "http://example.org/oa:Tag/444", + "id": "http://example.org/oa:Tag/388", "type": "Tag", }, { "chars": "Cauca", - "id": "http://example.org/oa:Tag/445", + "id": "http://example.org/oa:Tag/389", "type": "Tag", }, ], @@ -584,7 +584,7 @@ describe("Presentation 2 to 3", () => { "body": { "chars": "

Surface tool
Feuille
70.97 mm²

", "format": "text/html", - "id": "http://example.org/dctypes:Text/446", + "id": "http://example.org/dctypes:Text/390", "type": "Text", }, "id": "https://collections.recolnat.org/annotate-server/iiif/2/annotationList/30/febbe57672bb5eaf097de6c5448c43ad8a82e012/list/3", diff --git a/__tests__/presentation-3-parser/__snapshots__/cookbook.tests.ts.snap b/__tests__/presentation-3-parser/__snapshots__/cookbook.tests.ts.snap index 173d14d..2713588 100644 --- a/__tests__/presentation-3-parser/__snapshots__/cookbook.tests.ts.snap +++ b/__tests__/presentation-3-parser/__snapshots__/cookbook.tests.ts.snap @@ -16016,6 +16016,13 @@ exports[`Cookbook > Testing normalize %p (%p) 71`] = ` "height": 2504, "homepage": [], "id": "https://iiif.io/api/cookbook/recipe/0024-book-4-toc/canvas/p1", + "iiif-parser:hasPart": [ + { + "id": "https://iiif.io/api/cookbook/recipe/0024-book-4-toc/canvas/p1", + "iiif-parser:partOf": "https://iiif.io/api/cookbook/recipe/0024-book-4-toc/range/r1", + "type": "Canvas", + }, + ], "items": [ { "id": "https://iiif.io/api/cookbook/recipe/0024-book-4-toc/page/p1/1", @@ -16050,6 +16057,13 @@ exports[`Cookbook > Testing normalize %p (%p) 71`] = ` "height": 2512, "homepage": [], "id": "https://iiif.io/api/cookbook/recipe/0024-book-4-toc/canvas/p2", + "iiif-parser:hasPart": [ + { + "id": "https://iiif.io/api/cookbook/recipe/0024-book-4-toc/canvas/p2", + "iiif-parser:partOf": "https://iiif.io/api/cookbook/recipe/0024-book-4-toc/range/r1", + "type": "Canvas", + }, + ], "items": [ { "id": "https://iiif.io/api/cookbook/recipe/0024-book-4-toc/page/p2/1", @@ -16084,6 +16098,13 @@ exports[`Cookbook > Testing normalize %p (%p) 71`] = ` "height": 2456, "homepage": [], "id": "https://iiif.io/api/cookbook/recipe/0024-book-4-toc/canvas/p3", + "iiif-parser:hasPart": [ + { + "id": "https://iiif.io/api/cookbook/recipe/0024-book-4-toc/canvas/p3", + "iiif-parser:partOf": "https://iiif.io/api/cookbook/recipe/0024-book-4-toc/range/r2/1", + "type": "Canvas", + }, + ], "items": [ { "id": "https://iiif.io/api/cookbook/recipe/0024-book-4-toc/page/p3/1", @@ -16118,6 +16139,13 @@ exports[`Cookbook > Testing normalize %p (%p) 71`] = ` "height": 2440, "homepage": [], "id": "https://iiif.io/api/cookbook/recipe/0024-book-4-toc/canvas/p4", + "iiif-parser:hasPart": [ + { + "id": "https://iiif.io/api/cookbook/recipe/0024-book-4-toc/canvas/p4", + "iiif-parser:partOf": "https://iiif.io/api/cookbook/recipe/0024-book-4-toc/range/r2/1", + "type": "Canvas", + }, + ], "items": [ { "id": "https://iiif.io/api/cookbook/recipe/0024-book-4-toc/page/p4/1", @@ -16152,6 +16180,13 @@ exports[`Cookbook > Testing normalize %p (%p) 71`] = ` "height": 2416, "homepage": [], "id": "https://iiif.io/api/cookbook/recipe/0024-book-4-toc/canvas/p5", + "iiif-parser:hasPart": [ + { + "id": "https://iiif.io/api/cookbook/recipe/0024-book-4-toc/canvas/p5", + "iiif-parser:partOf": "https://iiif.io/api/cookbook/recipe/0024-book-4-toc/range/r2/2", + "type": "Canvas", + }, + ], "items": [ { "id": "https://iiif.io/api/cookbook/recipe/0024-book-4-toc/page/p5/1", @@ -16186,6 +16221,13 @@ exports[`Cookbook > Testing normalize %p (%p) 71`] = ` "height": 2416, "homepage": [], "id": "https://iiif.io/api/cookbook/recipe/0024-book-4-toc/canvas/p6", + "iiif-parser:hasPart": [ + { + "id": "https://iiif.io/api/cookbook/recipe/0024-book-4-toc/canvas/p6", + "iiif-parser:partOf": "https://iiif.io/api/cookbook/recipe/0024-book-4-toc/range/r2/2", + "type": "Canvas", + }, + ], "items": [ { "id": "https://iiif.io/api/cookbook/recipe/0024-book-4-toc/page/p6/1", @@ -16413,198 +16455,6 @@ exports[`Cookbook > Testing normalize %p (%p) 71`] = ` }, }, "Range": { - "https://iiif.io/api/cookbook/recipe/0024-book-4-toc/canvas/p1": { - "accompanyingCanvas": null, - "annotations": [], - "behavior": [], - "homepage": [], - "id": "https://iiif.io/api/cookbook/recipe/0024-book-4-toc/canvas/p1", - "iiif-parser:hasPart": [ - { - "id": "https://iiif.io/api/cookbook/recipe/0024-book-4-toc/canvas/p1", - "iiif-parser:partOf": "https://iiif.io/api/cookbook/recipe/0024-book-4-toc/range/r1", - "type": "Canvas", - }, - ], - "items": [], - "label": null, - "metadata": [], - "navDate": null, - "partOf": [], - "placeholderCanvas": null, - "provider": [], - "rendering": [], - "requiredStatement": null, - "rights": null, - "seeAlso": [], - "service": [], - "start": null, - "summary": null, - "supplementary": null, - "thumbnail": [], - "type": "Canvas", - "viewingDirection": "left-to-right", - }, - "https://iiif.io/api/cookbook/recipe/0024-book-4-toc/canvas/p2": { - "accompanyingCanvas": null, - "annotations": [], - "behavior": [], - "homepage": [], - "id": "https://iiif.io/api/cookbook/recipe/0024-book-4-toc/canvas/p2", - "iiif-parser:hasPart": [ - { - "id": "https://iiif.io/api/cookbook/recipe/0024-book-4-toc/canvas/p2", - "iiif-parser:partOf": "https://iiif.io/api/cookbook/recipe/0024-book-4-toc/range/r1", - "type": "Canvas", - }, - ], - "items": [], - "label": null, - "metadata": [], - "navDate": null, - "partOf": [], - "placeholderCanvas": null, - "provider": [], - "rendering": [], - "requiredStatement": null, - "rights": null, - "seeAlso": [], - "service": [], - "start": null, - "summary": null, - "supplementary": null, - "thumbnail": [], - "type": "Canvas", - "viewingDirection": "left-to-right", - }, - "https://iiif.io/api/cookbook/recipe/0024-book-4-toc/canvas/p3": { - "accompanyingCanvas": null, - "annotations": [], - "behavior": [], - "homepage": [], - "id": "https://iiif.io/api/cookbook/recipe/0024-book-4-toc/canvas/p3", - "iiif-parser:hasPart": [ - { - "id": "https://iiif.io/api/cookbook/recipe/0024-book-4-toc/canvas/p3", - "iiif-parser:partOf": "https://iiif.io/api/cookbook/recipe/0024-book-4-toc/range/r2/1", - "type": "Canvas", - }, - ], - "items": [], - "label": null, - "metadata": [], - "navDate": null, - "partOf": [], - "placeholderCanvas": null, - "provider": [], - "rendering": [], - "requiredStatement": null, - "rights": null, - "seeAlso": [], - "service": [], - "start": null, - "summary": null, - "supplementary": null, - "thumbnail": [], - "type": "Canvas", - "viewingDirection": "left-to-right", - }, - "https://iiif.io/api/cookbook/recipe/0024-book-4-toc/canvas/p4": { - "accompanyingCanvas": null, - "annotations": [], - "behavior": [], - "homepage": [], - "id": "https://iiif.io/api/cookbook/recipe/0024-book-4-toc/canvas/p4", - "iiif-parser:hasPart": [ - { - "id": "https://iiif.io/api/cookbook/recipe/0024-book-4-toc/canvas/p4", - "iiif-parser:partOf": "https://iiif.io/api/cookbook/recipe/0024-book-4-toc/range/r2/1", - "type": "Canvas", - }, - ], - "items": [], - "label": null, - "metadata": [], - "navDate": null, - "partOf": [], - "placeholderCanvas": null, - "provider": [], - "rendering": [], - "requiredStatement": null, - "rights": null, - "seeAlso": [], - "service": [], - "start": null, - "summary": null, - "supplementary": null, - "thumbnail": [], - "type": "Canvas", - "viewingDirection": "left-to-right", - }, - "https://iiif.io/api/cookbook/recipe/0024-book-4-toc/canvas/p5": { - "accompanyingCanvas": null, - "annotations": [], - "behavior": [], - "homepage": [], - "id": "https://iiif.io/api/cookbook/recipe/0024-book-4-toc/canvas/p5", - "iiif-parser:hasPart": [ - { - "id": "https://iiif.io/api/cookbook/recipe/0024-book-4-toc/canvas/p5", - "iiif-parser:partOf": "https://iiif.io/api/cookbook/recipe/0024-book-4-toc/range/r2/2", - "type": "Canvas", - }, - ], - "items": [], - "label": null, - "metadata": [], - "navDate": null, - "partOf": [], - "placeholderCanvas": null, - "provider": [], - "rendering": [], - "requiredStatement": null, - "rights": null, - "seeAlso": [], - "service": [], - "start": null, - "summary": null, - "supplementary": null, - "thumbnail": [], - "type": "Canvas", - "viewingDirection": "left-to-right", - }, - "https://iiif.io/api/cookbook/recipe/0024-book-4-toc/canvas/p6": { - "accompanyingCanvas": null, - "annotations": [], - "behavior": [], - "homepage": [], - "id": "https://iiif.io/api/cookbook/recipe/0024-book-4-toc/canvas/p6", - "iiif-parser:hasPart": [ - { - "id": "https://iiif.io/api/cookbook/recipe/0024-book-4-toc/canvas/p6", - "iiif-parser:partOf": "https://iiif.io/api/cookbook/recipe/0024-book-4-toc/range/r2/2", - "type": "Canvas", - }, - ], - "items": [], - "label": null, - "metadata": [], - "navDate": null, - "partOf": [], - "placeholderCanvas": null, - "provider": [], - "rendering": [], - "requiredStatement": null, - "rights": null, - "seeAlso": [], - "service": [], - "start": null, - "summary": null, - "supplementary": null, - "thumbnail": [], - "type": "Canvas", - "viewingDirection": "left-to-right", - }, "https://iiif.io/api/cookbook/recipe/0024-book-4-toc/range/r0": { "accompanyingCanvas": null, "annotations": [], @@ -17325,6 +17175,92 @@ exports[`Cookbook > Testing normalize %p (%p) 73`] = ` "height": 1080, "homepage": [], "id": "https://iiif.io/api/cookbook/recipe/0026-toc-opera/canvas/1", + "iiif-parser:hasPart": [ + { + "@explicit": true, + "accompanyingCanvas": {}, + "annotations": {}, + "behavior": {}, + "duration": {}, + "height": {}, + "homepage": {}, + "id": "https://iiif.io/api/cookbook/recipe/0026-toc-opera/canvas/1", + "iiif-parser:isExternal": {}, + "iiif-parser:partOf": "https://iiif.io/api/cookbook/recipe/0026-toc-opera/range/3", + "items": [ + { + "id": "https://iiif.io/api/cookbook/recipe/0026-toc-opera/canvas/1/annotation_page/1", + "type": "AnnotationPage", + }, + ], + "label": {}, + "metadata": {}, + "navDate": {}, + "partOf": {}, + "placeholderCanvas": {}, + "provider": {}, + "rendering": {}, + "requiredStatement": {}, + "rights": {}, + "seeAlso": {}, + "service": {}, + "summary": {}, + "thumbnail": {}, + "type": "Canvas", + "width": {}, + }, + { + "@explicit": true, + "accompanyingCanvas": {}, + "annotations": {}, + "behavior": {}, + "homepage": {}, + "id": "https://iiif.io/api/cookbook/recipe/0026-toc-opera/canvas/1", + "iiif-parser:isExternal": {}, + "iiif-parser:partOf": "https://iiif.io/api/cookbook/recipe/0026-toc-opera/range/4", + "items": [], + "label": {}, + "metadata": {}, + "navDate": {}, + "partOf": {}, + "placeholderCanvas": {}, + "provider": {}, + "rendering": {}, + "requiredStatement": {}, + "rights": {}, + "seeAlso": {}, + "service": {}, + "summary": {}, + "thumbnail": {}, + "type": "Canvas", + }, + { + "@explicit": true, + "accompanyingCanvas": {}, + "annotations": {}, + "behavior": {}, + "homepage": {}, + "id": "https://iiif.io/api/cookbook/recipe/0026-toc-opera/canvas/1", + "iiif-parser:isExternal": {}, + "iiif-parser:partOf": "https://iiif.io/api/cookbook/recipe/0026-toc-opera/range/5", + "items": [], + "label": {}, + "metadata": {}, + "navDate": {}, + "partOf": {}, + "placeholderCanvas": {}, + "provider": {}, + "rendering": {}, + "requiredStatement": {}, + "rights": {}, + "seeAlso": {}, + "service": {}, + "summary": {}, + "thumbnail": {}, + "type": "Canvas", + }, + ], + "iiif-parser:isExternal": true, "items": [ { "id": "https://iiif.io/api/cookbook/recipe/0026-toc-opera/canvas/1/annotation_page/1", @@ -17420,109 +17356,13 @@ exports[`Cookbook > Testing normalize %p (%p) 73`] = ` }, }, "Range": { - "https://iiif.io/api/cookbook/recipe/0026-toc-opera/canvas/1#t=0,302.05": { + "https://iiif.io/api/cookbook/recipe/0026-toc-opera/range/1": { "accompanyingCanvas": null, "annotations": [], "behavior": [], "homepage": [], - "id": "https://iiif.io/api/cookbook/recipe/0026-toc-opera/canvas/1#t=0,302.05", - "iiif-parser:hasPart": [ - { - "id": "https://iiif.io/api/cookbook/recipe/0026-toc-opera/canvas/1#t=0,302.05", - "iiif-parser:partOf": "https://iiif.io/api/cookbook/recipe/0026-toc-opera/range/3", - "type": "Canvas", - }, - ], - "items": [], - "label": null, - "metadata": [], - "navDate": null, - "partOf": [], - "placeholderCanvas": null, - "provider": [], - "rendering": [], - "requiredStatement": null, - "rights": null, - "seeAlso": [], - "service": [], - "start": null, - "summary": null, - "supplementary": null, - "thumbnail": [], - "type": "Canvas", - "viewingDirection": "left-to-right", - }, - "https://iiif.io/api/cookbook/recipe/0026-toc-opera/canvas/1#t=302.05,3971.24": { - "accompanyingCanvas": null, - "annotations": [], - "behavior": [], - "homepage": [], - "id": "https://iiif.io/api/cookbook/recipe/0026-toc-opera/canvas/1#t=302.05,3971.24", - "iiif-parser:hasPart": [ - { - "id": "https://iiif.io/api/cookbook/recipe/0026-toc-opera/canvas/1#t=302.05,3971.24", - "iiif-parser:partOf": "https://iiif.io/api/cookbook/recipe/0026-toc-opera/range/4", - "type": "Canvas", - }, - ], - "items": [], - "label": null, - "metadata": [], - "navDate": null, - "partOf": [], - "placeholderCanvas": null, - "provider": [], - "rendering": [], - "requiredStatement": null, - "rights": null, - "seeAlso": [], - "service": [], - "start": null, - "summary": null, - "supplementary": null, - "thumbnail": [], - "type": "Canvas", - "viewingDirection": "left-to-right", - }, - "https://iiif.io/api/cookbook/recipe/0026-toc-opera/canvas/1#t=3971.24": { - "accompanyingCanvas": null, - "annotations": [], - "behavior": [], - "homepage": [], - "id": "https://iiif.io/api/cookbook/recipe/0026-toc-opera/canvas/1#t=3971.24", - "iiif-parser:hasPart": [ - { - "id": "https://iiif.io/api/cookbook/recipe/0026-toc-opera/canvas/1#t=3971.24", - "iiif-parser:partOf": "https://iiif.io/api/cookbook/recipe/0026-toc-opera/range/5", - "type": "Canvas", - }, - ], - "items": [], - "label": null, - "metadata": [], - "navDate": null, - "partOf": [], - "placeholderCanvas": null, - "provider": [], - "rendering": [], - "requiredStatement": null, - "rights": null, - "seeAlso": [], - "service": [], - "start": null, - "summary": null, - "supplementary": null, - "thumbnail": [], - "type": "Canvas", - "viewingDirection": "left-to-right", - }, - "https://iiif.io/api/cookbook/recipe/0026-toc-opera/range/1": { - "accompanyingCanvas": null, - "annotations": [], - "behavior": [], - "homepage": [], - "id": "https://iiif.io/api/cookbook/recipe/0026-toc-opera/range/1", - "items": [ + "id": "https://iiif.io/api/cookbook/recipe/0026-toc-opera/range/1", + "items": [ { "id": "https://iiif.io/api/cookbook/recipe/0026-toc-opera/range/2", "type": "Range", @@ -17750,9 +17590,6 @@ exports[`Cookbook > Testing normalize %p (%p) 73`] = ` "mapping": { "https://fixtures.iiif.io/video/indiana/donizetti-elixir/vae0637_accessH264_low.mp4": "ContentResource", "https://iiif.io/api/cookbook/recipe/0026-toc-opera/canvas/1": "Canvas", - "https://iiif.io/api/cookbook/recipe/0026-toc-opera/canvas/1#t=0,302.05": "Canvas", - "https://iiif.io/api/cookbook/recipe/0026-toc-opera/canvas/1#t=302.05,3971.24": "Canvas", - "https://iiif.io/api/cookbook/recipe/0026-toc-opera/canvas/1#t=3971.24": "Canvas", "https://iiif.io/api/cookbook/recipe/0026-toc-opera/canvas/1/annotation_page/1": "AnnotationPage", "https://iiif.io/api/cookbook/recipe/0026-toc-opera/canvas/1/annotation_page/1/annotation/1": "Annotation", "https://iiif.io/api/cookbook/recipe/0026-toc-opera/manifest.json": "Manifest", @@ -20237,6 +20074,13 @@ exports[`Cookbook > Testing normalize %p (%p) 81`] = ` "height": 7230, "homepage": [], "id": "https://iiif.io/api/cookbook/recipe/0031-bound-multivolume/canvas/p1", + "iiif-parser:hasPart": [ + { + "id": "https://iiif.io/api/cookbook/recipe/0031-bound-multivolume/canvas/p1", + "iiif-parser:partOf": "https://iiif.io/api/cookbook/recipe/0031-bound-multivolume/range/r1", + "type": "Canvas", + }, + ], "items": [ { "id": "https://iiif.io/api/cookbook/recipe/0031-bound-multivolume/page/p1/1", @@ -20271,6 +20115,13 @@ exports[`Cookbook > Testing normalize %p (%p) 81`] = ` "height": 7230, "homepage": [], "id": "https://iiif.io/api/cookbook/recipe/0031-bound-multivolume/canvas/p2", + "iiif-parser:hasPart": [ + { + "id": "https://iiif.io/api/cookbook/recipe/0031-bound-multivolume/canvas/p2", + "iiif-parser:partOf": "https://iiif.io/api/cookbook/recipe/0031-bound-multivolume/range/r1", + "type": "Canvas", + }, + ], "items": [ { "id": "https://iiif.io/api/cookbook/recipe/0031-bound-multivolume/page/p2/1", @@ -20305,6 +20156,13 @@ exports[`Cookbook > Testing normalize %p (%p) 81`] = ` "height": 7230, "homepage": [], "id": "https://iiif.io/api/cookbook/recipe/0031-bound-multivolume/canvas/p3", + "iiif-parser:hasPart": [ + { + "id": "https://iiif.io/api/cookbook/recipe/0031-bound-multivolume/canvas/p3", + "iiif-parser:partOf": "https://iiif.io/api/cookbook/recipe/0031-bound-multivolume/range/r2", + "type": "Canvas", + }, + ], "items": [ { "id": "https://iiif.io/api/cookbook/recipe/0031-bound-multivolume/page/p3/1", @@ -20339,6 +20197,13 @@ exports[`Cookbook > Testing normalize %p (%p) 81`] = ` "height": 7230, "homepage": [], "id": "https://iiif.io/api/cookbook/recipe/0031-bound-multivolume/canvas/p4", + "iiif-parser:hasPart": [ + { + "id": "https://iiif.io/api/cookbook/recipe/0031-bound-multivolume/canvas/p4", + "iiif-parser:partOf": "https://iiif.io/api/cookbook/recipe/0031-bound-multivolume/range/r2", + "type": "Canvas", + }, + ], "items": [ { "id": "https://iiif.io/api/cookbook/recipe/0031-bound-multivolume/page/p4/1", @@ -20373,6 +20238,13 @@ exports[`Cookbook > Testing normalize %p (%p) 81`] = ` "height": 7230, "homepage": [], "id": "https://iiif.io/api/cookbook/recipe/0031-bound-multivolume/canvas/p5", + "iiif-parser:hasPart": [ + { + "id": "https://iiif.io/api/cookbook/recipe/0031-bound-multivolume/canvas/p5", + "iiif-parser:partOf": "https://iiif.io/api/cookbook/recipe/0031-bound-multivolume/range/r3", + "type": "Canvas", + }, + ], "items": [ { "id": "https://iiif.io/api/cookbook/recipe/0031-bound-multivolume/page/p5/1", @@ -20407,6 +20279,13 @@ exports[`Cookbook > Testing normalize %p (%p) 81`] = ` "height": 7230, "homepage": [], "id": "https://iiif.io/api/cookbook/recipe/0031-bound-multivolume/canvas/p6", + "iiif-parser:hasPart": [ + { + "id": "https://iiif.io/api/cookbook/recipe/0031-bound-multivolume/canvas/p6", + "iiif-parser:partOf": "https://iiif.io/api/cookbook/recipe/0031-bound-multivolume/range/r3", + "type": "Canvas", + }, + ], "items": [ { "id": "https://iiif.io/api/cookbook/recipe/0031-bound-multivolume/page/p6/1", @@ -20634,21 +20513,31 @@ exports[`Cookbook > Testing normalize %p (%p) 81`] = ` }, }, "Range": { - "https://iiif.io/api/cookbook/recipe/0031-bound-multivolume/canvas/p1": { + "https://iiif.io/api/cookbook/recipe/0031-bound-multivolume/range/r0": { "accompanyingCanvas": null, "annotations": [], "behavior": [], "homepage": [], - "id": "https://iiif.io/api/cookbook/recipe/0031-bound-multivolume/canvas/p1", - "iiif-parser:hasPart": [ + "id": "https://iiif.io/api/cookbook/recipe/0031-bound-multivolume/range/r0", + "items": [ { - "id": "https://iiif.io/api/cookbook/recipe/0031-bound-multivolume/canvas/p1", - "iiif-parser:partOf": "https://iiif.io/api/cookbook/recipe/0031-bound-multivolume/range/r1", - "type": "Canvas", + "id": "https://iiif.io/api/cookbook/recipe/0031-bound-multivolume/range/r1", + "type": "Range", + }, + { + "id": "https://iiif.io/api/cookbook/recipe/0031-bound-multivolume/range/r2", + "type": "Range", + }, + { + "id": "https://iiif.io/api/cookbook/recipe/0031-bound-multivolume/range/r3", + "type": "Range", }, ], - "items": [], - "label": null, + "label": { + "de": [ + "Gottesdienstliche Ceremonien, Oder H. Kirchen-Gebräuche Und Religions-Pflichten Der Christen", + ], + }, "metadata": [], "navDate": null, "partOf": [], @@ -20663,24 +20552,45 @@ exports[`Cookbook > Testing normalize %p (%p) 81`] = ` "summary": null, "supplementary": null, "thumbnail": [], - "type": "Canvas", + "type": "Range", "viewingDirection": "left-to-right", }, - "https://iiif.io/api/cookbook/recipe/0031-bound-multivolume/canvas/p2": { + "https://iiif.io/api/cookbook/recipe/0031-bound-multivolume/range/r1": { "accompanyingCanvas": null, "annotations": [], "behavior": [], "homepage": [], - "id": "https://iiif.io/api/cookbook/recipe/0031-bound-multivolume/canvas/p2", + "id": "https://iiif.io/api/cookbook/recipe/0031-bound-multivolume/range/r1", "iiif-parser:hasPart": [ { - "id": "https://iiif.io/api/cookbook/recipe/0031-bound-multivolume/canvas/p2", - "iiif-parser:partOf": "https://iiif.io/api/cookbook/recipe/0031-bound-multivolume/range/r1", - "type": "Canvas", + "id": "https://iiif.io/api/cookbook/recipe/0031-bound-multivolume/range/r1", + "iiif-parser:partOf": "https://iiif.io/api/cookbook/recipe/0031-bound-multivolume/range/r0", + "type": "Range", }, ], - "items": [], - "label": null, + "items": [ + { + "selector": undefined, + "source": { + "id": "https://iiif.io/api/cookbook/recipe/0031-bound-multivolume/canvas/p1", + "type": "Canvas", + }, + "type": "SpecificResource", + }, + { + "selector": undefined, + "source": { + "id": "https://iiif.io/api/cookbook/recipe/0031-bound-multivolume/canvas/p2", + "type": "Canvas", + }, + "type": "SpecificResource", + }, + ], + "label": { + "en": [ + "Front Matter", + ], + }, "metadata": [], "navDate": null, "partOf": [], @@ -20695,24 +20605,45 @@ exports[`Cookbook > Testing normalize %p (%p) 81`] = ` "summary": null, "supplementary": null, "thumbnail": [], - "type": "Canvas", + "type": "Range", "viewingDirection": "left-to-right", }, - "https://iiif.io/api/cookbook/recipe/0031-bound-multivolume/canvas/p3": { + "https://iiif.io/api/cookbook/recipe/0031-bound-multivolume/range/r2": { "accompanyingCanvas": null, "annotations": [], "behavior": [], "homepage": [], - "id": "https://iiif.io/api/cookbook/recipe/0031-bound-multivolume/canvas/p3", + "id": "https://iiif.io/api/cookbook/recipe/0031-bound-multivolume/range/r2", "iiif-parser:hasPart": [ { - "id": "https://iiif.io/api/cookbook/recipe/0031-bound-multivolume/canvas/p3", - "iiif-parser:partOf": "https://iiif.io/api/cookbook/recipe/0031-bound-multivolume/range/r2", - "type": "Canvas", + "id": "https://iiif.io/api/cookbook/recipe/0031-bound-multivolume/range/r2", + "iiif-parser:partOf": "https://iiif.io/api/cookbook/recipe/0031-bound-multivolume/range/r0", + "type": "Range", }, ], - "items": [], - "label": null, + "items": [ + { + "selector": undefined, + "source": { + "id": "https://iiif.io/api/cookbook/recipe/0031-bound-multivolume/canvas/p3", + "type": "Canvas", + }, + "type": "SpecificResource", + }, + { + "selector": undefined, + "source": { + "id": "https://iiif.io/api/cookbook/recipe/0031-bound-multivolume/canvas/p4", + "type": "Canvas", + }, + "type": "SpecificResource", + }, + ], + "label": { + "de": [ + "Erste Ausgabe. Begreift die Ceremonien der Lutheraner von der Augspurgischen Confession, der Reformirten, der Holländischen u. a. Kirchen", + ], + }, "metadata": [], "navDate": null, "partOf": [], @@ -20727,264 +20658,20 @@ exports[`Cookbook > Testing normalize %p (%p) 81`] = ` "summary": null, "supplementary": null, "thumbnail": [], - "type": "Canvas", + "type": "Range", "viewingDirection": "left-to-right", }, - "https://iiif.io/api/cookbook/recipe/0031-bound-multivolume/canvas/p4": { + "https://iiif.io/api/cookbook/recipe/0031-bound-multivolume/range/r3": { "accompanyingCanvas": null, "annotations": [], "behavior": [], "homepage": [], - "id": "https://iiif.io/api/cookbook/recipe/0031-bound-multivolume/canvas/p4", + "id": "https://iiif.io/api/cookbook/recipe/0031-bound-multivolume/range/r3", "iiif-parser:hasPart": [ { - "id": "https://iiif.io/api/cookbook/recipe/0031-bound-multivolume/canvas/p4", - "iiif-parser:partOf": "https://iiif.io/api/cookbook/recipe/0031-bound-multivolume/range/r2", - "type": "Canvas", - }, - ], - "items": [], - "label": null, - "metadata": [], - "navDate": null, - "partOf": [], - "placeholderCanvas": null, - "provider": [], - "rendering": [], - "requiredStatement": null, - "rights": null, - "seeAlso": [], - "service": [], - "start": null, - "summary": null, - "supplementary": null, - "thumbnail": [], - "type": "Canvas", - "viewingDirection": "left-to-right", - }, - "https://iiif.io/api/cookbook/recipe/0031-bound-multivolume/canvas/p5": { - "accompanyingCanvas": null, - "annotations": [], - "behavior": [], - "homepage": [], - "id": "https://iiif.io/api/cookbook/recipe/0031-bound-multivolume/canvas/p5", - "iiif-parser:hasPart": [ - { - "id": "https://iiif.io/api/cookbook/recipe/0031-bound-multivolume/canvas/p5", - "iiif-parser:partOf": "https://iiif.io/api/cookbook/recipe/0031-bound-multivolume/range/r3", - "type": "Canvas", - }, - ], - "items": [], - "label": null, - "metadata": [], - "navDate": null, - "partOf": [], - "placeholderCanvas": null, - "provider": [], - "rendering": [], - "requiredStatement": null, - "rights": null, - "seeAlso": [], - "service": [], - "start": null, - "summary": null, - "supplementary": null, - "thumbnail": [], - "type": "Canvas", - "viewingDirection": "left-to-right", - }, - "https://iiif.io/api/cookbook/recipe/0031-bound-multivolume/canvas/p6": { - "accompanyingCanvas": null, - "annotations": [], - "behavior": [], - "homepage": [], - "id": "https://iiif.io/api/cookbook/recipe/0031-bound-multivolume/canvas/p6", - "iiif-parser:hasPart": [ - { - "id": "https://iiif.io/api/cookbook/recipe/0031-bound-multivolume/canvas/p6", - "iiif-parser:partOf": "https://iiif.io/api/cookbook/recipe/0031-bound-multivolume/range/r3", - "type": "Canvas", - }, - ], - "items": [], - "label": null, - "metadata": [], - "navDate": null, - "partOf": [], - "placeholderCanvas": null, - "provider": [], - "rendering": [], - "requiredStatement": null, - "rights": null, - "seeAlso": [], - "service": [], - "start": null, - "summary": null, - "supplementary": null, - "thumbnail": [], - "type": "Canvas", - "viewingDirection": "left-to-right", - }, - "https://iiif.io/api/cookbook/recipe/0031-bound-multivolume/range/r0": { - "accompanyingCanvas": null, - "annotations": [], - "behavior": [], - "homepage": [], - "id": "https://iiif.io/api/cookbook/recipe/0031-bound-multivolume/range/r0", - "items": [ - { - "id": "https://iiif.io/api/cookbook/recipe/0031-bound-multivolume/range/r1", - "type": "Range", - }, - { - "id": "https://iiif.io/api/cookbook/recipe/0031-bound-multivolume/range/r2", - "type": "Range", - }, - { - "id": "https://iiif.io/api/cookbook/recipe/0031-bound-multivolume/range/r3", - "type": "Range", - }, - ], - "label": { - "de": [ - "Gottesdienstliche Ceremonien, Oder H. Kirchen-Gebräuche Und Religions-Pflichten Der Christen", - ], - }, - "metadata": [], - "navDate": null, - "partOf": [], - "placeholderCanvas": null, - "provider": [], - "rendering": [], - "requiredStatement": null, - "rights": null, - "seeAlso": [], - "service": [], - "start": null, - "summary": null, - "supplementary": null, - "thumbnail": [], - "type": "Range", - "viewingDirection": "left-to-right", - }, - "https://iiif.io/api/cookbook/recipe/0031-bound-multivolume/range/r1": { - "accompanyingCanvas": null, - "annotations": [], - "behavior": [], - "homepage": [], - "id": "https://iiif.io/api/cookbook/recipe/0031-bound-multivolume/range/r1", - "iiif-parser:hasPart": [ - { - "id": "https://iiif.io/api/cookbook/recipe/0031-bound-multivolume/range/r1", - "iiif-parser:partOf": "https://iiif.io/api/cookbook/recipe/0031-bound-multivolume/range/r0", - "type": "Range", - }, - ], - "items": [ - { - "selector": undefined, - "source": { - "id": "https://iiif.io/api/cookbook/recipe/0031-bound-multivolume/canvas/p1", - "type": "Canvas", - }, - "type": "SpecificResource", - }, - { - "selector": undefined, - "source": { - "id": "https://iiif.io/api/cookbook/recipe/0031-bound-multivolume/canvas/p2", - "type": "Canvas", - }, - "type": "SpecificResource", - }, - ], - "label": { - "en": [ - "Front Matter", - ], - }, - "metadata": [], - "navDate": null, - "partOf": [], - "placeholderCanvas": null, - "provider": [], - "rendering": [], - "requiredStatement": null, - "rights": null, - "seeAlso": [], - "service": [], - "start": null, - "summary": null, - "supplementary": null, - "thumbnail": [], - "type": "Range", - "viewingDirection": "left-to-right", - }, - "https://iiif.io/api/cookbook/recipe/0031-bound-multivolume/range/r2": { - "accompanyingCanvas": null, - "annotations": [], - "behavior": [], - "homepage": [], - "id": "https://iiif.io/api/cookbook/recipe/0031-bound-multivolume/range/r2", - "iiif-parser:hasPart": [ - { - "id": "https://iiif.io/api/cookbook/recipe/0031-bound-multivolume/range/r2", - "iiif-parser:partOf": "https://iiif.io/api/cookbook/recipe/0031-bound-multivolume/range/r0", - "type": "Range", - }, - ], - "items": [ - { - "selector": undefined, - "source": { - "id": "https://iiif.io/api/cookbook/recipe/0031-bound-multivolume/canvas/p3", - "type": "Canvas", - }, - "type": "SpecificResource", - }, - { - "selector": undefined, - "source": { - "id": "https://iiif.io/api/cookbook/recipe/0031-bound-multivolume/canvas/p4", - "type": "Canvas", - }, - "type": "SpecificResource", - }, - ], - "label": { - "de": [ - "Erste Ausgabe. Begreift die Ceremonien der Lutheraner von der Augspurgischen Confession, der Reformirten, der Holländischen u. a. Kirchen", - ], - }, - "metadata": [], - "navDate": null, - "partOf": [], - "placeholderCanvas": null, - "provider": [], - "rendering": [], - "requiredStatement": null, - "rights": null, - "seeAlso": [], - "service": [], - "start": null, - "summary": null, - "supplementary": null, - "thumbnail": [], - "type": "Range", - "viewingDirection": "left-to-right", - }, - "https://iiif.io/api/cookbook/recipe/0031-bound-multivolume/range/r3": { - "accompanyingCanvas": null, - "annotations": [], - "behavior": [], - "homepage": [], - "id": "https://iiif.io/api/cookbook/recipe/0031-bound-multivolume/range/r3", - "iiif-parser:hasPart": [ - { - "id": "https://iiif.io/api/cookbook/recipe/0031-bound-multivolume/range/r3", - "iiif-parser:partOf": "https://iiif.io/api/cookbook/recipe/0031-bound-multivolume/range/r0", - "type": "Range", + "id": "https://iiif.io/api/cookbook/recipe/0031-bound-multivolume/range/r3", + "iiif-parser:partOf": "https://iiif.io/api/cookbook/recipe/0031-bound-multivolume/range/r0", + "type": "Range", }, ], "items": [ @@ -23709,33 +23396,319 @@ exports[`Cookbook > Testing normalize %p (%p) 89`] = ` "height": 1080, "homepage": [], "id": "https://iiif.io/api/cookbook/recipe/0229-behavior-ranges/canvas/1", - "items": [ + "iiif-parser:hasPart": [ { - "id": "https://iiif.io/api/cookbook/recipe/0229-behavior-ranges/canvas/1/annotation_page/1", - "type": "AnnotationPage", - }, - ], - "label": null, - "metadata": [], - "navDate": null, - "partOf": [], - "placeholderCanvas": null, - "provider": [], - "rendering": [], - "requiredStatement": null, - "rights": null, - "seeAlso": [], - "service": [], - "summary": null, - "thumbnail": [], - "type": "Canvas", - "width": 1920, - }, - }, - "Collection": {}, - "ContentResource": { - "https://fixtures.iiif.io/video/indiana/donizetti-elixir/thumbnails/thumb-nav-01.png": { - "format": "image/png", + "@explicit": true, + "accompanyingCanvas": {}, + "annotations": {}, + "behavior": {}, + "duration": {}, + "height": {}, + "homepage": {}, + "id": "https://iiif.io/api/cookbook/recipe/0229-behavior-ranges/canvas/1", + "iiif-parser:isExternal": {}, + "iiif-parser:partOf": "https://iiif.io/api/cookbook/recipe/0229-behavior-ranges/range/1.1", + "items": [ + { + "id": "https://iiif.io/api/cookbook/recipe/0229-behavior-ranges/canvas/1/annotation_page/1", + "type": "AnnotationPage", + }, + ], + "label": {}, + "metadata": {}, + "navDate": {}, + "partOf": {}, + "placeholderCanvas": {}, + "provider": {}, + "rendering": {}, + "requiredStatement": {}, + "rights": {}, + "seeAlso": {}, + "service": {}, + "summary": {}, + "thumbnail": {}, + "type": "Canvas", + "width": {}, + }, + { + "@explicit": true, + "accompanyingCanvas": {}, + "annotations": {}, + "behavior": {}, + "homepage": {}, + "id": "https://iiif.io/api/cookbook/recipe/0229-behavior-ranges/canvas/1", + "iiif-parser:isExternal": {}, + "iiif-parser:partOf": "https://iiif.io/api/cookbook/recipe/0229-behavior-ranges/range/2", + "items": [], + "label": {}, + "metadata": {}, + "navDate": {}, + "partOf": {}, + "placeholderCanvas": {}, + "provider": {}, + "rendering": {}, + "requiredStatement": {}, + "rights": {}, + "seeAlso": {}, + "service": {}, + "summary": {}, + "thumbnail": {}, + "type": "Canvas", + }, + { + "@explicit": true, + "accompanyingCanvas": {}, + "annotations": {}, + "behavior": {}, + "homepage": {}, + "id": "https://iiif.io/api/cookbook/recipe/0229-behavior-ranges/canvas/1", + "iiif-parser:isExternal": {}, + "iiif-parser:partOf": "https://iiif.io/api/cookbook/recipe/0229-behavior-ranges/range/3", + "items": [], + "label": {}, + "metadata": {}, + "navDate": {}, + "partOf": {}, + "placeholderCanvas": {}, + "provider": {}, + "rendering": {}, + "requiredStatement": {}, + "rights": {}, + "seeAlso": {}, + "service": {}, + "summary": {}, + "thumbnail": {}, + "type": "Canvas", + }, + { + "@explicit": true, + "accompanyingCanvas": {}, + "annotations": {}, + "behavior": {}, + "homepage": {}, + "id": "https://iiif.io/api/cookbook/recipe/0229-behavior-ranges/canvas/1", + "iiif-parser:isExternal": {}, + "iiif-parser:partOf": "https://iiif.io/api/cookbook/recipe/0229-behavior-ranges/range/4", + "items": [], + "label": {}, + "metadata": {}, + "navDate": {}, + "partOf": {}, + "placeholderCanvas": {}, + "provider": {}, + "rendering": {}, + "requiredStatement": {}, + "rights": {}, + "seeAlso": {}, + "service": {}, + "summary": {}, + "thumbnail": {}, + "type": "Canvas", + }, + { + "@explicit": true, + "accompanyingCanvas": {}, + "annotations": {}, + "behavior": {}, + "homepage": {}, + "id": "https://iiif.io/api/cookbook/recipe/0229-behavior-ranges/canvas/1", + "iiif-parser:isExternal": {}, + "iiif-parser:partOf": "https://iiif.io/api/cookbook/recipe/0229-behavior-ranges/range/5", + "items": [], + "label": {}, + "metadata": {}, + "navDate": {}, + "partOf": {}, + "placeholderCanvas": {}, + "provider": {}, + "rendering": {}, + "requiredStatement": {}, + "rights": {}, + "seeAlso": {}, + "service": {}, + "summary": {}, + "thumbnail": {}, + "type": "Canvas", + }, + { + "@explicit": true, + "accompanyingCanvas": {}, + "annotations": {}, + "behavior": {}, + "homepage": {}, + "id": "https://iiif.io/api/cookbook/recipe/0229-behavior-ranges/canvas/1", + "iiif-parser:isExternal": {}, + "iiif-parser:partOf": "https://iiif.io/api/cookbook/recipe/0229-behavior-ranges/range/6", + "items": [], + "label": {}, + "metadata": {}, + "navDate": {}, + "partOf": {}, + "placeholderCanvas": {}, + "provider": {}, + "rendering": {}, + "requiredStatement": {}, + "rights": {}, + "seeAlso": {}, + "service": {}, + "summary": {}, + "thumbnail": {}, + "type": "Canvas", + }, + { + "@explicit": true, + "accompanyingCanvas": {}, + "annotations": {}, + "behavior": {}, + "homepage": {}, + "id": "https://iiif.io/api/cookbook/recipe/0229-behavior-ranges/canvas/1", + "iiif-parser:isExternal": {}, + "iiif-parser:partOf": "https://iiif.io/api/cookbook/recipe/0229-behavior-ranges/range/7", + "items": [], + "label": {}, + "metadata": {}, + "navDate": {}, + "partOf": {}, + "placeholderCanvas": {}, + "provider": {}, + "rendering": {}, + "requiredStatement": {}, + "rights": {}, + "seeAlso": {}, + "service": {}, + "summary": {}, + "thumbnail": {}, + "type": "Canvas", + }, + { + "@explicit": true, + "accompanyingCanvas": {}, + "annotations": {}, + "behavior": {}, + "homepage": {}, + "id": "https://iiif.io/api/cookbook/recipe/0229-behavior-ranges/canvas/1", + "iiif-parser:isExternal": {}, + "iiif-parser:partOf": "https://iiif.io/api/cookbook/recipe/0229-behavior-ranges/range/8", + "items": [], + "label": {}, + "metadata": {}, + "navDate": {}, + "partOf": {}, + "placeholderCanvas": {}, + "provider": {}, + "rendering": {}, + "requiredStatement": {}, + "rights": {}, + "seeAlso": {}, + "service": {}, + "summary": {}, + "thumbnail": {}, + "type": "Canvas", + }, + { + "@explicit": true, + "accompanyingCanvas": {}, + "annotations": {}, + "behavior": {}, + "homepage": {}, + "id": "https://iiif.io/api/cookbook/recipe/0229-behavior-ranges/canvas/1", + "iiif-parser:isExternal": {}, + "iiif-parser:partOf": "https://iiif.io/api/cookbook/recipe/0229-behavior-ranges/range/9", + "items": [], + "label": {}, + "metadata": {}, + "navDate": {}, + "partOf": {}, + "placeholderCanvas": {}, + "provider": {}, + "rendering": {}, + "requiredStatement": {}, + "rights": {}, + "seeAlso": {}, + "service": {}, + "summary": {}, + "thumbnail": {}, + "type": "Canvas", + }, + { + "@explicit": true, + "accompanyingCanvas": {}, + "annotations": {}, + "behavior": {}, + "homepage": {}, + "id": "https://iiif.io/api/cookbook/recipe/0229-behavior-ranges/canvas/1", + "iiif-parser:isExternal": {}, + "iiif-parser:partOf": "https://iiif.io/api/cookbook/recipe/0229-behavior-ranges/range/10", + "items": [], + "label": {}, + "metadata": {}, + "navDate": {}, + "partOf": {}, + "placeholderCanvas": {}, + "provider": {}, + "rendering": {}, + "requiredStatement": {}, + "rights": {}, + "seeAlso": {}, + "service": {}, + "summary": {}, + "thumbnail": {}, + "type": "Canvas", + }, + { + "@explicit": true, + "accompanyingCanvas": {}, + "annotations": {}, + "behavior": {}, + "homepage": {}, + "id": "https://iiif.io/api/cookbook/recipe/0229-behavior-ranges/canvas/1", + "iiif-parser:isExternal": {}, + "iiif-parser:partOf": "https://iiif.io/api/cookbook/recipe/0229-behavior-ranges/range/11", + "items": [], + "label": {}, + "metadata": {}, + "navDate": {}, + "partOf": {}, + "placeholderCanvas": {}, + "provider": {}, + "rendering": {}, + "requiredStatement": {}, + "rights": {}, + "seeAlso": {}, + "service": {}, + "summary": {}, + "thumbnail": {}, + "type": "Canvas", + }, + ], + "iiif-parser:isExternal": true, + "items": [ + { + "id": "https://iiif.io/api/cookbook/recipe/0229-behavior-ranges/canvas/1/annotation_page/1", + "type": "AnnotationPage", + }, + ], + "label": null, + "metadata": [], + "navDate": null, + "partOf": [], + "placeholderCanvas": null, + "provider": [], + "rendering": [], + "requiredStatement": null, + "rights": null, + "seeAlso": [], + "service": [], + "summary": null, + "thumbnail": [], + "type": "Canvas", + "width": 1920, + }, + }, + "Collection": {}, + "ContentResource": { + "https://fixtures.iiif.io/video/indiana/donizetti-elixir/thumbnails/thumb-nav-01.png": { + "format": "image/png", "height": 1266, "id": "https://fixtures.iiif.io/video/indiana/donizetti-elixir/thumbnails/thumb-nav-01.png", "iiif-parser:hasPart": [ @@ -23894,390 +23867,30 @@ exports[`Cookbook > Testing normalize %p (%p) 89`] = ` "https://iiif.io/api/cookbook/recipe/0229-behavior-ranges/manifest.json ": { "@context": [ "http://iiif.io/api/presentation/3/context.json", - ], - "accompanyingCanvas": null, - "annotations": [], - "behavior": [], - "homepage": [], - "id": "https://iiif.io/api/cookbook/recipe/0229-behavior-ranges/manifest.json ", - "iiif-parser:hasPart": [ - { - "id": "https://iiif.io/api/cookbook/recipe/0229-behavior-ranges/manifest.json ", - "iiif-parser:partOf": "https://iiif.io/api/cookbook/recipe/0229-behavior-ranges/manifest.json ", - "type": "Manifest", - }, - ], - "items": [ - { - "id": "https://iiif.io/api/cookbook/recipe/0229-behavior-ranges/canvas/1", - "type": "Canvas", - }, - ], - "label": { - "en": [ - "Video navigation with thumbnails in a Range", - ], - }, - "metadata": [], - "navDate": null, - "partOf": [], - "placeholderCanvas": null, - "provider": [], - "rendering": [], - "requiredStatement": null, - "rights": null, - "seeAlso": [], - "service": [], - "services": [], - "start": null, - "structures": [ - { - "id": "https://iiif.io/api/cookbook/recipe/0229-behavior-ranges/range/1", - "type": "Range", - }, - ], - "summary": null, - "thumbnail": [], - "type": "Manifest", - "viewingDirection": "left-to-right", - }, - }, - "Range": { - "https://iiif.io/api/cookbook/recipe/0229-behavior-ranges/canvas/1#t=0,9": { - "accompanyingCanvas": null, - "annotations": [], - "behavior": [], - "homepage": [], - "id": "https://iiif.io/api/cookbook/recipe/0229-behavior-ranges/canvas/1#t=0,9", - "iiif-parser:hasPart": [ - { - "id": "https://iiif.io/api/cookbook/recipe/0229-behavior-ranges/canvas/1#t=0,9", - "iiif-parser:partOf": "https://iiif.io/api/cookbook/recipe/0229-behavior-ranges/range/1.1", - "type": "Canvas", - }, - ], - "items": [], - "label": null, - "metadata": [], - "navDate": null, - "partOf": [], - "placeholderCanvas": null, - "provider": [], - "rendering": [], - "requiredStatement": null, - "rights": null, - "seeAlso": [], - "service": [], - "start": null, - "summary": null, - "supplementary": null, - "thumbnail": [], - "type": "Canvas", - "viewingDirection": "left-to-right", - }, - "https://iiif.io/api/cookbook/recipe/0229-behavior-ranges/canvas/1#t=1220,1525": { - "accompanyingCanvas": null, - "annotations": [], - "behavior": [], - "homepage": [], - "id": "https://iiif.io/api/cookbook/recipe/0229-behavior-ranges/canvas/1#t=1220,1525", - "iiif-parser:hasPart": [ - { - "id": "https://iiif.io/api/cookbook/recipe/0229-behavior-ranges/canvas/1#t=1220,1525", - "iiif-parser:partOf": "https://iiif.io/api/cookbook/recipe/0229-behavior-ranges/range/6", - "type": "Canvas", - }, - ], - "items": [], - "label": null, - "metadata": [], - "navDate": null, - "partOf": [], - "placeholderCanvas": null, - "provider": [], - "rendering": [], - "requiredStatement": null, - "rights": null, - "seeAlso": [], - "service": [], - "start": null, - "summary": null, - "supplementary": null, - "thumbnail": [], - "type": "Canvas", - "viewingDirection": "left-to-right", - }, - "https://iiif.io/api/cookbook/recipe/0229-behavior-ranges/canvas/1#t=1525,1830": { - "accompanyingCanvas": null, - "annotations": [], - "behavior": [], - "homepage": [], - "id": "https://iiif.io/api/cookbook/recipe/0229-behavior-ranges/canvas/1#t=1525,1830", - "iiif-parser:hasPart": [ - { - "id": "https://iiif.io/api/cookbook/recipe/0229-behavior-ranges/canvas/1#t=1525,1830", - "iiif-parser:partOf": "https://iiif.io/api/cookbook/recipe/0229-behavior-ranges/range/7", - "type": "Canvas", - }, - ], - "items": [], - "label": null, - "metadata": [], - "navDate": null, - "partOf": [], - "placeholderCanvas": null, - "provider": [], - "rendering": [], - "requiredStatement": null, - "rights": null, - "seeAlso": [], - "service": [], - "start": null, - "summary": null, - "supplementary": null, - "thumbnail": [], - "type": "Canvas", - "viewingDirection": "left-to-right", - }, - "https://iiif.io/api/cookbook/recipe/0229-behavior-ranges/canvas/1#t=1830,2135": { - "accompanyingCanvas": null, - "annotations": [], - "behavior": [], - "homepage": [], - "id": "https://iiif.io/api/cookbook/recipe/0229-behavior-ranges/canvas/1#t=1830,2135", - "iiif-parser:hasPart": [ - { - "id": "https://iiif.io/api/cookbook/recipe/0229-behavior-ranges/canvas/1#t=1830,2135", - "iiif-parser:partOf": "https://iiif.io/api/cookbook/recipe/0229-behavior-ranges/range/8", - "type": "Canvas", - }, - ], - "items": [], - "label": null, - "metadata": [], - "navDate": null, - "partOf": [], - "placeholderCanvas": null, - "provider": [], - "rendering": [], - "requiredStatement": null, - "rights": null, - "seeAlso": [], - "service": [], - "start": null, - "summary": null, - "supplementary": null, - "thumbnail": [], - "type": "Canvas", - "viewingDirection": "left-to-right", - }, - "https://iiif.io/api/cookbook/recipe/0229-behavior-ranges/canvas/1#t=2135,2440": { - "accompanyingCanvas": null, - "annotations": [], - "behavior": [], - "homepage": [], - "id": "https://iiif.io/api/cookbook/recipe/0229-behavior-ranges/canvas/1#t=2135,2440", - "iiif-parser:hasPart": [ - { - "id": "https://iiif.io/api/cookbook/recipe/0229-behavior-ranges/canvas/1#t=2135,2440", - "iiif-parser:partOf": "https://iiif.io/api/cookbook/recipe/0229-behavior-ranges/range/9", - "type": "Canvas", - }, - ], - "items": [], - "label": null, - "metadata": [], - "navDate": null, - "partOf": [], - "placeholderCanvas": null, - "provider": [], - "rendering": [], - "requiredStatement": null, - "rights": null, - "seeAlso": [], - "service": [], - "start": null, - "summary": null, - "supplementary": null, - "thumbnail": [], - "type": "Canvas", - "viewingDirection": "left-to-right", - }, - "https://iiif.io/api/cookbook/recipe/0229-behavior-ranges/canvas/1#t=2440,2745": { - "accompanyingCanvas": null, - "annotations": [], - "behavior": [], - "homepage": [], - "id": "https://iiif.io/api/cookbook/recipe/0229-behavior-ranges/canvas/1#t=2440,2745", - "iiif-parser:hasPart": [ - { - "id": "https://iiif.io/api/cookbook/recipe/0229-behavior-ranges/canvas/1#t=2440,2745", - "iiif-parser:partOf": "https://iiif.io/api/cookbook/recipe/0229-behavior-ranges/range/10", - "type": "Canvas", - }, - ], - "items": [], - "label": null, - "metadata": [], - "navDate": null, - "partOf": [], - "placeholderCanvas": null, - "provider": [], - "rendering": [], - "requiredStatement": null, - "rights": null, - "seeAlso": [], - "service": [], - "start": null, - "summary": null, - "supplementary": null, - "thumbnail": [], - "type": "Canvas", - "viewingDirection": "left-to-right", - }, - "https://iiif.io/api/cookbook/recipe/0229-behavior-ranges/canvas/1#t=2745,3307.22": { - "accompanyingCanvas": null, - "annotations": [], - "behavior": [], - "homepage": [], - "id": "https://iiif.io/api/cookbook/recipe/0229-behavior-ranges/canvas/1#t=2745,3307.22", - "iiif-parser:hasPart": [ - { - "id": "https://iiif.io/api/cookbook/recipe/0229-behavior-ranges/canvas/1#t=2745,3307.22", - "iiif-parser:partOf": "https://iiif.io/api/cookbook/recipe/0229-behavior-ranges/range/11", - "type": "Canvas", - }, - ], - "items": [], - "label": null, - "metadata": [], - "navDate": null, - "partOf": [], - "placeholderCanvas": null, - "provider": [], - "rendering": [], - "requiredStatement": null, - "rights": null, - "seeAlso": [], - "service": [], - "start": null, - "summary": null, - "supplementary": null, - "thumbnail": [], - "type": "Canvas", - "viewingDirection": "left-to-right", - }, - "https://iiif.io/api/cookbook/recipe/0229-behavior-ranges/canvas/1#t=305,610": { - "accompanyingCanvas": null, - "annotations": [], - "behavior": [], - "homepage": [], - "id": "https://iiif.io/api/cookbook/recipe/0229-behavior-ranges/canvas/1#t=305,610", - "iiif-parser:hasPart": [ - { - "id": "https://iiif.io/api/cookbook/recipe/0229-behavior-ranges/canvas/1#t=305,610", - "iiif-parser:partOf": "https://iiif.io/api/cookbook/recipe/0229-behavior-ranges/range/3", - "type": "Canvas", - }, - ], - "items": [], - "label": null, - "metadata": [], - "navDate": null, - "partOf": [], - "placeholderCanvas": null, - "provider": [], - "rendering": [], - "requiredStatement": null, - "rights": null, - "seeAlso": [], - "service": [], - "start": null, - "summary": null, - "supplementary": null, - "thumbnail": [], - "type": "Canvas", - "viewingDirection": "left-to-right", - }, - "https://iiif.io/api/cookbook/recipe/0229-behavior-ranges/canvas/1#t=610,915": { - "accompanyingCanvas": null, - "annotations": [], - "behavior": [], - "homepage": [], - "id": "https://iiif.io/api/cookbook/recipe/0229-behavior-ranges/canvas/1#t=610,915", - "iiif-parser:hasPart": [ - { - "id": "https://iiif.io/api/cookbook/recipe/0229-behavior-ranges/canvas/1#t=610,915", - "iiif-parser:partOf": "https://iiif.io/api/cookbook/recipe/0229-behavior-ranges/range/4", - "type": "Canvas", - }, - ], - "items": [], - "label": null, - "metadata": [], - "navDate": null, - "partOf": [], - "placeholderCanvas": null, - "provider": [], - "rendering": [], - "requiredStatement": null, - "rights": null, - "seeAlso": [], - "service": [], - "start": null, - "summary": null, - "supplementary": null, - "thumbnail": [], - "type": "Canvas", - "viewingDirection": "left-to-right", - }, - "https://iiif.io/api/cookbook/recipe/0229-behavior-ranges/canvas/1#t=9,305": { - "accompanyingCanvas": null, - "annotations": [], - "behavior": [], - "homepage": [], - "id": "https://iiif.io/api/cookbook/recipe/0229-behavior-ranges/canvas/1#t=9,305", - "iiif-parser:hasPart": [ - { - "id": "https://iiif.io/api/cookbook/recipe/0229-behavior-ranges/canvas/1#t=9,305", - "iiif-parser:partOf": "https://iiif.io/api/cookbook/recipe/0229-behavior-ranges/range/2", - "type": "Canvas", - }, - ], - "items": [], - "label": null, - "metadata": [], - "navDate": null, - "partOf": [], - "placeholderCanvas": null, - "provider": [], - "rendering": [], - "requiredStatement": null, - "rights": null, - "seeAlso": [], - "service": [], - "start": null, - "summary": null, - "supplementary": null, - "thumbnail": [], - "type": "Canvas", - "viewingDirection": "left-to-right", - }, - "https://iiif.io/api/cookbook/recipe/0229-behavior-ranges/canvas/1#t=915,1220": { + ], "accompanyingCanvas": null, "annotations": [], "behavior": [], "homepage": [], - "id": "https://iiif.io/api/cookbook/recipe/0229-behavior-ranges/canvas/1#t=915,1220", + "id": "https://iiif.io/api/cookbook/recipe/0229-behavior-ranges/manifest.json ", "iiif-parser:hasPart": [ { - "id": "https://iiif.io/api/cookbook/recipe/0229-behavior-ranges/canvas/1#t=915,1220", - "iiif-parser:partOf": "https://iiif.io/api/cookbook/recipe/0229-behavior-ranges/range/5", + "id": "https://iiif.io/api/cookbook/recipe/0229-behavior-ranges/manifest.json ", + "iiif-parser:partOf": "https://iiif.io/api/cookbook/recipe/0229-behavior-ranges/manifest.json ", + "type": "Manifest", + }, + ], + "items": [ + { + "id": "https://iiif.io/api/cookbook/recipe/0229-behavior-ranges/canvas/1", "type": "Canvas", }, ], - "items": [], - "label": null, + "label": { + "en": [ + "Video navigation with thumbnails in a Range", + ], + }, "metadata": [], "navDate": null, "partOf": [], @@ -24288,13 +23901,21 @@ exports[`Cookbook > Testing normalize %p (%p) 89`] = ` "rights": null, "seeAlso": [], "service": [], + "services": [], "start": null, + "structures": [ + { + "id": "https://iiif.io/api/cookbook/recipe/0229-behavior-ranges/range/1", + "type": "Range", + }, + ], "summary": null, - "supplementary": null, "thumbnail": [], - "type": "Canvas", + "type": "Manifest", "viewingDirection": "left-to-right", }, + }, + "Range": { "https://iiif.io/api/cookbook/recipe/0229-behavior-ranges/range/1": { "accompanyingCanvas": null, "annotations": [], @@ -24964,17 +24585,6 @@ exports[`Cookbook > Testing normalize %p (%p) 89`] = ` "https://fixtures.iiif.io/video/indiana/donizetti-elixir/thumbnails/thumb-nav-10.png": "ContentResource", "https://fixtures.iiif.io/video/indiana/donizetti-elixir/vae0637_accessH264_low_act_2.mp4": "ContentResource", "https://iiif.io/api/cookbook/recipe/0229-behavior-ranges/canvas/1": "Canvas", - "https://iiif.io/api/cookbook/recipe/0229-behavior-ranges/canvas/1#t=0,9": "Canvas", - "https://iiif.io/api/cookbook/recipe/0229-behavior-ranges/canvas/1#t=1220,1525": "Canvas", - "https://iiif.io/api/cookbook/recipe/0229-behavior-ranges/canvas/1#t=1525,1830": "Canvas", - "https://iiif.io/api/cookbook/recipe/0229-behavior-ranges/canvas/1#t=1830,2135": "Canvas", - "https://iiif.io/api/cookbook/recipe/0229-behavior-ranges/canvas/1#t=2135,2440": "Canvas", - "https://iiif.io/api/cookbook/recipe/0229-behavior-ranges/canvas/1#t=2440,2745": "Canvas", - "https://iiif.io/api/cookbook/recipe/0229-behavior-ranges/canvas/1#t=2745,3307.22": "Canvas", - "https://iiif.io/api/cookbook/recipe/0229-behavior-ranges/canvas/1#t=305,610": "Canvas", - "https://iiif.io/api/cookbook/recipe/0229-behavior-ranges/canvas/1#t=610,915": "Canvas", - "https://iiif.io/api/cookbook/recipe/0229-behavior-ranges/canvas/1#t=9,305": "Canvas", - "https://iiif.io/api/cookbook/recipe/0229-behavior-ranges/canvas/1#t=915,1220": "Canvas", "https://iiif.io/api/cookbook/recipe/0229-behavior-ranges/canvas/1/annotation_page/1": "AnnotationPage", "https://iiif.io/api/cookbook/recipe/0229-behavior-ranges/canvas/1/annotation_page/1/annotation/2": "Annotation", "https://iiif.io/api/cookbook/recipe/0229-behavior-ranges/manifest.json ": "Manifest", @@ -26418,6 +26028,97 @@ exports[`Cookbook > Testing normalize %p (%p) 99`] = ` "height": 1080, "homepage": [], "id": "https://iiif.io/api/cookbook/recipe/0064-opera-one-canvas/canvas/1", + "iiif-parser:hasPart": [ + { + "@explicit": true, + "accompanyingCanvas": {}, + "annotations": {}, + "behavior": {}, + "duration": {}, + "height": {}, + "homepage": {}, + "id": "https://iiif.io/api/cookbook/recipe/0064-opera-one-canvas/canvas/1", + "iiif-parser:isExternal": {}, + "iiif-parser:partOf": "https://iiif.io/api/cookbook/recipe/0064-opera-one-canvas/range/3", + "items": [ + { + "id": "https://iiif.io/api/cookbook/recipe/0064-opera-one-canvas/canvas/1/annotation_page/1", + "type": "AnnotationPage", + }, + ], + "label": {}, + "metadata": {}, + "navDate": {}, + "partOf": {}, + "placeholderCanvas": {}, + "provider": {}, + "rendering": {}, + "requiredStatement": {}, + "rights": {}, + "seeAlso": {}, + "service": {}, + "summary": {}, + "thumbnail": [ + { + "id": "https://fixtures.iiif.io/video/indiana/donizetti-elixir/act1-thumbnail.png", + "type": "ContentResource", + }, + ], + "type": "Canvas", + "width": {}, + }, + { + "@explicit": true, + "accompanyingCanvas": {}, + "annotations": {}, + "behavior": {}, + "homepage": {}, + "id": "https://iiif.io/api/cookbook/recipe/0064-opera-one-canvas/canvas/1", + "iiif-parser:isExternal": {}, + "iiif-parser:partOf": "https://iiif.io/api/cookbook/recipe/0064-opera-one-canvas/range/4", + "items": [], + "label": {}, + "metadata": {}, + "navDate": {}, + "partOf": {}, + "placeholderCanvas": {}, + "provider": {}, + "rendering": {}, + "requiredStatement": {}, + "rights": {}, + "seeAlso": {}, + "service": {}, + "summary": {}, + "thumbnail": [], + "type": "Canvas", + }, + { + "@explicit": true, + "accompanyingCanvas": {}, + "annotations": {}, + "behavior": {}, + "homepage": {}, + "id": "https://iiif.io/api/cookbook/recipe/0064-opera-one-canvas/canvas/1", + "iiif-parser:isExternal": {}, + "iiif-parser:partOf": "https://iiif.io/api/cookbook/recipe/0064-opera-one-canvas/range/5", + "items": [], + "label": {}, + "metadata": {}, + "navDate": {}, + "partOf": {}, + "placeholderCanvas": {}, + "provider": {}, + "rendering": {}, + "requiredStatement": {}, + "rights": {}, + "seeAlso": {}, + "service": {}, + "summary": {}, + "thumbnail": [], + "type": "Canvas", + }, + ], + "iiif-parser:isExternal": true, "items": [ { "id": "https://iiif.io/api/cookbook/recipe/0064-opera-one-canvas/canvas/1/annotation_page/1", @@ -26569,102 +26270,6 @@ exports[`Cookbook > Testing normalize %p (%p) 99`] = ` }, }, "Range": { - "https://iiif.io/api/cookbook/recipe/0064-opera-one-canvas/canvas/1#t=0,302.05": { - "accompanyingCanvas": null, - "annotations": [], - "behavior": [], - "homepage": [], - "id": "https://iiif.io/api/cookbook/recipe/0064-opera-one-canvas/canvas/1#t=0,302.05", - "iiif-parser:hasPart": [ - { - "id": "https://iiif.io/api/cookbook/recipe/0064-opera-one-canvas/canvas/1#t=0,302.05", - "iiif-parser:partOf": "https://iiif.io/api/cookbook/recipe/0064-opera-one-canvas/range/3", - "type": "Canvas", - }, - ], - "items": [], - "label": null, - "metadata": [], - "navDate": null, - "partOf": [], - "placeholderCanvas": null, - "provider": [], - "rendering": [], - "requiredStatement": null, - "rights": null, - "seeAlso": [], - "service": [], - "start": null, - "summary": null, - "supplementary": null, - "thumbnail": [], - "type": "Canvas", - "viewingDirection": "left-to-right", - }, - "https://iiif.io/api/cookbook/recipe/0064-opera-one-canvas/canvas/1#t=302.05,3971.24": { - "accompanyingCanvas": null, - "annotations": [], - "behavior": [], - "homepage": [], - "id": "https://iiif.io/api/cookbook/recipe/0064-opera-one-canvas/canvas/1#t=302.05,3971.24", - "iiif-parser:hasPart": [ - { - "id": "https://iiif.io/api/cookbook/recipe/0064-opera-one-canvas/canvas/1#t=302.05,3971.24", - "iiif-parser:partOf": "https://iiif.io/api/cookbook/recipe/0064-opera-one-canvas/range/4", - "type": "Canvas", - }, - ], - "items": [], - "label": null, - "metadata": [], - "navDate": null, - "partOf": [], - "placeholderCanvas": null, - "provider": [], - "rendering": [], - "requiredStatement": null, - "rights": null, - "seeAlso": [], - "service": [], - "start": null, - "summary": null, - "supplementary": null, - "thumbnail": [], - "type": "Canvas", - "viewingDirection": "left-to-right", - }, - "https://iiif.io/api/cookbook/recipe/0064-opera-one-canvas/canvas/1#t=3971.24,7278.422": { - "accompanyingCanvas": null, - "annotations": [], - "behavior": [], - "homepage": [], - "id": "https://iiif.io/api/cookbook/recipe/0064-opera-one-canvas/canvas/1#t=3971.24,7278.422", - "iiif-parser:hasPart": [ - { - "id": "https://iiif.io/api/cookbook/recipe/0064-opera-one-canvas/canvas/1#t=3971.24,7278.422", - "iiif-parser:partOf": "https://iiif.io/api/cookbook/recipe/0064-opera-one-canvas/range/5", - "type": "Canvas", - }, - ], - "items": [], - "label": null, - "metadata": [], - "navDate": null, - "partOf": [], - "placeholderCanvas": null, - "provider": [], - "rendering": [], - "requiredStatement": null, - "rights": null, - "seeAlso": [], - "service": [], - "start": null, - "summary": null, - "supplementary": null, - "thumbnail": [], - "type": "Canvas", - "viewingDirection": "left-to-right", - }, "https://iiif.io/api/cookbook/recipe/0064-opera-one-canvas/range/1": { "accompanyingCanvas": null, "annotations": [], @@ -26901,9 +26506,6 @@ exports[`Cookbook > Testing normalize %p (%p) 99`] = ` "https://fixtures.iiif.io/video/indiana/donizetti-elixir/vae0637_accessH264_low_act_1.mp4": "ContentResource", "https://fixtures.iiif.io/video/indiana/donizetti-elixir/vae0637_accessH264_low_act_2.mp4": "ContentResource", "https://iiif.io/api/cookbook/recipe/0064-opera-one-canvas/canvas/1": "Canvas", - "https://iiif.io/api/cookbook/recipe/0064-opera-one-canvas/canvas/1#t=0,302.05": "Canvas", - "https://iiif.io/api/cookbook/recipe/0064-opera-one-canvas/canvas/1#t=302.05,3971.24": "Canvas", - "https://iiif.io/api/cookbook/recipe/0064-opera-one-canvas/canvas/1#t=3971.24,7278.422": "Canvas", "https://iiif.io/api/cookbook/recipe/0064-opera-one-canvas/canvas/1/annotation_page/1": "AnnotationPage", "https://iiif.io/api/cookbook/recipe/0064-opera-one-canvas/canvas/1/annotation_page/1/annotation/1": "Annotation", "https://iiif.io/api/cookbook/recipe/0064-opera-one-canvas/canvas/1/annotation_page/1/annotation/2": "Annotation", @@ -27213,6 +26815,71 @@ exports[`Cookbook > Testing normalize %p (%p) 101`] = ` "height": 1080, "homepage": [], "id": "https://iiif.io/api/cookbook/recipe/0065-opera-multiple-canvases/canvas/1", + "iiif-parser:hasPart": [ + { + "@explicit": true, + "accompanyingCanvas": {}, + "annotations": {}, + "behavior": {}, + "duration": {}, + "height": {}, + "homepage": {}, + "id": "https://iiif.io/api/cookbook/recipe/0065-opera-multiple-canvases/canvas/1", + "iiif-parser:isExternal": {}, + "iiif-parser:partOf": "https://iiif.io/api/cookbook/recipe/0065-opera-multiple-canvases/range/3", + "items": [ + { + "id": "https://iiif.io/api/cookbook/recipe/0065-opera-multiple-canvases/canvas/1/annotation_page/1", + "type": "AnnotationPage", + }, + ], + "label": {}, + "metadata": {}, + "navDate": {}, + "partOf": {}, + "placeholderCanvas": {}, + "provider": {}, + "rendering": {}, + "requiredStatement": {}, + "rights": {}, + "seeAlso": {}, + "service": {}, + "summary": {}, + "thumbnail": [ + { + "id": "https://fixtures.iiif.io/video/indiana/donizetti-elixir/act1-thumbnail.png", + "type": "ContentResource", + }, + ], + "type": "Canvas", + "width": {}, + }, + { + "@explicit": true, + "accompanyingCanvas": {}, + "annotations": {}, + "behavior": {}, + "homepage": {}, + "id": "https://iiif.io/api/cookbook/recipe/0065-opera-multiple-canvases/canvas/1", + "iiif-parser:isExternal": {}, + "iiif-parser:partOf": "https://iiif.io/api/cookbook/recipe/0065-opera-multiple-canvases/range/4", + "items": [], + "metadata": {}, + "navDate": {}, + "partOf": {}, + "placeholderCanvas": {}, + "provider": {}, + "rendering": {}, + "requiredStatement": {}, + "rights": {}, + "seeAlso": {}, + "service": {}, + "summary": {}, + "thumbnail": [], + "type": "Canvas", + }, + ], + "iiif-parser:isExternal": true, "items": [ { "id": "https://iiif.io/api/cookbook/recipe/0065-opera-multiple-canvases/canvas/1/annotation_page/1", @@ -27252,6 +26919,14 @@ exports[`Cookbook > Testing normalize %p (%p) 101`] = ` "height": 1080, "homepage": [], "id": "https://iiif.io/api/cookbook/recipe/0065-opera-multiple-canvases/canvas/2", + "iiif-parser:hasPart": [ + { + "id": "https://iiif.io/api/cookbook/recipe/0065-opera-multiple-canvases/canvas/2", + "iiif-parser:partOf": "https://iiif.io/api/cookbook/recipe/0065-opera-multiple-canvases/range/5", + "type": "Canvas", + }, + ], + "iiif-parser:isExternal": true, "items": [ { "id": "https://iiif.io/api/cookbook/recipe/0065-opera-multiple-canvases/canvas/2/annotation_page/1", @@ -27422,102 +27097,6 @@ exports[`Cookbook > Testing normalize %p (%p) 101`] = ` }, }, "Range": { - "https://iiif.io/api/cookbook/recipe/0065-opera-multiple-canvases/canvas/1#t=0,302.05": { - "accompanyingCanvas": null, - "annotations": [], - "behavior": [], - "homepage": [], - "id": "https://iiif.io/api/cookbook/recipe/0065-opera-multiple-canvases/canvas/1#t=0,302.05", - "iiif-parser:hasPart": [ - { - "id": "https://iiif.io/api/cookbook/recipe/0065-opera-multiple-canvases/canvas/1#t=0,302.05", - "iiif-parser:partOf": "https://iiif.io/api/cookbook/recipe/0065-opera-multiple-canvases/range/3", - "type": "Canvas", - }, - ], - "items": [], - "label": null, - "metadata": [], - "navDate": null, - "partOf": [], - "placeholderCanvas": null, - "provider": [], - "rendering": [], - "requiredStatement": null, - "rights": null, - "seeAlso": [], - "service": [], - "start": null, - "summary": null, - "supplementary": null, - "thumbnail": [], - "type": "Canvas", - "viewingDirection": "left-to-right", - }, - "https://iiif.io/api/cookbook/recipe/0065-opera-multiple-canvases/canvas/1#t=302.05,3971.24": { - "accompanyingCanvas": null, - "annotations": [], - "behavior": [], - "homepage": [], - "id": "https://iiif.io/api/cookbook/recipe/0065-opera-multiple-canvases/canvas/1#t=302.05,3971.24", - "iiif-parser:hasPart": [ - { - "id": "https://iiif.io/api/cookbook/recipe/0065-opera-multiple-canvases/canvas/1#t=302.05,3971.24", - "iiif-parser:partOf": "https://iiif.io/api/cookbook/recipe/0065-opera-multiple-canvases/range/4", - "type": "Canvas", - }, - ], - "items": [], - "label": null, - "metadata": [], - "navDate": null, - "partOf": [], - "placeholderCanvas": null, - "provider": [], - "rendering": [], - "requiredStatement": null, - "rights": null, - "seeAlso": [], - "service": [], - "start": null, - "summary": null, - "supplementary": null, - "thumbnail": [], - "type": "Canvas", - "viewingDirection": "left-to-right", - }, - "https://iiif.io/api/cookbook/recipe/0065-opera-multiple-canvases/canvas/2#t=0,3307.22": { - "accompanyingCanvas": null, - "annotations": [], - "behavior": [], - "homepage": [], - "id": "https://iiif.io/api/cookbook/recipe/0065-opera-multiple-canvases/canvas/2#t=0,3307.22", - "iiif-parser:hasPart": [ - { - "id": "https://iiif.io/api/cookbook/recipe/0065-opera-multiple-canvases/canvas/2#t=0,3307.22", - "iiif-parser:partOf": "https://iiif.io/api/cookbook/recipe/0065-opera-multiple-canvases/range/5", - "type": "Canvas", - }, - ], - "items": [], - "label": null, - "metadata": [], - "navDate": null, - "partOf": [], - "placeholderCanvas": null, - "provider": [], - "rendering": [], - "requiredStatement": null, - "rights": null, - "seeAlso": [], - "service": [], - "start": null, - "summary": null, - "supplementary": null, - "thumbnail": [], - "type": "Canvas", - "viewingDirection": "left-to-right", - }, "https://iiif.io/api/cookbook/recipe/0065-opera-multiple-canvases/range/1": { "accompanyingCanvas": null, "annotations": [], @@ -27755,12 +27334,9 @@ exports[`Cookbook > Testing normalize %p (%p) 101`] = ` "https://fixtures.iiif.io/video/indiana/donizetti-elixir/vae0637_accessH264_low_act_1.mp4": "ContentResource", "https://fixtures.iiif.io/video/indiana/donizetti-elixir/vae0637_accessH264_low_act_2.mp4": "ContentResource", "https://iiif.io/api/cookbook/recipe/0065-opera-multiple-canvases/canvas/1": "Canvas", - "https://iiif.io/api/cookbook/recipe/0065-opera-multiple-canvases/canvas/1#t=0,302.05": "Canvas", - "https://iiif.io/api/cookbook/recipe/0065-opera-multiple-canvases/canvas/1#t=302.05,3971.24": "Canvas", "https://iiif.io/api/cookbook/recipe/0065-opera-multiple-canvases/canvas/1/annotation_page/1": "AnnotationPage", "https://iiif.io/api/cookbook/recipe/0065-opera-multiple-canvases/canvas/1/annotation_page/1/annotation/1": "Annotation", "https://iiif.io/api/cookbook/recipe/0065-opera-multiple-canvases/canvas/2": "Canvas", - "https://iiif.io/api/cookbook/recipe/0065-opera-multiple-canvases/canvas/2#t=0,3307.22": "Canvas", "https://iiif.io/api/cookbook/recipe/0065-opera-multiple-canvases/canvas/2/annotation_page/1": "AnnotationPage", "https://iiif.io/api/cookbook/recipe/0065-opera-multiple-canvases/canvas/2/annotation_page/1/annotation/1": "Annotation", "https://iiif.io/api/cookbook/recipe/0065-opera-multiple-canvases/manifest.json": "Manifest", diff --git a/__tests__/presentation-3-parser/__snapshots__/smoke.tests.ts.snap b/__tests__/presentation-3-parser/__snapshots__/smoke.tests.ts.snap index c694541..79ad407 100644 --- a/__tests__/presentation-3-parser/__snapshots__/smoke.tests.ts.snap +++ b/__tests__/presentation-3-parser/__snapshots__/smoke.tests.ts.snap @@ -838,6 +838,14 @@ exports[`Smoke tests > Smoke test: ./fixtures/presentation-3/bl-ranges.json 1`] "height": 0, "homepage": [], "id": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000004", + "iiif-parser:hasPart": [ + { + "id": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000004", + "iiif-parser:partOf": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x00000d", + "type": "Canvas", + }, + ], + "iiif-parser:isExternal": true, "items": [ { "id": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000004/anno1", @@ -872,6 +880,14 @@ exports[`Smoke tests > Smoke test: ./fixtures/presentation-3/bl-ranges.json 1`] "height": 0, "homepage": [], "id": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000005", + "iiif-parser:hasPart": [ + { + "id": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000005", + "iiif-parser:partOf": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x00000e", + "type": "Canvas", + }, + ], + "iiif-parser:isExternal": true, "items": [ { "id": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000005/anno2", @@ -906,6 +922,172 @@ exports[`Smoke tests > Smoke test: ./fixtures/presentation-3/bl-ranges.json 1`] "height": 0, "homepage": [], "id": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000007", + "iiif-parser:hasPart": [ + { + "@explicit": true, + "accompanyingCanvas": {}, + "annotations": {}, + "behavior": {}, + "duration": {}, + "height": {}, + "homepage": {}, + "id": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000007", + "iiif-parser:isExternal": {}, + "iiif-parser:partOf": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x00000c/nn3", + "items": [ + { + "id": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000007/anno3", + "type": "AnnotationPage", + }, + ], + "label": {}, + "metadata": {}, + "navDate": {}, + "partOf": {}, + "placeholderCanvas": {}, + "provider": {}, + "rendering": {}, + "requiredStatement": {}, + "rights": {}, + "seeAlso": {}, + "service": {}, + "summary": {}, + "thumbnail": {}, + "type": "Canvas", + "width": {}, + }, + { + "@explicit": true, + "accompanyingCanvas": {}, + "annotations": {}, + "behavior": {}, + "height": {}, + "homepage": {}, + "id": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000007", + "iiif-parser:isExternal": {}, + "iiif-parser:partOf": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x00000f", + "items": [], + "metadata": {}, + "navDate": {}, + "partOf": {}, + "placeholderCanvas": {}, + "provider": {}, + "rendering": {}, + "requiredStatement": {}, + "rights": {}, + "seeAlso": {}, + "service": {}, + "summary": {}, + "thumbnail": {}, + "type": "Canvas", + "width": {}, + }, + { + "@explicit": true, + "accompanyingCanvas": {}, + "annotations": {}, + "behavior": {}, + "height": {}, + "homepage": {}, + "id": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000007", + "iiif-parser:isExternal": {}, + "iiif-parser:partOf": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000016", + "items": [], + "metadata": {}, + "navDate": {}, + "partOf": {}, + "placeholderCanvas": {}, + "provider": {}, + "rendering": {}, + "requiredStatement": {}, + "rights": {}, + "seeAlso": {}, + "service": {}, + "summary": {}, + "thumbnail": {}, + "type": "Canvas", + "width": {}, + }, + { + "@explicit": true, + "accompanyingCanvas": {}, + "annotations": {}, + "behavior": {}, + "height": {}, + "homepage": {}, + "id": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000007", + "iiif-parser:isExternal": {}, + "iiif-parser:partOf": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000016/nn3", + "items": [], + "metadata": {}, + "navDate": {}, + "partOf": {}, + "placeholderCanvas": {}, + "provider": {}, + "rendering": {}, + "requiredStatement": {}, + "rights": {}, + "seeAlso": {}, + "service": {}, + "summary": {}, + "thumbnail": {}, + "type": "Canvas", + "width": {}, + }, + { + "@explicit": true, + "accompanyingCanvas": {}, + "annotations": {}, + "behavior": {}, + "height": {}, + "homepage": {}, + "id": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000007", + "iiif-parser:isExternal": {}, + "iiif-parser:partOf": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000018", + "items": [], + "metadata": {}, + "navDate": {}, + "partOf": {}, + "placeholderCanvas": {}, + "provider": {}, + "rendering": {}, + "requiredStatement": {}, + "rights": {}, + "seeAlso": {}, + "service": {}, + "summary": {}, + "thumbnail": {}, + "type": "Canvas", + "width": {}, + }, + { + "@explicit": true, + "accompanyingCanvas": {}, + "annotations": {}, + "behavior": {}, + "height": {}, + "homepage": {}, + "id": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000007", + "iiif-parser:isExternal": {}, + "iiif-parser:partOf": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x00001b", + "items": [], + "metadata": {}, + "navDate": {}, + "partOf": {}, + "placeholderCanvas": {}, + "provider": {}, + "rendering": {}, + "requiredStatement": {}, + "rights": {}, + "seeAlso": {}, + "service": {}, + "summary": {}, + "thumbnail": {}, + "type": "Canvas", + "width": {}, + }, + ], + "iiif-parser:isExternal": true, "items": [ { "id": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000007/anno3", @@ -940,6 +1122,224 @@ exports[`Smoke tests > Smoke test: ./fixtures/presentation-3/bl-ranges.json 1`] "height": 0, "homepage": [], "id": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000008", + "iiif-parser:hasPart": [ + { + "@explicit": true, + "accompanyingCanvas": {}, + "annotations": {}, + "behavior": {}, + "duration": {}, + "height": {}, + "homepage": {}, + "id": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000008", + "iiif-parser:isExternal": {}, + "iiif-parser:partOf": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x00001b", + "items": [ + { + "id": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000008/anno4", + "type": "AnnotationPage", + }, + ], + "label": {}, + "metadata": {}, + "navDate": {}, + "partOf": {}, + "placeholderCanvas": {}, + "provider": {}, + "rendering": {}, + "requiredStatement": {}, + "rights": {}, + "seeAlso": {}, + "service": {}, + "summary": {}, + "thumbnail": {}, + "type": "Canvas", + "width": {}, + }, + { + "@explicit": true, + "accompanyingCanvas": {}, + "annotations": {}, + "behavior": {}, + "height": {}, + "homepage": {}, + "id": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000008", + "iiif-parser:isExternal": {}, + "iiif-parser:partOf": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x00001b/nn2", + "items": [], + "metadata": {}, + "navDate": {}, + "partOf": {}, + "placeholderCanvas": {}, + "provider": {}, + "rendering": {}, + "requiredStatement": {}, + "rights": {}, + "seeAlso": {}, + "service": {}, + "summary": {}, + "thumbnail": {}, + "type": "Canvas", + "width": {}, + }, + { + "@explicit": true, + "accompanyingCanvas": {}, + "annotations": {}, + "behavior": {}, + "height": {}, + "homepage": {}, + "id": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000008", + "iiif-parser:isExternal": {}, + "iiif-parser:partOf": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x00001c", + "items": [], + "metadata": {}, + "navDate": {}, + "partOf": {}, + "placeholderCanvas": {}, + "provider": {}, + "rendering": {}, + "requiredStatement": {}, + "rights": {}, + "seeAlso": {}, + "service": {}, + "summary": {}, + "thumbnail": {}, + "type": "Canvas", + "width": {}, + }, + { + "@explicit": true, + "accompanyingCanvas": {}, + "annotations": {}, + "behavior": {}, + "height": {}, + "homepage": {}, + "id": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000008", + "iiif-parser:isExternal": {}, + "iiif-parser:partOf": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x00001c/nn3", + "items": [], + "metadata": {}, + "navDate": {}, + "partOf": {}, + "placeholderCanvas": {}, + "provider": {}, + "rendering": {}, + "requiredStatement": {}, + "rights": {}, + "seeAlso": {}, + "service": {}, + "summary": {}, + "thumbnail": {}, + "type": "Canvas", + "width": {}, + }, + { + "@explicit": true, + "accompanyingCanvas": {}, + "annotations": {}, + "behavior": {}, + "height": {}, + "homepage": {}, + "id": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000008", + "iiif-parser:isExternal": {}, + "iiif-parser:partOf": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x00001d", + "items": [], + "metadata": {}, + "navDate": {}, + "partOf": {}, + "placeholderCanvas": {}, + "provider": {}, + "rendering": {}, + "requiredStatement": {}, + "rights": {}, + "seeAlso": {}, + "service": {}, + "summary": {}, + "thumbnail": {}, + "type": "Canvas", + "width": {}, + }, + { + "@explicit": true, + "accompanyingCanvas": {}, + "annotations": {}, + "behavior": {}, + "height": {}, + "homepage": {}, + "id": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000008", + "iiif-parser:isExternal": {}, + "iiif-parser:partOf": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000024", + "items": [], + "metadata": {}, + "navDate": {}, + "partOf": {}, + "placeholderCanvas": {}, + "provider": {}, + "rendering": {}, + "requiredStatement": {}, + "rights": {}, + "seeAlso": {}, + "service": {}, + "summary": {}, + "thumbnail": {}, + "type": "Canvas", + "width": {}, + }, + { + "@explicit": true, + "accompanyingCanvas": {}, + "annotations": {}, + "behavior": {}, + "height": {}, + "homepage": {}, + "id": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000008", + "iiif-parser:isExternal": {}, + "iiif-parser:partOf": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000024/nn6", + "items": [], + "metadata": {}, + "navDate": {}, + "partOf": {}, + "placeholderCanvas": {}, + "provider": {}, + "rendering": {}, + "requiredStatement": {}, + "rights": {}, + "seeAlso": {}, + "service": {}, + "summary": {}, + "thumbnail": {}, + "type": "Canvas", + "width": {}, + }, + { + "@explicit": true, + "accompanyingCanvas": {}, + "annotations": {}, + "behavior": {}, + "height": {}, + "homepage": {}, + "id": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000008", + "iiif-parser:isExternal": {}, + "iiif-parser:partOf": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000026", + "items": [], + "metadata": {}, + "navDate": {}, + "partOf": {}, + "placeholderCanvas": {}, + "provider": {}, + "rendering": {}, + "requiredStatement": {}, + "rights": {}, + "seeAlso": {}, + "service": {}, + "summary": {}, + "thumbnail": {}, + "type": "Canvas", + "width": {}, + }, + ], + "iiif-parser:isExternal": true, "items": [ { "id": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000008/anno4", @@ -974,6 +1374,198 @@ exports[`Smoke tests > Smoke test: ./fixtures/presentation-3/bl-ranges.json 1`] "height": 0, "homepage": [], "id": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x00000a", + "iiif-parser:hasPart": [ + { + "@explicit": true, + "accompanyingCanvas": {}, + "annotations": {}, + "behavior": {}, + "duration": {}, + "height": {}, + "homepage": {}, + "id": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x00000a", + "iiif-parser:isExternal": {}, + "iiif-parser:partOf": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000028", + "items": [ + { + "id": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x00000a/anno5", + "type": "AnnotationPage", + }, + ], + "label": {}, + "metadata": {}, + "navDate": {}, + "partOf": {}, + "placeholderCanvas": {}, + "provider": {}, + "rendering": {}, + "requiredStatement": {}, + "rights": {}, + "seeAlso": {}, + "service": {}, + "summary": {}, + "thumbnail": {}, + "type": "Canvas", + "width": {}, + }, + { + "@explicit": true, + "accompanyingCanvas": {}, + "annotations": {}, + "behavior": {}, + "height": {}, + "homepage": {}, + "id": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x00000a", + "iiif-parser:isExternal": {}, + "iiif-parser:partOf": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000028/nn8", + "items": [], + "metadata": {}, + "navDate": {}, + "partOf": {}, + "placeholderCanvas": {}, + "provider": {}, + "rendering": {}, + "requiredStatement": {}, + "rights": {}, + "seeAlso": {}, + "service": {}, + "summary": {}, + "thumbnail": {}, + "type": "Canvas", + "width": {}, + }, + { + "@explicit": true, + "accompanyingCanvas": {}, + "annotations": {}, + "behavior": {}, + "height": {}, + "homepage": {}, + "id": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x00000a", + "iiif-parser:isExternal": {}, + "iiif-parser:partOf": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x00002a", + "items": [], + "metadata": {}, + "navDate": {}, + "partOf": {}, + "placeholderCanvas": {}, + "provider": {}, + "rendering": {}, + "requiredStatement": {}, + "rights": {}, + "seeAlso": {}, + "service": {}, + "summary": {}, + "thumbnail": {}, + "type": "Canvas", + "width": {}, + }, + { + "@explicit": true, + "accompanyingCanvas": {}, + "annotations": {}, + "behavior": {}, + "height": {}, + "homepage": {}, + "id": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x00000a", + "iiif-parser:isExternal": {}, + "iiif-parser:partOf": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x00002a/nn9", + "items": [], + "metadata": {}, + "navDate": {}, + "partOf": {}, + "placeholderCanvas": {}, + "provider": {}, + "rendering": {}, + "requiredStatement": {}, + "rights": {}, + "seeAlso": {}, + "service": {}, + "summary": {}, + "thumbnail": {}, + "type": "Canvas", + "width": {}, + }, + { + "@explicit": true, + "accompanyingCanvas": {}, + "annotations": {}, + "behavior": {}, + "height": {}, + "homepage": {}, + "id": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x00000a", + "iiif-parser:isExternal": {}, + "iiif-parser:partOf": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x00002c", + "items": [], + "metadata": {}, + "navDate": {}, + "partOf": {}, + "placeholderCanvas": {}, + "provider": {}, + "rendering": {}, + "requiredStatement": {}, + "rights": {}, + "seeAlso": {}, + "service": {}, + "summary": {}, + "thumbnail": {}, + "type": "Canvas", + "width": {}, + }, + { + "@explicit": true, + "accompanyingCanvas": {}, + "annotations": {}, + "behavior": {}, + "height": {}, + "homepage": {}, + "id": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x00000a", + "iiif-parser:isExternal": {}, + "iiif-parser:partOf": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000002/top/nn9", + "items": [], + "metadata": {}, + "navDate": {}, + "partOf": {}, + "placeholderCanvas": {}, + "provider": {}, + "rendering": {}, + "requiredStatement": {}, + "rights": {}, + "seeAlso": {}, + "service": {}, + "summary": {}, + "thumbnail": {}, + "type": "Canvas", + "width": {}, + }, + { + "@explicit": true, + "accompanyingCanvas": {}, + "annotations": {}, + "behavior": {}, + "height": {}, + "homepage": {}, + "id": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x00000a", + "iiif-parser:isExternal": {}, + "iiif-parser:partOf": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x00002e", + "items": [], + "metadata": {}, + "navDate": {}, + "partOf": {}, + "placeholderCanvas": {}, + "provider": {}, + "rendering": {}, + "requiredStatement": {}, + "rights": {}, + "seeAlso": {}, + "service": {}, + "summary": {}, + "thumbnail": {}, + "type": "Canvas", + "width": {}, + }, + ], + "iiif-parser:isExternal": true, "items": [ { "id": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x00000a/anno5", @@ -1008,6 +1600,14 @@ exports[`Smoke tests > Smoke test: ./fixtures/presentation-3/bl-ranges.json 1`] "height": 0, "homepage": [], "id": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x00000b", + "iiif-parser:hasPart": [ + { + "id": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x00000b", + "iiif-parser:partOf": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x00002e", + "type": "Canvas", + }, + ], + "iiif-parser:isExternal": true, "items": [ { "id": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x00000b/anno6", @@ -2150,821 +2750,39 @@ exports[`Smoke tests > Smoke test: ./fixtures/presentation-3/bl-ranges.json 1`] }, { "label": { - "en": [ - "Shelfmark", - ], - }, - "value": { - "en": [ - "C733/48", - ], - }, - }, - { - "label": { - "en": [ - "Link to catalogue", - ], - }, - "value": { - "en": [ - "View the catalogue record", - ], - }, - }, - ], - "navDate": null, - "partOf": [], - "placeholderCanvas": null, - "provider": [], - "rendering": [ - { - "id": "https://api.bl.uk/item/download/ark:/81055/vdc_100052320369.0x000002/ark:/81055/vdc_100052320369.0x000002/top", - "type": "ContentResource", - }, - ], - "requiredStatement": null, - "rights": null, - "seeAlso": [], - "service": [], - "start": null, - "summary": null, - "supplementary": null, - "thumbnail": [], - "type": "Range", - "viewingDirection": "left-to-right", - }, - "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000002/top/nn9": { - "accompanyingCanvas": null, - "annotations": [], - "behavior": [ - "no-nav", - ], - "homepage": [], - "id": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000002/top/nn9", - "iiif-parser:hasPart": [ - { - "id": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000002/top/nn9", - "iiif-parser:partOf": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000002/top", - "type": "Range", - }, - ], - "items": [ - { - "selector": { - "type": "FragmentSelector", - "value": "t=1346.4,1406.32", - }, - "source": { - "id": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x00000a", - "type": "Canvas", - }, - "type": "SpecificResource", - }, - ], - "label": null, - "metadata": [], - "navDate": null, - "partOf": [], - "placeholderCanvas": null, - "provider": [], - "rendering": [], - "requiredStatement": null, - "rights": null, - "seeAlso": [], - "service": [], - "start": null, - "summary": null, - "supplementary": null, - "thumbnail": [], - "type": "Range", - "viewingDirection": "left-to-right", - }, - "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000004#t=0,2023.56": { - "accompanyingCanvas": null, - "annotations": [], - "behavior": [], - "homepage": [], - "id": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000004#t=0,2023.56", - "iiif-parser:hasPart": [ - { - "id": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000004#t=0,2023.56", - "iiif-parser:partOf": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x00000d", - "type": "Canvas", - }, - ], - "items": [], - "label": null, - "metadata": [], - "navDate": null, - "partOf": [], - "placeholderCanvas": null, - "provider": [], - "rendering": [], - "requiredStatement": null, - "rights": null, - "seeAlso": [], - "service": [], - "start": null, - "summary": null, - "supplementary": null, - "thumbnail": [], - "type": "Canvas", - "viewingDirection": "left-to-right", - }, - "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000005#t=0,1760.04": { - "accompanyingCanvas": null, - "annotations": [], - "behavior": [], - "homepage": [], - "id": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000005#t=0,1760.04", - "iiif-parser:hasPart": [ - { - "id": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000005#t=0,1760.04", - "iiif-parser:partOf": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x00000e", - "type": "Canvas", - }, - ], - "items": [], - "label": null, - "metadata": [], - "navDate": null, - "partOf": [], - "placeholderCanvas": null, - "provider": [], - "rendering": [], - "requiredStatement": null, - "rights": null, - "seeAlso": [], - "service": [], - "start": null, - "summary": null, - "supplementary": null, - "thumbnail": [], - "type": "Canvas", - "viewingDirection": "left-to-right", - }, - "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000007#t=0,0.36": { - "accompanyingCanvas": null, - "annotations": [], - "behavior": [], - "homepage": [], - "id": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000007#t=0,0.36", - "iiif-parser:hasPart": [ - { - "id": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000007#t=0,0.36", - "iiif-parser:partOf": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x00000c/nn3", - "type": "Canvas", - }, - ], - "items": [], - "label": null, - "metadata": [], - "navDate": null, - "partOf": [], - "placeholderCanvas": null, - "provider": [], - "rendering": [], - "requiredStatement": null, - "rights": null, - "seeAlso": [], - "service": [], - "start": null, - "summary": null, - "supplementary": null, - "thumbnail": [], - "type": "Canvas", - "viewingDirection": "left-to-right", - }, - "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000007#t=0.36,484.8": { - "accompanyingCanvas": null, - "annotations": [], - "behavior": [], - "homepage": [], - "id": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000007#t=0.36,484.8", - "iiif-parser:hasPart": [ - { - "id": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000007#t=0.36,484.8", - "iiif-parser:partOf": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x00000f", - "type": "Canvas", - }, - ], - "items": [], - "label": null, - "metadata": [], - "navDate": null, - "partOf": [], - "placeholderCanvas": null, - "provider": [], - "rendering": [], - "requiredStatement": null, - "rights": null, - "seeAlso": [], - "service": [], - "start": null, - "summary": null, - "supplementary": null, - "thumbnail": [], - "type": "Canvas", - "viewingDirection": "left-to-right", - }, - "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000007#t=1027,1392.56": { - "accompanyingCanvas": null, - "annotations": [], - "behavior": [], - "homepage": [], - "id": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000007#t=1027,1392.56", - "iiif-parser:hasPart": [ - { - "id": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000007#t=1027,1392.56", - "iiif-parser:partOf": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x00001b", - "type": "Canvas", - }, - ], - "items": [], - "label": null, - "metadata": [], - "navDate": null, - "partOf": [], - "placeholderCanvas": null, - "provider": [], - "rendering": [], - "requiredStatement": null, - "rights": null, - "seeAlso": [], - "service": [], - "start": null, - "summary": null, - "supplementary": null, - "thumbnail": [], - "type": "Canvas", - "viewingDirection": "left-to-right", - }, - "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000007#t=487.56,526.36": { - "accompanyingCanvas": null, - "annotations": [], - "behavior": [], - "homepage": [], - "id": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000007#t=487.56,526.36", - "iiif-parser:hasPart": [ - { - "id": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000007#t=487.56,526.36", - "iiif-parser:partOf": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000016", - "type": "Canvas", - }, - ], - "items": [], - "label": null, - "metadata": [], - "navDate": null, - "partOf": [], - "placeholderCanvas": null, - "provider": [], - "rendering": [], - "requiredStatement": null, - "rights": null, - "seeAlso": [], - "service": [], - "start": null, - "summary": null, - "supplementary": null, - "thumbnail": [], - "type": "Canvas", - "viewingDirection": "left-to-right", - }, - "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000007#t=526.36,526.64": { - "accompanyingCanvas": null, - "annotations": [], - "behavior": [], - "homepage": [], - "id": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000007#t=526.36,526.64", - "iiif-parser:hasPart": [ - { - "id": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000007#t=526.36,526.64", - "iiif-parser:partOf": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000016/nn3", - "type": "Canvas", - }, - ], - "items": [], - "label": null, - "metadata": [], - "navDate": null, - "partOf": [], - "placeholderCanvas": null, - "provider": [], - "rendering": [], - "requiredStatement": null, - "rights": null, - "seeAlso": [], - "service": [], - "start": null, - "summary": null, - "supplementary": null, - "thumbnail": [], - "type": "Canvas", - "viewingDirection": "left-to-right", - }, - "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000007#t=526.64,1026.48": { - "accompanyingCanvas": null, - "annotations": [], - "behavior": [], - "homepage": [], - "id": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000007#t=526.64,1026.48", - "iiif-parser:hasPart": [ - { - "id": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000007#t=526.64,1026.48", - "iiif-parser:partOf": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000018", - "type": "Canvas", - }, - ], - "items": [], - "label": null, - "metadata": [], - "navDate": null, - "partOf": [], - "placeholderCanvas": null, - "provider": [], - "rendering": [], - "requiredStatement": null, - "rights": null, - "seeAlso": [], - "service": [], - "start": null, - "summary": null, - "supplementary": null, - "thumbnail": [], - "type": "Canvas", - "viewingDirection": "left-to-right", - }, - "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000008#t=0,259.8": { - "accompanyingCanvas": null, - "annotations": [], - "behavior": [], - "homepage": [], - "id": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000008#t=0,259.8", - "iiif-parser:hasPart": [ - { - "id": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000008#t=0,259.8", - "iiif-parser:partOf": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x00001b", - "type": "Canvas", - }, - ], - "items": [], - "label": null, - "metadata": [], - "navDate": null, - "partOf": [], - "placeholderCanvas": null, - "provider": [], - "rendering": [], - "requiredStatement": null, - "rights": null, - "seeAlso": [], - "service": [], - "start": null, - "summary": null, - "supplementary": null, - "thumbnail": [], - "type": "Canvas", - "viewingDirection": "left-to-right", - }, - "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000008#t=259.8,260": { - "accompanyingCanvas": null, - "annotations": [], - "behavior": [], - "homepage": [], - "id": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000008#t=259.8,260", - "iiif-parser:hasPart": [ - { - "id": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000008#t=259.8,260", - "iiif-parser:partOf": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x00001b/nn2", - "type": "Canvas", - }, - ], - "items": [], - "label": null, - "metadata": [], - "navDate": null, - "partOf": [], - "placeholderCanvas": null, - "provider": [], - "rendering": [], - "requiredStatement": null, - "rights": null, - "seeAlso": [], - "service": [], - "start": null, - "summary": null, - "supplementary": null, - "thumbnail": [], - "type": "Canvas", - "viewingDirection": "left-to-right", - }, - "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000008#t=260,276.24": { - "accompanyingCanvas": null, - "annotations": [], - "behavior": [], - "homepage": [], - "id": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000008#t=260,276.24", - "iiif-parser:hasPart": [ - { - "id": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000008#t=260,276.24", - "iiif-parser:partOf": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x00001c", - "type": "Canvas", - }, - ], - "items": [], - "label": null, - "metadata": [], - "navDate": null, - "partOf": [], - "placeholderCanvas": null, - "provider": [], - "rendering": [], - "requiredStatement": null, - "rights": null, - "seeAlso": [], - "service": [], - "start": null, - "summary": null, - "supplementary": null, - "thumbnail": [], - "type": "Canvas", - "viewingDirection": "left-to-right", - }, - "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000008#t=276.24,277.44": { - "accompanyingCanvas": null, - "annotations": [], - "behavior": [], - "homepage": [], - "id": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000008#t=276.24,277.44", - "iiif-parser:hasPart": [ - { - "id": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000008#t=276.24,277.44", - "iiif-parser:partOf": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x00001c/nn3", - "type": "Canvas", - }, - ], - "items": [], - "label": null, - "metadata": [], - "navDate": null, - "partOf": [], - "placeholderCanvas": null, - "provider": [], - "rendering": [], - "requiredStatement": null, - "rights": null, - "seeAlso": [], - "service": [], - "start": null, - "summary": null, - "supplementary": null, - "thumbnail": [], - "type": "Canvas", - "viewingDirection": "left-to-right", - }, - "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000008#t=277.44,821.36": { - "accompanyingCanvas": null, - "annotations": [], - "behavior": [], - "homepage": [], - "id": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000008#t=277.44,821.36", - "iiif-parser:hasPart": [ - { - "id": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000008#t=277.44,821.36", - "iiif-parser:partOf": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x00001d", - "type": "Canvas", - }, - ], - "items": [], - "label": null, - "metadata": [], - "navDate": null, - "partOf": [], - "placeholderCanvas": null, - "provider": [], - "rendering": [], - "requiredStatement": null, - "rights": null, - "seeAlso": [], - "service": [], - "start": null, - "summary": null, - "supplementary": null, - "thumbnail": [], - "type": "Canvas", - "viewingDirection": "left-to-right", - }, - "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000008#t=821.76,992.32": { - "accompanyingCanvas": null, - "annotations": [], - "behavior": [], - "homepage": [], - "id": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000008#t=821.76,992.32", - "iiif-parser:hasPart": [ - { - "id": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000008#t=821.76,992.32", - "iiif-parser:partOf": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000024", - "type": "Canvas", - }, - ], - "items": [], - "label": null, - "metadata": [], - "navDate": null, - "partOf": [], - "placeholderCanvas": null, - "provider": [], - "rendering": [], - "requiredStatement": null, - "rights": null, - "seeAlso": [], - "service": [], - "start": null, - "summary": null, - "supplementary": null, - "thumbnail": [], - "type": "Canvas", - "viewingDirection": "left-to-right", - }, - "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000008#t=992.32,992.96": { - "accompanyingCanvas": null, - "annotations": [], - "behavior": [], - "homepage": [], - "id": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000008#t=992.32,992.96", - "iiif-parser:hasPart": [ - { - "id": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000008#t=992.32,992.96", - "iiif-parser:partOf": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000024/nn6", - "type": "Canvas", - }, - ], - "items": [], - "label": null, - "metadata": [], - "navDate": null, - "partOf": [], - "placeholderCanvas": null, - "provider": [], - "rendering": [], - "requiredStatement": null, - "rights": null, - "seeAlso": [], - "service": [], - "start": null, - "summary": null, - "supplementary": null, - "thumbnail": [], - "type": "Canvas", - "viewingDirection": "left-to-right", - }, - "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000008#t=992.96,1404.92": { - "accompanyingCanvas": null, - "annotations": [], - "behavior": [], - "homepage": [], - "id": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000008#t=992.96,1404.92", - "iiif-parser:hasPart": [ - { - "id": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000008#t=992.96,1404.92", - "iiif-parser:partOf": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000026", - "type": "Canvas", - }, - ], - "items": [], - "label": null, - "metadata": [], - "navDate": null, - "partOf": [], - "placeholderCanvas": null, - "provider": [], - "rendering": [], - "requiredStatement": null, - "rights": null, - "seeAlso": [], - "service": [], - "start": null, - "summary": null, - "supplementary": null, - "thumbnail": [], - "type": "Canvas", - "viewingDirection": "left-to-right", - }, - "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x00000a#t=0,736": { - "accompanyingCanvas": null, - "annotations": [], - "behavior": [], - "homepage": [], - "id": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x00000a#t=0,736", - "iiif-parser:hasPart": [ - { - "id": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x00000a#t=0,736", - "iiif-parser:partOf": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000028", - "type": "Canvas", - }, - ], - "items": [], - "label": null, - "metadata": [], - "navDate": null, - "partOf": [], - "placeholderCanvas": null, - "provider": [], - "rendering": [], - "requiredStatement": null, - "rights": null, - "seeAlso": [], - "service": [], - "start": null, - "summary": null, - "supplementary": null, - "thumbnail": [], - "type": "Canvas", - "viewingDirection": "left-to-right", - }, - "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x00000a#t=1158.84,1159.12": { - "accompanyingCanvas": null, - "annotations": [], - "behavior": [], - "homepage": [], - "id": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x00000a#t=1158.84,1159.12", - "iiif-parser:hasPart": [ - { - "id": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x00000a#t=1158.84,1159.12", - "iiif-parser:partOf": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x00002a/nn9", - "type": "Canvas", - }, - ], - "items": [], - "label": null, - "metadata": [], - "navDate": null, - "partOf": [], - "placeholderCanvas": null, - "provider": [], - "rendering": [], - "requiredStatement": null, - "rights": null, - "seeAlso": [], - "service": [], - "start": null, - "summary": null, - "supplementary": null, - "thumbnail": [], - "type": "Canvas", - "viewingDirection": "left-to-right", - }, - "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x00000a#t=1159.12,1346.4": { - "accompanyingCanvas": null, - "annotations": [], - "behavior": [], - "homepage": [], - "id": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x00000a#t=1159.12,1346.4", - "iiif-parser:hasPart": [ - { - "id": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x00000a#t=1159.12,1346.4", - "iiif-parser:partOf": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x00002c", - "type": "Canvas", - }, - ], - "items": [], - "label": null, - "metadata": [], - "navDate": null, - "partOf": [], - "placeholderCanvas": null, - "provider": [], - "rendering": [], - "requiredStatement": null, - "rights": null, - "seeAlso": [], - "service": [], - "start": null, - "summary": null, - "supplementary": null, - "thumbnail": [], - "type": "Canvas", - "viewingDirection": "left-to-right", - }, - "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x00000a#t=1346.4,1406.32": { - "accompanyingCanvas": null, - "annotations": [], - "behavior": [], - "homepage": [], - "id": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x00000a#t=1346.4,1406.32", - "iiif-parser:hasPart": [ - { - "id": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x00000a#t=1346.4,1406.32", - "iiif-parser:partOf": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000002/top/nn9", - "type": "Canvas", - }, - ], - "items": [], - "label": null, - "metadata": [], - "navDate": null, - "partOf": [], - "placeholderCanvas": null, - "provider": [], - "rendering": [], - "requiredStatement": null, - "rights": null, - "seeAlso": [], - "service": [], - "start": null, - "summary": null, - "supplementary": null, - "thumbnail": [], - "type": "Canvas", - "viewingDirection": "left-to-right", - }, - "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x00000a#t=1346.52,1406.32": { - "accompanyingCanvas": null, - "annotations": [], - "behavior": [], - "homepage": [], - "id": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x00000a#t=1346.52,1406.32", - "iiif-parser:hasPart": [ - { - "id": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x00000a#t=1346.52,1406.32", - "iiif-parser:partOf": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x00002e", - "type": "Canvas", - }, - ], - "items": [], - "label": null, - "metadata": [], - "navDate": null, - "partOf": [], - "placeholderCanvas": null, - "provider": [], - "rendering": [], - "requiredStatement": null, - "rights": null, - "seeAlso": [], - "service": [], - "start": null, - "summary": null, - "supplementary": null, - "thumbnail": [], - "type": "Canvas", - "viewingDirection": "left-to-right", - }, - "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x00000a#t=736,736.72": { - "accompanyingCanvas": null, - "annotations": [], - "behavior": [], - "homepage": [], - "id": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x00000a#t=736,736.72", - "iiif-parser:hasPart": [ - { - "id": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x00000a#t=736,736.72", - "iiif-parser:partOf": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000028/nn8", - "type": "Canvas", - }, - ], - "items": [], - "label": null, - "metadata": [], - "navDate": null, - "partOf": [], - "placeholderCanvas": null, - "provider": [], - "rendering": [], - "requiredStatement": null, - "rights": null, - "seeAlso": [], - "service": [], - "start": null, - "summary": null, - "supplementary": null, - "thumbnail": [], - "type": "Canvas", - "viewingDirection": "left-to-right", - }, - "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x00000a#t=736.72,1158.84": { - "accompanyingCanvas": null, - "annotations": [], - "behavior": [], - "homepage": [], - "id": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x00000a#t=736.72,1158.84", - "iiif-parser:hasPart": [ + "en": [ + "Shelfmark", + ], + }, + "value": { + "en": [ + "C733/48", + ], + }, + }, { - "id": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x00000a#t=736.72,1158.84", - "iiif-parser:partOf": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x00002a", - "type": "Canvas", + "label": { + "en": [ + "Link to catalogue", + ], + }, + "value": { + "en": [ + "View the catalogue record", + ], + }, }, ], - "items": [], - "label": null, - "metadata": [], "navDate": null, "partOf": [], "placeholderCanvas": null, "provider": [], - "rendering": [], + "rendering": [ + { + "id": "https://api.bl.uk/item/download/ark:/81055/vdc_100052320369.0x000002/ark:/81055/vdc_100052320369.0x000002/top", + "type": "ContentResource", + }, + ], "requiredStatement": null, "rights": null, "seeAlso": [], @@ -2973,23 +2791,37 @@ exports[`Smoke tests > Smoke test: ./fixtures/presentation-3/bl-ranges.json 1`] "summary": null, "supplementary": null, "thumbnail": [], - "type": "Canvas", + "type": "Range", "viewingDirection": "left-to-right", }, - "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x00000b#t=0,1398.84": { + "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000002/top/nn9": { "accompanyingCanvas": null, "annotations": [], - "behavior": [], + "behavior": [ + "no-nav", + ], "homepage": [], - "id": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x00000b#t=0,1398.84", + "id": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000002/top/nn9", "iiif-parser:hasPart": [ { - "id": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x00000b#t=0,1398.84", - "iiif-parser:partOf": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x00002e", - "type": "Canvas", + "id": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000002/top/nn9", + "iiif-parser:partOf": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000002/top", + "type": "Range", + }, + ], + "items": [ + { + "selector": { + "type": "FragmentSelector", + "value": "t=1346.4,1406.32", + }, + "source": { + "id": "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x00000a", + "type": "Canvas", + }, + "type": "SpecificResource", }, ], - "items": [], "label": null, "metadata": [], "navDate": null, @@ -3005,7 +2837,7 @@ exports[`Smoke tests > Smoke test: ./fixtures/presentation-3/bl-ranges.json 1`] "summary": null, "supplementary": null, "thumbnail": [], - "type": "Canvas", + "type": "Range", "viewingDirection": "left-to-right", }, "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x00000c": { @@ -7025,45 +6857,21 @@ exports[`Smoke tests > Smoke test: ./fixtures/presentation-3/bl-ranges.json 1`] "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000002/top": "Range", "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000002/top/nn9": "Range", "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000004": "Canvas", - "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000004#t=0,2023.56": "Canvas", "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000004/anno1": "AnnotationPage", "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000004/anno1/1": "Annotation", "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000005": "Canvas", - "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000005#t=0,1760.04": "Canvas", "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000005/anno2": "AnnotationPage", "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000005/anno2/1": "Annotation", "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000007": "Canvas", - "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000007#t=0,0.36": "Canvas", - "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000007#t=0.36,484.8": "Canvas", - "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000007#t=1027,1392.56": "Canvas", - "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000007#t=487.56,526.36": "Canvas", - "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000007#t=526.36,526.64": "Canvas", - "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000007#t=526.64,1026.48": "Canvas", "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000007/anno3": "AnnotationPage", "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000007/anno3/1": "Annotation", "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000008": "Canvas", - "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000008#t=0,259.8": "Canvas", - "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000008#t=259.8,260": "Canvas", - "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000008#t=260,276.24": "Canvas", - "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000008#t=276.24,277.44": "Canvas", - "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000008#t=277.44,821.36": "Canvas", - "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000008#t=821.76,992.32": "Canvas", - "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000008#t=992.32,992.96": "Canvas", - "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000008#t=992.96,1404.92": "Canvas", "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000008/anno4": "AnnotationPage", "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x000008/anno4/1": "Annotation", "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x00000a": "Canvas", - "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x00000a#t=0,736": "Canvas", - "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x00000a#t=1158.84,1159.12": "Canvas", - "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x00000a#t=1159.12,1346.4": "Canvas", - "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x00000a#t=1346.4,1406.32": "Canvas", - "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x00000a#t=1346.52,1406.32": "Canvas", - "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x00000a#t=736,736.72": "Canvas", - "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x00000a#t=736.72,1158.84": "Canvas", "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x00000a/anno5": "AnnotationPage", "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x00000a/anno5/1": "Annotation", "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x00000b": "Canvas", - "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x00000b#t=0,1398.84": "Canvas", "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x00000b/anno6": "AnnotationPage", "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x00000b/anno6/1": "Annotation", "https://api.bl.uk/metadata/iiif/ark:/81055/vdc_100052320369.0x00000c": "Range", @@ -11508,6 +11316,13 @@ exports[`Smoke tests > Smoke test: ./fixtures/presentation-3/wellcome-p3.json 1` "height": 4412, "homepage": [], "id": "https://iiif-test.wellcomecollection.org/presentation/b28857021/canvases/b28857021_0001.jp2", + "iiif-parser:hasPart": [ + { + "id": "https://iiif-test.wellcomecollection.org/presentation/b28857021/canvases/b28857021_0001.jp2", + "iiif-parser:partOf": "https://iiif-test.wellcomecollection.org/presentation/b28857021/ranges/LOG_0001", + "type": "Canvas", + }, + ], "items": [ { "id": "https://iiif-test.wellcomecollection.org/presentation/b28857021/canvases/b28857021_0001.jp2/painting", @@ -11949,6 +11764,13 @@ exports[`Smoke tests > Smoke test: ./fixtures/presentation-3/wellcome-p3.json 1` "height": 4424, "homepage": [], "id": "https://iiif-test.wellcomecollection.org/presentation/b28857021/canvases/b28857021_0010.jp2", + "iiif-parser:hasPart": [ + { + "id": "https://iiif-test.wellcomecollection.org/presentation/b28857021/canvases/b28857021_0010.jp2", + "iiif-parser:partOf": "https://iiif-test.wellcomecollection.org/presentation/b28857021/ranges/LOG_0002", + "type": "Canvas", + }, + ], "items": [ { "id": "https://iiif-test.wellcomecollection.org/presentation/b28857021/canvases/b28857021_0010.jp2/painting", @@ -13605,70 +13427,6 @@ exports[`Smoke tests > Smoke test: ./fixtures/presentation-3/wellcome-p3.json 1` }, }, "Range": { - "https://iiif-test.wellcomecollection.org/presentation/b28857021/canvases/b28857021_0001.jp2": { - "accompanyingCanvas": null, - "annotations": [], - "behavior": [], - "homepage": [], - "id": "https://iiif-test.wellcomecollection.org/presentation/b28857021/canvases/b28857021_0001.jp2", - "iiif-parser:hasPart": [ - { - "id": "https://iiif-test.wellcomecollection.org/presentation/b28857021/canvases/b28857021_0001.jp2", - "iiif-parser:partOf": "https://iiif-test.wellcomecollection.org/presentation/b28857021/ranges/LOG_0001", - "type": "Canvas", - }, - ], - "items": [], - "label": null, - "metadata": [], - "navDate": null, - "partOf": [], - "placeholderCanvas": null, - "provider": [], - "rendering": [], - "requiredStatement": null, - "rights": null, - "seeAlso": [], - "service": [], - "start": null, - "summary": null, - "supplementary": null, - "thumbnail": [], - "type": "Canvas", - "viewingDirection": "left-to-right", - }, - "https://iiif-test.wellcomecollection.org/presentation/b28857021/canvases/b28857021_0010.jp2": { - "accompanyingCanvas": null, - "annotations": [], - "behavior": [], - "homepage": [], - "id": "https://iiif-test.wellcomecollection.org/presentation/b28857021/canvases/b28857021_0010.jp2", - "iiif-parser:hasPart": [ - { - "id": "https://iiif-test.wellcomecollection.org/presentation/b28857021/canvases/b28857021_0010.jp2", - "iiif-parser:partOf": "https://iiif-test.wellcomecollection.org/presentation/b28857021/ranges/LOG_0002", - "type": "Canvas", - }, - ], - "items": [], - "label": null, - "metadata": [], - "navDate": null, - "partOf": [], - "placeholderCanvas": null, - "provider": [], - "rendering": [], - "requiredStatement": null, - "rights": null, - "seeAlso": [], - "service": [], - "start": null, - "summary": null, - "supplementary": null, - "thumbnail": [], - "type": "Canvas", - "viewingDirection": "left-to-right", - }, "https://iiif-test.wellcomecollection.org/presentation/b28857021/ranges/LOG_0001": { "accompanyingCanvas": null, "annotations": [], @@ -17120,6 +16878,13 @@ exports[`Smoke tests > Smoke test: ./fixtures/presentation-3/wellcome-p3-2.json "height": 3543, "homepage": [], "id": "https://iiif.wellcomecollection.org/presentation/b18035723/canvases/b18035723_0001.JP2", + "iiif-parser:hasPart": [ + { + "id": "https://iiif.wellcomecollection.org/presentation/b18035723/canvases/b18035723_0001.JP2", + "iiif-parser:partOf": "https://iiif.wellcomecollection.org/presentation/b18035723/ranges/LOG_0001", + "type": "Canvas", + }, + ], "items": [ { "id": "https://iiif.wellcomecollection.org/presentation/b18035723/canvases/b18035723_0001.JP2/painting", @@ -17169,6 +16934,13 @@ exports[`Smoke tests > Smoke test: ./fixtures/presentation-3/wellcome-p3-2.json "height": 3040, "homepage": [], "id": "https://iiif.wellcomecollection.org/presentation/b18035723/canvases/b18035723_0002.JP2", + "iiif-parser:hasPart": [ + { + "id": "https://iiif.wellcomecollection.org/presentation/b18035723/canvases/b18035723_0002.JP2", + "iiif-parser:partOf": "https://iiif.wellcomecollection.org/presentation/b18035723/ranges/LOG_0002", + "type": "Canvas", + }, + ], "items": [ { "id": "https://iiif.wellcomecollection.org/presentation/b18035723/canvases/b18035723_0002.JP2/painting", @@ -17267,6 +17039,13 @@ exports[`Smoke tests > Smoke test: ./fixtures/presentation-3/wellcome-p3-2.json "height": 3372, "homepage": [], "id": "https://iiif.wellcomecollection.org/presentation/b18035723/canvases/b18035723_0004.JP2", + "iiif-parser:hasPart": [ + { + "id": "https://iiif.wellcomecollection.org/presentation/b18035723/canvases/b18035723_0004.JP2", + "iiif-parser:partOf": "https://iiif.wellcomecollection.org/presentation/b18035723/ranges/LOG_0003", + "type": "Canvas", + }, + ], "items": [ { "id": "https://iiif.wellcomecollection.org/presentation/b18035723/canvases/b18035723_0004.JP2/painting", @@ -22439,102 +22218,6 @@ exports[`Smoke tests > Smoke test: ./fixtures/presentation-3/wellcome-p3-2.json }, }, "Range": { - "https://iiif.wellcomecollection.org/presentation/b18035723/canvases/b18035723_0001.JP2": { - "accompanyingCanvas": null, - "annotations": [], - "behavior": [], - "homepage": [], - "id": "https://iiif.wellcomecollection.org/presentation/b18035723/canvases/b18035723_0001.JP2", - "iiif-parser:hasPart": [ - { - "id": "https://iiif.wellcomecollection.org/presentation/b18035723/canvases/b18035723_0001.JP2", - "iiif-parser:partOf": "https://iiif.wellcomecollection.org/presentation/b18035723/ranges/LOG_0001", - "type": "Canvas", - }, - ], - "items": [], - "label": null, - "metadata": [], - "navDate": null, - "partOf": [], - "placeholderCanvas": null, - "provider": [], - "rendering": [], - "requiredStatement": null, - "rights": null, - "seeAlso": [], - "service": [], - "start": null, - "summary": null, - "supplementary": null, - "thumbnail": [], - "type": "Canvas", - "viewingDirection": "left-to-right", - }, - "https://iiif.wellcomecollection.org/presentation/b18035723/canvases/b18035723_0002.JP2": { - "accompanyingCanvas": null, - "annotations": [], - "behavior": [], - "homepage": [], - "id": "https://iiif.wellcomecollection.org/presentation/b18035723/canvases/b18035723_0002.JP2", - "iiif-parser:hasPart": [ - { - "id": "https://iiif.wellcomecollection.org/presentation/b18035723/canvases/b18035723_0002.JP2", - "iiif-parser:partOf": "https://iiif.wellcomecollection.org/presentation/b18035723/ranges/LOG_0002", - "type": "Canvas", - }, - ], - "items": [], - "label": null, - "metadata": [], - "navDate": null, - "partOf": [], - "placeholderCanvas": null, - "provider": [], - "rendering": [], - "requiredStatement": null, - "rights": null, - "seeAlso": [], - "service": [], - "start": null, - "summary": null, - "supplementary": null, - "thumbnail": [], - "type": "Canvas", - "viewingDirection": "left-to-right", - }, - "https://iiif.wellcomecollection.org/presentation/b18035723/canvases/b18035723_0004.JP2": { - "accompanyingCanvas": null, - "annotations": [], - "behavior": [], - "homepage": [], - "id": "https://iiif.wellcomecollection.org/presentation/b18035723/canvases/b18035723_0004.JP2", - "iiif-parser:hasPart": [ - { - "id": "https://iiif.wellcomecollection.org/presentation/b18035723/canvases/b18035723_0004.JP2", - "iiif-parser:partOf": "https://iiif.wellcomecollection.org/presentation/b18035723/ranges/LOG_0003", - "type": "Canvas", - }, - ], - "items": [], - "label": null, - "metadata": [], - "navDate": null, - "partOf": [], - "placeholderCanvas": null, - "provider": [], - "rendering": [], - "requiredStatement": null, - "rights": null, - "seeAlso": [], - "service": [], - "start": null, - "summary": null, - "supplementary": null, - "thumbnail": [], - "type": "Canvas", - "viewingDirection": "left-to-right", - }, "https://iiif.wellcomecollection.org/presentation/b18035723/ranges/LOG_0001": { "accompanyingCanvas": null, "annotations": [], diff --git a/__tests__/presentation-3-parser/annotation-collections.test.ts b/__tests__/presentation-3-parser/annotation-collections.test.ts new file mode 100644 index 0000000..79e792c --- /dev/null +++ b/__tests__/presentation-3-parser/annotation-collections.test.ts @@ -0,0 +1,46 @@ +import { normalize, serialize, serializeConfigPresentation3 } from "../../src"; + +function roundTrip(input: any): any { + const result = normalize(structuredClone(input)); + return serialize({ ...result, requests: {} }, result.resource, serializeConfigPresentation3); +} + +describe("AnnotationCollection coverage (#29)", () => { + test("preserves descriptive/linking fields, counts, and first/last pages", () => { + const input = { + "@context": "http://iiif.io/api/presentation/3/context.json", + id: "https://example.org/comments", + type: "AnnotationCollection", + label: { en: ["Comments"] }, + summary: { en: ["Transcriptions"] }, + total: 2, + homepage: [{ id: "https://example.org/comments.html", type: "Text", format: "text/html" }], + first: { id: "https://example.org/comments/page1", type: "AnnotationPage", items: [] }, + last: "https://example.org/comments/page2", + }; + expect(roundTrip(input)).toEqual(input); + }); + + test("serializes a first page with a reference back to its collection", () => { + const id = "https://example.org/comments"; + const input = { + id, + type: "AnnotationCollection", + total: 0, + first: { id: `${id}/page`, type: "AnnotationPage", partOf: [{ id, type: "AnnotationCollection" }], items: [] }, + }; + expect(roundTrip(input)).toEqual(input); + }); + test("keeps linked collections inline in the existing page store model", () => { + const collection = { + id: "https://example.org/comments", + type: "AnnotationCollection", + label: { en: ["Comments"] }, + total: 0, + }; + const input = { id: "https://example.org/page", type: "AnnotationPage", items: [], partOf: [collection] }; + const result = normalize(structuredClone(input)); + expect((result.entities.AnnotationPage as any)[input.id].partOf).toEqual([collection]); + expect(roundTrip(input).partOf).toEqual([collection]); + }); +}); diff --git a/__tests__/presentation-3-parser/compatibility-coverage.test.ts b/__tests__/presentation-3-parser/compatibility-coverage.test.ts new file mode 100644 index 0000000..be830a9 --- /dev/null +++ b/__tests__/presentation-3-parser/compatibility-coverage.test.ts @@ -0,0 +1,62 @@ +import { normalize, serialize, serializeConfigPresentation3 } from "../../src"; + +describe("Presentation 3 compatibility coverage (#29)", () => { + test("retains annotation audience, accessibility and non-array motivation", () => { + const id = "https://example.org/annotation"; + const input = { + id, + type: "Annotation", + motivation: "commenting", + target: "https://example.org/canvas", + audience: { id: "https://example.org/audience", type: "Audience" }, + accessibility: "alternativeText", + seeAlso: [{ id: "https://example.org/data", type: "Dataset", format: "application/json" }], + body: { type: "TextualBody", value: "A description" }, + }; + const result = normalize(structuredClone(input)); + const stored = (result.entities.Annotation as any)[id]; + expect(stored.audience).toEqual([input.audience]); + expect(stored.accessibility).toEqual(["alternativeText"]); + expect(stored.motivation).toEqual(["commenting"]); + stored.motivation = "commenting"; + const output = serialize({ ...result, requests: {} }, result.resource, serializeConfigPresentation3); + expect(output.motivation).toBe("commenting"); + expect(output.seeAlso).toEqual([{ id: "https://example.org/data", type: "Dataset", format: "application/json" }]); + expect(output.body.value).toBe("A description"); + }); + + test("serializes ImageService2 and records a single-object nested service", () => { + const input = { + id: "https://example.org/image.jpg", + type: "Image", + format: "image/jpeg", + service: [ + { + id: "https://example.org/image", + type: "ImageService2", + profile: "level1", + service: { + id: "https://example.org/auth", + type: "AuthCookieService1", + profile: "http://iiif.io/api/auth/1/login", + }, + }, + ], + }; + const result = normalize({ + id: "https://example.org/annotation", + type: "Annotation", + motivation: "painting", + target: "https://example.org/canvas", + body: input, + }); + expect((result.entities.Service as any)["https://example.org/auth"]).toBeDefined(); + const output = serialize({ ...result, requests: {} }, result.resource, serializeConfigPresentation3); + expect(output.body.service[0]).toMatchObject({ + "@id": "https://example.org/image", + "@type": "ImageService2", + profile: "http://iiif.io/api/image/2/level1.json", + }); + expect(output.body.service[0].service).toHaveLength(1); + }); +}); diff --git a/__tests__/presentation-3-parser/linked-fields.test.ts b/__tests__/presentation-3-parser/linked-fields.test.ts new file mode 100644 index 0000000..6dd5482 --- /dev/null +++ b/__tests__/presentation-3-parser/linked-fields.test.ts @@ -0,0 +1,121 @@ +import { normalize, serialize, serializeConfigPresentation3, Traverse } from "../../src"; +import homepage from "../../fixtures/cookbook/0047-homepage.json"; +import seeAlso from "../../fixtures/cookbook/0053-seeAlso.json"; +import provider from "../../fixtures/cookbook/0234-provider.json"; + +function roundTrip(input: any): any { + const result = normalize(structuredClone(input)); + return serialize({ ...result, requests: {} }, result.resource, serializeConfigPresentation3); +} + +describe("Presentation 3 linked fields (#28)", () => { + test.each([homepage, seeAlso, provider])("preserves published cookbook links for $id", (manifest) => { + const result = roundTrip(manifest); + for (const key of ["homepage", "seeAlso", "provider"] as const) { + if ((manifest as any)[key]) expect(result[key]).toEqual((manifest as any)[key]); + } + expect(JSON.stringify(result)).not.toContain("__$UNSET$__"); + }); + + test("resolves links on annotations, their bodies, and agents", () => { + const link = { id: "https://example.org/data", type: "Dataset", format: "application/json" }; + const agent = { + id: "https://example.org/agent", + type: "Agent", + label: { en: ["Provider"] }, + homepage: [{ id: "https://example.org", type: "Text", format: "text/html" }], + seeAlso: [link], + }; + const input = { + id: "https://example.org/annotation", + type: "Annotation", + motivation: "commenting", + label: { en: ["Comment"] }, + summary: { en: ["Summary"] }, + seeAlso: [link], + provider: [agent], + target: "https://example.org/canvas", + body: { id: "https://example.org/body", type: "TextualBody", value: "Hello", seeAlso: [link] }, + }; + expect(roundTrip(input)).toEqual(input); + }); + + test.each(["Manifest", "Collection"])("preserves homepage on a %s referenced through partOf", (type) => { + const manifest = { + id: "https://example.org/parent", + type, + label: { en: ["Parent"] }, + homepage: [{ id: "https://example.org/home", type: "Text", format: "text/html" }], + }; + const input = { id: "https://example.org/canvas", type: "Canvas", items: [], partOf: [manifest] }; + expect(roundTrip(input).partOf).toEqual([manifest]); + }); + test.each(["Manifest", "Collection"])("serializes %s items with a back-reference to their collection", (type) => { + const id = "https://example.org/collection"; + const input = { + id, + type: "Collection", + label: { en: ["Parent"] }, + items: [ + { + id: "https://example.org/child", + type, + label: { en: ["Child"] }, + partOf: [{ id, type: "Collection" }], + }, + ], + }; + expect(roundTrip(input).items[0].partOf).toBeUndefined(); + }); + + test("keeps the content resource as the service callback parent", () => { + const image = { + id: "https://example.org/image", + type: "Image", + service: [{ id: "https://example.org/service", type: "ImageService3", profile: "level1" }], + }; + const parents: unknown[] = []; + new Traverse({ + service: [ + (_, context) => { + parents.push(context.parent); + }, + ], + }).traverseContentResource(image as any); + expect(parents).toEqual([image]); + }); + + test("preserves the existing inline body-link store shape", () => { + const link = { id: "https://example.org/data", type: "Dataset", format: "application/json" }; + const result = normalize({ + id: "https://example.org/annotation", + type: "Annotation", + target: "https://example.org/canvas", + body: { id: "https://example.org/body", type: "TextualBody", value: "Hello", seeAlso: [link] }, + }); + expect((result.entities.ContentResource as any)["https://example.org/body"].seeAlso).toEqual([link]); + }); + + test("keeps self-referencing inline links finite", () => { + const id = "https://example.org/thumbnail"; + const input = { + "@context": "http://iiif.io/api/presentation/3/context.json", + id: "https://example.org/manifest", + type: "Manifest", + label: { en: ["Manifest"] }, + items: [], + thumbnail: [{ id, type: "Image", seeAlso: [{ id, type: "Image" }] }], + }; + expect(roundTrip(input)).toEqual(input); + }); + + test("keeps ignoring untyped links in manually populated stores", () => { + const result = normalize({ id: "https://example.org/manifest", type: "Manifest", items: [] }); + (result.entities.Manifest as any)[result.resource.id].homepage = [ + "https://example.org/link", + { id: "https://example.org/link" }, + ]; + const output = serialize({ ...result, requests: {} }, result.resource, serializeConfigPresentation3); + expect(output.homepage).toBeUndefined(); + }); +}); diff --git a/__tests__/presentation-3-parser/merge-coverage.test.ts b/__tests__/presentation-3-parser/merge-coverage.test.ts new file mode 100644 index 0000000..6e2ad7e --- /dev/null +++ b/__tests__/presentation-3-parser/merge-coverage.test.ts @@ -0,0 +1,59 @@ +import { merge, mergeEntities } from "../../src/presentation-3/normalize"; + +describe("normalization merge coverage (#29)", () => { + test("merges repeated entity links and appends new links without losing either", () => { + const existing = [{ id: "https://example.org/one", type: "Dataset", label: { en: ["One"] } }]; + const incoming = [ + { id: "https://example.org/one", type: "Dataset", format: "application/json" }, + { id: "https://example.org/two", type: "Dataset", format: "text/csv" }, + ]; + expect(merge(existing, incoming)).toEqual([{ ...existing[0], format: "application/json" }, incoming[1]]); + expect(existing).toHaveLength(1); + expect(existing[0]).not.toHaveProperty("format"); + }); + + test("handles null values in both directions and within link arrays", () => { + const resource = { label: { en: ["Label"] } }; + expect(merge(null, resource)).toEqual(resource); + expect(merge(resource, null)).toBe(resource); + expect(merge([], [null, undefined, "one"])).toEqual(["one"]); + const link = { id: "https://example.org/link", type: "Dataset" }; + expect(merge([null], [link])).toEqual([null, link]); + }); + + test("rejects incompatible containers and preserves unchanged values", () => { + expect(() => merge([], {})).toThrow("Cannot merge array with non-array"); + expect(() => merge({}, [])).toThrow("Cannot merge object with non-object"); + expect(merge({ en: ["One"] }, { en: ["One", "Two"] })).toEqual({ en: ["One", "Two"] }); + expect(merge(["One"], ["One"])).toEqual(["One"]); + expect(merge([], ["One", "One"])).toEqual(["One", "One"]); + expect(merge({ en: [] }, { en: ["One", "One"] })).toEqual({ en: ["One", "One"] }); + expect(merge([["One"]], [["Two"]])).toEqual([["One"], ["Two"]]); + }); + + test("keeps string entities and rejects missing or mismatched identifiers and types", () => { + expect(mergeEntities("https://example.org/canvas" as any, {})).toBe("https://example.org/canvas"); + const canvas = { id: "https://example.org/canvas", type: "Canvas" }; + expect(() => mergeEntities(canvas, { id: canvas.id })).toThrow("identical identifiers and type"); + expect(() => mergeEntities(canvas, { type: "Canvas" })).toThrow("identical identifiers and type"); + expect(mergeEntities(canvas, canvas)).toEqual(canvas); + }); + test("matches legacy service aliases without duplicating them", () => { + const id = "https://example.org/image-service"; + const existing = [{ id, "@id": id, "@type": "ImageService2", profile: "level1" }]; + const incoming = [{ "@id": id, "@type": "ImageService2", profile: "level1", width: 1000 }]; + expect(merge(existing, incoming)).toEqual([{ ...existing[0], width: 1000 }]); + }); + + test("deduplicates equal untyped objects when merging service sizes", () => { + const id = "https://example.org/image-service"; + const sizes = [ + { width: 100, height: 50 }, + { width: 200, height: 100 }, + ]; + const existing = [{ id, "@type": "ImageService2", sizes }]; + const incoming = [{ "@id": id, "@type": "ImageService2", sizes: structuredClone(sizes) }]; + expect(merge(existing, incoming)[0].sizes).toEqual(sizes); + expect(merge([{ value: { en: ["One"] } }], [{ value: { en: ["One"] } }])).toHaveLength(1); + }); +}); diff --git a/__tests__/presentation-3-parser/p2-annotations.test.ts b/__tests__/presentation-3-parser/p2-annotations.test.ts new file mode 100644 index 0000000..3f0bca5 --- /dev/null +++ b/__tests__/presentation-3-parser/p2-annotations.test.ts @@ -0,0 +1,81 @@ +import { normalize, serialize, serializeConfigPresentation2 } from "../../src"; +import annotationList from "../../fixtures/presentation-2/iiif-fixture-annotation-list.json"; +import manifest from "../../fixtures/presentation-2/iiif-fixture-manifest.json"; +import selectedImage from "../../fixtures/regressions/p2-image-selector-body.json"; + +function asPresentation2(input: any): any { + const result = normalize(structuredClone(input)); + return serialize({ ...result, requests: {} }, result.resource, serializeConfigPresentation2); +} + +describe("Presentation 2 Open Annotation serialization (#49)", () => { + test("preserves text, motivations and selectors in the official annotation list", () => { + const output = asPresentation2(annotationList); + expect(output["@type"]).toBe("sc:AnnotationList"); + expect(Array.isArray(output.resources)).toBe(true); + expect(output.resources).toHaveLength(2); + expect(output.resources[0].resource).toMatchObject({ + "@type": "cnt:ContentAsText", + chars: "Top of First Page to Display", + }); + expect(output.resources[0].on).toBe(`${(annotationList as any).resources[0].on.full["@id"]}#xywh=225,70,750,150`); + expect(output.resources[1].motivation).toEqual(["oa:tagging", "oa:commenting"]); + expect(output.resources[1].resource.map((body: any) => body.chars)).toEqual( + (annotationList as any).resources[1].resource.map((body: any) => body.chars) + ); + expect(output.resources[1].on).toMatchObject({ + "@type": "oa:SpecificResource", + selector: { "@type": "oa:Choice" }, + }); + expect(JSON.stringify(output)).not.toContain("vault://"); + }); + + test("serializes painting pages as flat images arrays and external lists as otherContent", () => { + const input = structuredClone(manifest) as any; + input.sequences[0].canvases[0].otherContent = [ + { "@id": "https://example.org/comments", "@type": "sc:AnnotationList" }, + ]; + const output = asPresentation2(input); + const canvas = output.sequences[0].canvases[0]; + expect(Array.isArray(canvas.images)).toBe(true); + expect(Array.isArray(canvas.images[0])).toBe(false); + expect(canvas.images[0]["@type"]).toBe("oa:Annotation"); + expect(canvas.otherContent[0]["@id"]).toBe("https://example.org/comments"); + expect(canvas.annotations).toBeUndefined(); + }); + + test("converts selected image bodies and all image selector parameters", () => { + const output = asPresentation2(selectedImage); + expect(output.resource).toMatchObject({ + "@type": "oa:SpecificResource", + full: { "@id": selectedImage.resource.full["@id"], "@type": "dctypes:Image" }, + selector: selectedImage.resource.selector, + }); + }); + + test("serializes collection members and fragment-bearing range canvas references", () => { + const output = asPresentation2({ + id: "https://example.org/collection", + type: "Collection", + label: { en: ["Collection"] }, + items: [{ id: "https://example.org/manifest", type: "Manifest", label: { en: ["Manifest"] }, items: [] }], + }); + expect(output.members).toHaveLength(1); + expect(output.members[0]["@id"]).toBe("https://example.org/manifest"); + expect(output.members[0].sequences).toBeUndefined(); + expect(output.members[0].structures).toBeUndefined(); + const range = asPresentation2({ + id: "https://example.org/range", + type: "Range", + label: { en: ["Range"] }, + items: [ + { + type: "SpecificResource", + source: { id: "https://example.org/canvas", type: "Canvas" }, + selector: { type: "FragmentSelector", value: "t=10,20" }, + }, + ], + }); + expect(range.canvases).toEqual(["https://example.org/canvas#t=10,20"]); + }); +}); diff --git a/__tests__/presentation-3-parser/paginated-collections.test.ts b/__tests__/presentation-3-parser/paginated-collections.test.ts new file mode 100644 index 0000000..a66cf4b --- /dev/null +++ b/__tests__/presentation-3-parser/paginated-collections.test.ts @@ -0,0 +1,57 @@ +import collection from "../../fixtures/presentation-2/paginated-collection.json"; +import page from "../../fixtures/presentation-2/paginated-collection-page.json"; +import { normalize, serialize, serializeConfigPresentation3, serializeConfigPresentation2 } from "../../src"; + +describe("paged Presentation 2 collections (#42)", () => { + test.each([collection, page])("keeps navigation after normalization and serialization of $@id", (input) => { + const result = normalize(structuredClone(input)); + const output = serialize({ ...result, requests: {} }, result.resource, serializeConfigPresentation3); + for (const key of ["first", "prev", "next"] as const) { + if ((input as any)[key]) expect(output[key]).toEqual({ id: (input as any)[key], type: "Collection" }); + } + if ("total" in input) expect(output.total).toBe(input.total); + expect(output.items?.some((item: any) => item.id === output.next?.id)).not.toBe(true); + }); + + test("keeps zero totals and object pagination references", () => { + const result = normalize({ + "@id": "https://example.org/collection", + "@type": "sc:Collection", + label: "Empty", + total: 0, + next: { "@id": "https://example.org/next", "@type": "sc:Collection" }, + }); + const output = serialize({ ...result, requests: {} }, result.resource, serializeConfigPresentation3); + expect(output.total).toBe(0); + expect(output.next).toEqual({ id: "https://example.org/next", type: "Collection" }); + }); + test("keeps last, startIndex and embedded collection pagination in both serializers", () => { + const input = { + "@id": "https://example.org/parent", + "@type": "sc:Collection", + label: "Parent", + last: "https://example.org/last", + startIndex: 0, + total: 0, + collections: [ + { + "@id": "https://example.org/child", + "@type": "sc:Collection", + label: "Child", + first: "https://example.org/first", + total: 0, + }, + ], + }; + const result = normalize(input); + const store = { ...result, requests: {} }; + const p3 = serialize(store, result.resource, serializeConfigPresentation3); + expect(p3.last).toEqual({ id: input.last, type: "Collection" }); + expect(p3.startIndex).toBe(0); + expect(p3.items[0]).toMatchObject({ first: { id: "https://example.org/first", type: "Collection" }, total: 0 }); + const p2 = serialize(store, result.resource, serializeConfigPresentation2); + expect(p2.last).toBe(input.last); + expect(p2.startIndex).toBe(0); + expect(p2.total).toBe(0); + }); +}); diff --git a/__tests__/presentation-3-parser/range-selectors.test.ts b/__tests__/presentation-3-parser/range-selectors.test.ts new file mode 100644 index 0000000..75f5934 --- /dev/null +++ b/__tests__/presentation-3-parser/range-selectors.test.ts @@ -0,0 +1,43 @@ +import { normalize, serialize, serializeConfigPresentation3 } from "../../src"; +import rangeManifest from "../../fixtures/cookbook/0065-opera-multiple-canvases.json"; + +const canvasId = "https://example.org/canvas"; +const fragment = "xywh=0,1,2,3"; + +describe("range selectors (#3)", () => { + test.each([ + `${canvasId}#${fragment}`, + { id: `${canvasId}#${fragment}`, type: "Canvas" }, + { type: "SpecificResource", source: canvasId, selector: { type: "FragmentSelector", value: fragment } }, + { + type: "SpecificResource", + source: { id: canvasId, type: "Canvas" }, + selector: { type: "FragmentSelector", value: fragment }, + }, + ])("keeps a canonical canvas reference for %j", (item) => { + const result = normalize({ id: "https://example.org/range", type: "Range", items: [structuredClone(item)] }); + const range = (result.entities.Range as any)[result.resource.id]; + expect(range.items[0]).toEqual({ + type: "SpecificResource", + source: { id: canvasId, type: "Canvas" }, + selector: { type: "FragmentSelector", value: fragment }, + }); + expect((result.entities.Canvas as any)[canvasId]).toBeDefined(); + expect((result.mapping as any)[`${canvasId}#${fragment}`]).toBeUndefined(); + const output = serialize({ ...result, requests: {} }, result.resource, serializeConfigPresentation3); + expect(output.items).toEqual([{ id: `${canvasId}#${fragment}`, type: "Canvas" }]); + }); + + test("preserves temporal canvas selectors from the opera cookbook manifest", () => { + const result = normalize(structuredClone(rangeManifest)); + expect(Object.keys(result.entities.Canvas).every((id) => !id.includes("#t="))).toBe(true); + const output = serialize({ ...result, requests: {} }, result.resource, serializeConfigPresentation3); + expect(output.structures).toEqual(rangeManifest.structures); + }); + test("keeps unrelated anchor names intact", () => { + const id = `${canvasId}#part=2`; + const result = normalize({ id: "https://example.org/range", type: "Range", items: [id] }); + expect((result.entities.Canvas as any)[id]).toBeDefined(); + expect((result.entities.Range as any)[result.resource.id].items[0].selector).toBeUndefined(); + }); +}); diff --git a/__tests__/presentation-3-parser/target-hints.test.ts b/__tests__/presentation-3-parser/target-hints.test.ts new file mode 100644 index 0000000..817ad11 --- /dev/null +++ b/__tests__/presentation-3-parser/target-hints.test.ts @@ -0,0 +1,33 @@ +import { expandTargetToSpecificResource } from "../../src/shared/expand-target"; + +const id = "https://example.org/canvas"; + +describe("annotation target hints (#29)", () => { + test.each([[[id]], { type: "Choice", items: [id] }, { type: "List", items: [id] }])( + "keeps the configured source type when choosing the first target in %j", + (input) => { + expect(expandTargetToSpecificResource(input as any, { typeHint: "Canvas" }).source).toEqual({ + id, + type: "Canvas", + }); + } + ); + + test("uses the known source type for fragment targets", () => { + const result = expandTargetToSpecificResource(`${id}#t=10,20`, { typeMap: { [id]: "Canvas" } }); + expect(result.source).toEqual({ id, type: "Canvas" }); + expect(result.selector).toEqual({ type: "FragmentSelector", value: "t=10,20" }); + }); + + test("normalizes a canvas's manifest membership", () => { + expect( + expandTargetToSpecificResource({ + type: "SpecificResource", + source: { id, type: "Canvas", partOf: "https://example.org/manifest" }, + selector: { type: "FragmentSelector", value: "xywh=1,2,3,4" }, + } as any) + ).toMatchObject({ + source: { id, type: "Canvas", partOf: [{ id: "https://example.org/manifest", type: "Manifest" }] }, + }); + }); +}); diff --git a/fixtures/presentation-2/iiif-multiple-descriptions.json b/fixtures/presentation-2/iiif-multiple-descriptions.json new file mode 100644 index 0000000..3b85d93 --- /dev/null +++ b/fixtures/presentation-2/iiif-multiple-descriptions.json @@ -0,0 +1,42 @@ +{ + "@context": "http://iiif.io/api/presentation/2/context.json", + "@id": "http://iiif.io/api/presentation/2.1/example/fixtures/6/manifest.json", + "@type": "sc:Manifest", + "label": "Test 6 Manifest: Multiple Descriptions", + "description": [ + "This is one description", + { + "@value": "This is another", + "@language": "en" + } + ], + "within": "http://iiif.io/api/presentation/2.1/example/fixtures/collection.json", + "sequences": [ + { + "@type": "sc:Sequence", + "label": "Test 6 Sequence 1", + "canvases": [ + { + "@id": "http://iiif.io/api/presentation/2.1/example/fixtures/canvas/6/c1.json", + "@type": "sc:Canvas", + "label": "Test 6 Canvas: 1", + "height": 1800, + "width": 1200, + "images": [ + { + "@type": "oa:Annotation", + "motivation": "sc:painting", + "resource": { + "@id": "http://iiif.io/api/presentation/2.1/example/fixtures/resources/page1-full.png", + "@type": "dctypes:Image", + "height": 1800, + "width": 1200 + }, + "on": "http://iiif.io/api/presentation/2.1/example/fixtures/canvas/6/c1.json" + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/fixtures/presentation-2/iiif-string-seealso.json b/fixtures/presentation-2/iiif-string-seealso.json new file mode 100644 index 0000000..e8dca5c --- /dev/null +++ b/fixtures/presentation-2/iiif-string-seealso.json @@ -0,0 +1,36 @@ +{ + "@context": "http://iiif.io/api/presentation/2/context.json", + "@id": "http://iiif.io/api/presentation/2.1/example/fixtures/8/manifest.json", + "@type": "sc:Manifest", + "label": "Test 8 Manifest: SeeAlso link / Manifest", + "seeAlso": "http://www.example.org/link/to/metadata", + "within": "http://iiif.io/api/presentation/2.1/example/fixtures/collection.json", + "sequences": [ + { + "@type": "sc:Sequence", + "label": "Test 8 Sequence 1", + "canvases": [ + { + "@id": "http://iiif.io/api/presentation/2.1/example/fixtures/canvas/8/c1.json", + "@type": "sc:Canvas", + "label": "Test 8 Canvas: 1", + "height": 1800, + "width": 1200, + "images": [ + { + "@type": "oa:Annotation", + "motivation": "sc:painting", + "resource": { + "@id": "http://iiif.io/api/presentation/2.1/example/fixtures/resources/page1-full.png", + "@type": "dctypes:Image", + "height": 1800, + "width": 1200 + }, + "on": "http://iiif.io/api/presentation/2.1/example/fixtures/canvas/8/c1.json" + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/fixtures/presentation-2/leipzig-invalid-label-collection.json b/fixtures/presentation-2/leipzig-invalid-label-collection.json new file mode 100644 index 0000000..9eeb002 --- /dev/null +++ b/fixtures/presentation-2/leipzig-invalid-label-collection.json @@ -0,0 +1,406 @@ +{ + "@context": "http://iiif.io/api/presentation/2/context.json", + "@id": "https://iiif.ub.uni-leipzig.de/static/collections/misc/cdvost2018.json", + "@type": "sc:Collection", + "label": "Auswahl illuminierter mittelalterlicher Handschriften", + "description": "Auswahl illuminierter mittelalterlicher Handschriften der Universitaetsbibliothek Leipzig. Aufbereitet anlaesslich Coding da Vinci Ost 2018. Kontakt: https://www.ub.uni-leipzig.de", + "attribution": "Public Domain Mark 1.0", + "logo": "http://iiif.ub.uni-leipzig.de/ubl-logo.png", + "manifests": [ + { + "@id":"https://iiif.ub.uni-leipzig.de/0000002636/manifest.json", + "@type":"sc:Manifest", + "@label":"Ms 13" + }, + { + "@id":"https://iiif.ub.uni-leipzig.de/0000002974/manifest.json", + "@type":"sc:Manifest", + "@label":"Ms 14" + }, + { + "@id":"https://iiif.ub.uni-leipzig.de/0000004536/manifest.json", + "@type":"sc:Manifest", + "@label":"Ms 49" + }, + { + "@id":"https://iiif.ub.uni-leipzig.de/0000002216/manifest.json", + "@type":"sc:Manifest", + "@label":"Ms 51" + }, + { + "@id":"https://iiif.ub.uni-leipzig.de/0000002519/manifest.json", + "@type":"sc:Manifest", + "@label":"Ms 57" + }, + { + "@id":"https://iiif.ub.uni-leipzig.de/0000004578/manifest.json", + "@type":"sc:Manifest", + "@label":"Ms 58" + }, + { + "@id":"https://iiif.ub.uni-leipzig.de/0000004723/manifest.json", + "@type":"sc:Manifest", + "@label":"Ms 61" + }, + { + "@id":"https://iiif.ub.uni-leipzig.de/0000004684/manifest.json", + "@type":"sc:Manifest", + "@label":"Ms 63" + }, + { + "@id":"https://iiif.ub.uni-leipzig.de/0000005111/manifest.json", + "@type":"sc:Manifest", + "@label":"Ms 75" + }, + { + "@id":"https://iiif.ub.uni-leipzig.de/0000007413/manifest.json", + "@type":"sc:Manifest", + "@label":"Ms 85" + }, + { + "@id":"https://iiif.ub.uni-leipzig.de/0000007023/manifest.json", + "@type":"sc:Manifest", + "@label":"Ms 91" + }, + { + "@id":"https://iiif.ub.uni-leipzig.de/0000004763/manifest.json", + "@type":"sc:Manifest", + "@label":"Ms 93" + }, + { + "@id":"https://iiif.ub.uni-leipzig.de/0000007221/manifest.json", + "@type":"sc:Manifest", + "@label":"Ms 101" + }, + { + "@id":"https://iiif.ub.uni-leipzig.de/0000004596/manifest.json", + "@type":"sc:Manifest", + "@label":"Ms 106" + }, + { + "@id":"https://iiif.ub.uni-leipzig.de/0000001791/manifest.json", + "@type":"sc:Manifest", + "@label":"Ms 115" + }, + { + "@id":"https://iiif.ub.uni-leipzig.de/0000007416/manifest.json", + "@type":"sc:Manifest", + "@label":"Ms 148" + }, + { + "@id":"https://iiif.ub.uni-leipzig.de/0000006377/manifest.json", + "@type":"sc:Manifest", + "@label":"Ms 173" + }, + { + "@id":"https://iiif.ub.uni-leipzig.de/0000004595/manifest.json", + "@type":"sc:Manifest", + "@label":"Ms 187" + }, + { + "@id":"https://iiif.ub.uni-leipzig.de/0000007336/manifest.json", + "@type":"sc:Manifest", + "@label":"Ms 202" + }, + { + "@id":"https://iiif.ub.uni-leipzig.de/0000003064/manifest.json", + "@type":"sc:Manifest", + "@label":"Ms 219" + }, + { + "@id":"https://iiif.ub.uni-leipzig.de/0000006144/manifest.json", + "@type":"sc:Manifest", + "@label":"Ms 220" + }, + { + "@id":"https://iiif.ub.uni-leipzig.de/0000006231/manifest.json", + "@type":"sc:Manifest", + "@label":"Ms 232" + }, + { + "@id":"https://iiif.ub.uni-leipzig.de/0000006939/manifest.json", + "@type":"sc:Manifest", + "@label":"Ms 244" + }, + { + "@id":"https://iiif.ub.uni-leipzig.de/0000006946/manifest.json", + "@type":"sc:Manifest", + "@label":"Ms 247" + }, + { + "@id":"https://iiif.ub.uni-leipzig.de/0000006345/manifest.json", + "@type":"sc:Manifest", + "@label":"Ms 283" + }, + { + "@id":"https://iiif.ub.uni-leipzig.de/0000006769/manifest.json", + "@type":"sc:Manifest", + "@label":"Ms 290" + }, + { + "@id":"https://iiif.ub.uni-leipzig.de/0000006352/manifest.json", + "@type":"sc:Manifest", + "@label":"Ms 305" + }, + { + "@id":"https://iiif.ub.uni-leipzig.de/0000008291/manifest.json", + "@type":"sc:Manifest", + "@label":"Ms 316" + }, + { + "@id":"https://iiif.ub.uni-leipzig.de/0000004682/manifest.json", + "@type":"sc:Manifest", + "@label":"Ms 326" + }, + { + "@id":"https://iiif.ub.uni-leipzig.de/0000006672/manifest.json", + "@type":"sc:Manifest", + "@label":"Ms 332" + }, + { + "@id":"https://iiif.ub.uni-leipzig.de/0000004467/manifest.json", + "@type":"sc:Manifest", + "@label":"Ms 335" + }, + { + "@id":"https://iiif.ub.uni-leipzig.de/0000007343/manifest.json", + "@type":"sc:Manifest", + "@label":"Ms 372" + }, + { + "@id":"https://iiif.ub.uni-leipzig.de/0000006395/manifest.json", + "@type":"sc:Manifest", + "@label":"Ms 388" + }, + { + "@id":"https://iiif.ub.uni-leipzig.de/0000006384/manifest.json", + "@type":"sc:Manifest", + "@label":"Ms 389" + }, + { + "@id":"https://iiif.ub.uni-leipzig.de/0000002165/manifest.json", + "@type":"sc:Manifest", + "@label":"Ms 391" + }, + { + "@id":"https://iiif.ub.uni-leipzig.de/0000004718/manifest.json", + "@type":"sc:Manifest", + "@label":"Ms 397" + }, + { + "@id":"https://iiif.ub.uni-leipzig.de/0000002660/manifest.json", + "@type":"sc:Manifest", + "@label":"Ms 410" + }, + { + "@id":"https://iiif.ub.uni-leipzig.de/0000004604/manifest.json", + "@type":"sc:Manifest", + "@label":"Ms 412" + }, + { + "@id":"https://iiif.ub.uni-leipzig.de/0000005957/manifest.json", + "@type":"sc:Manifest", + "@label":"Ms 433" + }, + { + "@id":"https://iiif.ub.uni-leipzig.de/0000006217/manifest.json", + "@type":"sc:Manifest", + "@label":"Ms 667" + }, + { + "@id":"https://iiif.ub.uni-leipzig.de/0000004967/manifest.json", + "@type":"sc:Manifest", + "@label":"Ms 763" + }, + { + "@id":"https://iiif.ub.uni-leipzig.de/0000004986/manifest.json", + "@type":"sc:Manifest", + "@label":"Ms 777" + }, + { + "@id":"https://iiif.ub.uni-leipzig.de/0000004673/manifest.json", + "@type":"sc:Manifest", + "@label":"Ms 786" + }, + { + "@id":"https://iiif.ub.uni-leipzig.de/0000002544/manifest.json", + "@type":"sc:Manifest", + "@label":"Ms 788" + }, + { + "@id":"https://iiif.ub.uni-leipzig.de/0000004694/manifest.json", + "@type":"sc:Manifest", + "@label":"Ms 793" + }, + { + "@id":"https://iiif.ub.uni-leipzig.de/0000004984/manifest.json", + "@type":"sc:Manifest", + "@label":"Ms 809" + }, + { + "@id":"https://iiif.ub.uni-leipzig.de/0000004133/manifest.json", + "@type":"sc:Manifest", + "@label":"Ms 827" + }, + { + "@id":"https://iiif.ub.uni-leipzig.de/0000004762/manifest.json", + "@type":"sc:Manifest", + "@label":"Ms 828" + }, + { + "@id":"https://iiif.ub.uni-leipzig.de/0000005368/manifest.json", + "@type":"sc:Manifest", + "@label":"Ms 843" + }, + { + "@id":"https://iiif.ub.uni-leipzig.de/0000004410/manifest.json", + "@type":"sc:Manifest", + "@label":"Ms 847" + }, + { + "@id":"https://iiif.ub.uni-leipzig.de/0000004132/manifest.json", + "@type":"sc:Manifest", + "@label":"Ms 848" + }, + { + "@id":"https://iiif.ub.uni-leipzig.de/0000005375/manifest.json", + "@type":"sc:Manifest", + "@label":"Ms 849" + }, + { + "@id":"https://iiif.ub.uni-leipzig.de/0000003065/manifest.json", + "@type":"sc:Manifest", + "@label":"Ms 855" + }, + { + "@id":"https://iiif.ub.uni-leipzig.de/0000004409/manifest.json", + "@type":"sc:Manifest", + "@label":"Ms 859" + }, + { + "@id":"https://iiif.ub.uni-leipzig.de/0000004726/manifest.json", + "@type":"sc:Manifest", + "@label":"Ms 861" + }, + { + "@id":"https://iiif.ub.uni-leipzig.de/0000005128/manifest.json", + "@type":"sc:Manifest", + "@label":"Ms 1111" + }, + { + "@id":"https://iiif.ub.uni-leipzig.de/0000004482/manifest.json", + "@type":"sc:Manifest", + "@label":"Ms 1329" + }, + { + "@id":"https://iiif.ub.uni-leipzig.de/0000004476/manifest.json", + "@type":"sc:Manifest", + "@label":"Ms 1361" + }, + { + "@id":"https://iiif.ub.uni-leipzig.de/0000004124/manifest.json", + "@type":"sc:Manifest", + "@label":"Ms 1369" + }, + { + "@id":"https://iiif.ub.uni-leipzig.de/0000004395/manifest.json", + "@type":"sc:Manifest", + "@label":"Ms 1399" + }, + { + "@id":"https://iiif.ub.uni-leipzig.de/0000004084/manifest.json", + "@type":"sc:Manifest", + "@label":"Ms 1402" + }, + { + "@id":"https://iiif.ub.uni-leipzig.de/0000004077/manifest.json", + "@type":"sc:Manifest", + "@label":"Ms 1403" + }, + { + "@id":"https://iiif.ub.uni-leipzig.de/0000004114/manifest.json", + "@type":"sc:Manifest", + "@label":"Ms 1405" + }, + { + "@id":"https://iiif.ub.uni-leipzig.de/0000003063/manifest.json", + "@type":"sc:Manifest", + "@label":"Ms 1419" + }, + { + "@id":"https://iiif.ub.uni-leipzig.de/0000003150/manifest.json", + "@type":"sc:Manifest", + "@label":"Ms 1420" + }, + { + "@id":"https://iiif.ub.uni-leipzig.de/0000003860/manifest.json", + "@type":"sc:Manifest", + "@label":"Ms 1452" + }, + { + "@id":"https://iiif.ub.uni-leipzig.de/0000003234/manifest.json", + "@type":"sc:Manifest", + "@label":"Ms 1459" + }, + { + "@id":"https://iiif.ub.uni-leipzig.de/0000004120/manifest.json", + "@type":"sc:Manifest", + "@label":"Ms 1466" + }, + { + "@id":"https://iiif.ub.uni-leipzig.de/0000004437/manifest.json", + "@type":"sc:Manifest", + "@label":"Ms 1505" + }, + { + "@id":"https://iiif.ub.uni-leipzig.de/0000004411/manifest.json", + "@type":"sc:Manifest", + "@label":"Ms 1515" + }, + { + "@id":"https://iiif.ub.uni-leipzig.de/0000004368/manifest.json", + "@type":"sc:Manifest", + "@label":"Ms 1526" + }, + { + "@id":"https://iiif.ub.uni-leipzig.de/0000004380/manifest.json", + "@type":"sc:Manifest", + "@label":"Ms 1528" + }, + { + "@id":"https://iiif.ub.uni-leipzig.de/0000004631/manifest.json", + "@type":"sc:Manifest", + "@label":"Ms 1555" + }, + { + "@id":"https://iiif.ub.uni-leipzig.de/0000006185/manifest.json", + "@type":"sc:Manifest", + "@label":"Rep. I 4" + }, + { + "@id":"https://iiif.ub.uni-leipzig.de/0000004677/manifest.json", + "@type":"sc:Manifest", + "@label":"Rep. I 16" + }, + { + "@id":"https://iiif.ub.uni-leipzig.de/0000004590/manifest.json", + "@type":"sc:Manifest", + "@label":"Rep. I 19" + }, + { + "@id":"https://iiif.ub.uni-leipzig.de/0000004679/manifest.json", + "@type":"sc:Manifest", + "@label":"Rep. I 72" + }, + { + "@id":"https://iiif.ub.uni-leipzig.de/0000004577/manifest.json", + "@type":"sc:Manifest", + "@label":"Cod. gr. 6" + }, + { + "@id":"https://iiif.ub.uni-leipzig.de/0000004592/manifest.json", + "@type":"sc:Manifest", + "@label":"Cod. gr. 76" + } + ] +} diff --git a/fixtures/presentation-2/tpen-isolated-canvas.json b/fixtures/presentation-2/tpen-isolated-canvas.json new file mode 100644 index 0000000..e2aadd6 --- /dev/null +++ b/fixtures/presentation-2/tpen-isolated-canvas.json @@ -0,0 +1,1526 @@ +{ + "@id" : "https://t-pen.org/TPEN/canvas/13250383", + "@type" : "sc:Canvas", + "label" : "bav_pal_lat_51_0041.jpg", + "width" : 666, + "height" : 1000, + "otherContent" : [ { + "@id" : "https://t-pen.org/TPEN/project/7157/annotations/13250383", + "@type" : "sc:AnnotationList", + "label" : "https://t-pen.org/TPEN/canvas/13250383 List", + "proj" : 7157, + "on" : "https://t-pen.org/TPEN/canvas/13250383", + "@context" : "http://iiif.io/api/presentation/2/context.json", + "resources" : [ ] + }, { + "@id" : "https://t-pen.org/TPEN/project/7158/annotations/13250383", + "@type" : "sc:AnnotationList", + "label" : "https://t-pen.org/TPEN/canvas/13250383 List", + "proj" : 7158, + "on" : "https://t-pen.org/TPEN/canvas/13250383", + "@context" : "http://iiif.io/api/presentation/2/context.json", + "resources" : [ { + "@id" : "https://t-pen.org/TPEN/line/104710760", + "_tpen_line_id" : "line/104710760", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "¶Hec singule beatitudines ut impleantur postulant suffragium, septe donorum, que " + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=53,81,346,24", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-03-18 12:49:55.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104710968", + "_tpen_line_id" : "line/104710968", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "dona ut habeantur in oratione dominica, vii precibus oramus. Ecce qualiter sapientia dei " + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=53,105,346,15", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-03-18 12:50:07.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104710969", + "_tpen_line_id" : "line/104710969", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "cuncta prouidit uirtus beatitudinis precipitur ex dono spiritus sancti, donum prece ora" + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=53,120,346,22", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-03-18 12:50:24.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104710761", + "_tpen_line_id" : "line/104710761", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "tionis quam composuit impetratur." + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=53,142,181,12", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-03-18 12:50:26.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104710807", + "_tpen_line_id" : "line/104710807", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "Septem quippe sunt dona, sed unus spiritus; " + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=53,154,181,12", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-03-18 12:52:15.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104710808", + "_tpen_line_id" : "line/104710808", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "vii orationes, sed una petitio. " + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=53,166,181,15", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-03-18 12:52:24.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104710809", + "_tpen_line_id" : "line/104710809", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "Unde unam petii a domino hanc requiram. Septem " + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=53,180,181,15", + "_tpen_note" : "", + "_tpen_creator" : 2489, + "modified" : "2021-05-17 09:36:07.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104710810", + "_tpen_line_id" : "line/104710810", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "uirtutum beatitudines, una ca" + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=53,195,181,13", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-03-18 12:52:55.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104710811", + "_tpen_line_id" : "line/104710811", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "ritas. ¶ Iusticie lumen est misericordia, misericordie " + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=53,209,181,16", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-03-18 12:54:10.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104710812", + "_tpen_line_id" : "line/104710812", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "uirtus iusticia. Hec misericordia eget spiritu " + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=53,225,181,16", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-03-18 12:54:27.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104710813", + "_tpen_line_id" : "line/104710813", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "consilii, sine quo nemo circumspecte mi" + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=53,241,181,16", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-03-18 12:54:37.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104710814", + "_tpen_line_id" : "line/104710814", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "seretur. Initium est sui misereri, finis " + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=53,257,181,15", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-03-18 12:54:45.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104710815", + "_tpen_line_id" : "line/104710815", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "est pro alio mori. Ordo enim miseren" + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=53,272,181,15", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-03-18 12:55:06.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104710816", + "_tpen_line_id" : "line/104710816", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "di est, ut unusquisque incipiat a se finem " + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=53,287,181,15", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-03-18 12:55:13.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104710817", + "_tpen_line_id" : "line/104710817", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "Christo insinuat, pro peccatoribus mortuus ex " + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=53,302,181,13", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-03-18 12:55:25.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104710818", + "_tpen_line_id" : "line/104710818", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "misericordia. ¶ Misericordia de precedentibus nascitur quia " + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=53,315,181,18", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-03-18 12:56:17.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104710819", + "_tpen_line_id" : "line/104710819", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "si precesserit uera humilitas et animus " + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=53,333,181,13", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-03-18 12:56:28.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104710820", + "_tpen_line_id" : "line/104710820", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "mansuescat, et suos et aliorum casus " + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=53,346,181,16", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-03-18 12:56:38.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104710821", + "_tpen_line_id" : "line/104710821", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "fleat, et iusticiam esuriat; post nasci" + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=53,362,181,15", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-03-18 12:56:51.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104710822", + "_tpen_line_id" : "line/104710822", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "tur uera misericordia, tunc enim alienas miserias " + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=53,377,181,16", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-03-18 12:57:02.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104710823", + "_tpen_line_id" : "line/104710823", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "faciet suas, et pro uiribus uiuabit, et si " + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=53,393,181,16", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-03-18 12:57:08.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104710824", + "_tpen_line_id" : "line/104710824", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "facultas uiuandi deest, compassio " + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=53,410,181,16", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-03-18 12:57:19.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104710825", + "_tpen_line_id" : "line/104710825", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "non deerit, de hac dicitur, \"Estote miseri" + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=53,426,181,19", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-06-07 13:20:06.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104710826", + "_tpen_line_id" : "line/104710826", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "cordes, sicut et pater uerum misericors est,\" id est, ad hoc misere" + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=53,445,181,15", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-06-07 13:20:17.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104710827", + "_tpen_line_id" : "line/104710827", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "amini, ad quod et deus, ut bonitas redun" + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=53,460,181,19", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-03-18 12:58:16.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104710828", + "_tpen_line_id" : "line/104710828", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "det in omnibus. ¶Pax est unigenitus de quo " + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=53,479,181,15", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-03-24 13:30:27.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104710829", + "_tpen_line_id" : "line/104710829", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "dicitur ipse pax nostra. Qui pacem diligit " + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=53,494,181,18", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-03-24 13:30:35.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104710830", + "_tpen_line_id" : "line/104710830", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "filius est pacis. ¶ sunt qui omnes motus " + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=53,512,181,12", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-03-24 13:32:43.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104710831", + "_tpen_line_id" : "line/104710831", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "animi componunt, et rationi subiciunt quia his " + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=53,524,181,18", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-03-24 13:32:53.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104710832", + "_tpen_line_id" : "line/104710832", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "nihil repugnat, similes patri suo " + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=53,541,181,15", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-03-24 13:33:02.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104710833", + "_tpen_line_id" : "line/104710833", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "sunt , qui est summa quies. De hac pace eiectus " + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=53,556,181,15", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-03-24 13:33:24.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104710834", + "_tpen_line_id" : "line/104710834", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "est qui peruersis dominatur; hac pace intus fir" + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=53,571,181,18", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-03-24 13:33:40.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104710835", + "_tpen_line_id" : "line/104710835", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "ma quecumque exterius concitauerit, qui eiectus est " + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=53,589,181,13", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-03-24 13:34:04.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104710836", + "_tpen_line_id" : "line/104710836", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "foras, auget gloriam et cum deficit inno" + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=53,602,181,15", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-03-24 13:34:22.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104710837", + "_tpen_line_id" : "line/104710837", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "tescere facit quanta firmitas intus sit. " + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=53,617,181,15", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-03-24 13:34:31.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104710838", + "_tpen_line_id" : "line/104710838", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "Unde “Beati qui persecutionem patiuntur” propter iusticiam quoniam " + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=53,632,181,15", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-06-07 13:21:18.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104710839", + "_tpen_line_id" : "line/104710839", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "ipsorum est regnum celorum .” Pax a nobis incipit " + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=53,646,181,9", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-03-24 13:35:50.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104710840", + "_tpen_line_id" : "line/104710840", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "quia dum lex carnis repugnat legi " + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=53,655,181,13", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-03-24 13:35:59.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104710841", + "_tpen_line_id" : "line/104710841", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "mentis, non modo alterius sed nobis possumus " + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=53,669,181,13", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-03-24 13:36:20.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104710842", + "_tpen_line_id" : "line/104710842", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "esse pacifici. Sed postquam spiritus impetrat ut " + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=53,682,181,16", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-03-24 13:36:38.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104710843", + "_tpen_line_id" : "line/104710843", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "totus homo spiritui seruiat, tunc pax ad alios " + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=53,698,181,18", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-03-24 13:36:48.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104710806", + "_tpen_line_id" : "line/104710806", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "deriuatur, ut cum omnibus pacem habeamus. Vera quidem pax est ubi nichil repugnat, pax dei omnem sensum superat, que et si non potest capi ut est, tamen pro capacitate " + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=53,716,562,12", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-03-24 13:37:32.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104710844", + "_tpen_line_id" : "line/104710844", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "nostra capi potest non quanta est, sed quantum ad reconciliationem adoptionis sufficit infinitas eterne pacis. Et si non potest intelligi, et quomodo filiis ire pax conueniat, " + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=53,728,562,18", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-03-24 13:41:03.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104710845", + "_tpen_line_id" : "line/104710845", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "et quomodo deus pax seuere iudicat, et quanta et qualis sit pax totius trinitatis, ubi una uirtus, una operatio, tamen quod est et quod tanta et talis est, quanta predicatur, fide capi" + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=53,746,562,13", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-03-24 13:41:36.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104710846", + "_tpen_line_id" : "line/104710846", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "mus ad gradum pacis qui purgatus est intendit et dirigitur." + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=53,759,562,22", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-03-24 13:41:40.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104710759", + "_tpen_line_id" : "line/104710759", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "Congrua ascensio prius conte(m)ptus seculi " + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=404,81,222,21", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-03-24 13:44:25.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104710762", + "_tpen_line_id" : "line/104710762", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "cui dantur eterna, inde mores temperat mansue" + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=404,102,222,16", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-03-24 13:44:37.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104710763", + "_tpen_line_id" : "line/104710763", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "tudo et lenitas, et non est mitis qui prius pauper non fuerit. " + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=404,118,222,16", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-03-24 13:44:52.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104710764", + "_tpen_line_id" : "line/104710764", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "Impossibilem est enim secundum gaudium ascendere nisi primum " + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=404,135,222,13", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-03-24 13:45:39.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104710765", + "_tpen_line_id" : "line/104710765", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "apprehenderis, quia inter diuitias, curas, odia ani" + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=404,148,222,13", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-03-24 13:45:57.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104710766", + "_tpen_line_id" : "line/104710766", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "mus non est mitis. Sed huius incendii materia uix ad " + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=404,161,222,12", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-03-24 13:46:11.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104710767", + "_tpen_line_id" : "line/104710767", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "purum detrahitur et ideo hec lenitas sepe corrumpitur. " + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=404,173,222,15", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-04-30 14:26:09.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104710768", + "_tpen_line_id" : "line/104710768", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "Unde subdit, ‘Beati qui lugent pro suis uel aliorum pec" + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=404,188,222,16", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-03-24 13:50:22.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104710769", + "_tpen_line_id" : "line/104710769", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "catis, quod est ab irriguo inferius. Sed quod fit " + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=404,204,222,15", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-03-24 13:50:40.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104710770", + "_tpen_line_id" : "line/104710770", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "ab irriguo superius est ex desiderio celestis patrie. " + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=404,219,222,18", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-03-24 13:50:47.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104710771", + "_tpen_line_id" : "line/104710771", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "Ille lauat presentes sordes, hic accendit artius " + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=404,237,222,13", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-03-24 13:51:04.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104710772", + "_tpen_line_id" : "line/104710772", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "aeternorum amatores. Qui ante peccata deposu" + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=404,250,222,10", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-03-24 13:51:19.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104710773", + "_tpen_line_id" : "line/104710773", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "it mores mansuetudine correxit, flens etiam " + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=404,260,222,15", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-03-24 13:51:28.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104710774", + "_tpen_line_id" : "line/104710774", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "potest esurire et sitire iusticiam, quod prius " + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=404,275,222,13", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-03-24 13:52:16.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104710777", + "_tpen_line_id" : "line/104710777", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "non poterat. Hec uirtus sine spiritu fortitudinis non " + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=404,288,222,13", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-03-24 13:52:28.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104710778", + "_tpen_line_id" : "line/104710778", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "impletur, quia per eum esuriendo ad satietatem " + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=404,302,222,16", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-03-24 13:52:35.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104710779", + "_tpen_line_id" : "line/104710779", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "quandoque ueniemus." + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=404,318,222,18", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-03-24 13:52:37.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104710780", + "_tpen_line_id" : "line/104710780", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "¶Octaua sententia que ad caput redit perfectumque " + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=404,336,222,15", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-03-24 13:53:43.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104710781", + "_tpen_line_id" : "line/104710781", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "hominem declarat significat et circumcisio" + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=404,351,222,15", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-03-24 13:53:56.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104710782", + "_tpen_line_id" : "line/104710782", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "nem octaua die et resurrectionem domini post sabbatum, " + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=404,365,222,16", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-03-24 13:54:09.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104710783", + "_tpen_line_id" : "line/104710783", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "qui est octauus et primus dies et octauas quas in " + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=404,382,222,12", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-03-24 13:54:30.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104710784", + "_tpen_line_id" : "line/104710784", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "nouo testamento celebramus et numerus pente" + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=404,393,222,19", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-03-24 13:54:49.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104710785", + "_tpen_line_id" : "line/104710785", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "costes. Nam septem numero septies multipli" + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=404,413,222,15", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-03-24 13:55:01.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104710786", + "_tpen_line_id" : "line/104710786", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "cato quo fuerit , xlix. quasi octauus additur ut " + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=404,428,222,21", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-03-24 13:55:14.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104710787", + "_tpen_line_id" : "line/104710787", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "l. compleantur, et tanquam redeatur ad caput quo die " + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=404,448,222,15", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-03-24 13:55:27.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104710788", + "_tpen_line_id" : "line/104710788", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "missus est spiritus quo in regno celorum ducimur, et heredi" + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=404,463,222,15", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-03-24 13:55:49.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104710789", + "_tpen_line_id" : "line/104710789", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "tatem accipimus, consolamur, et pascimur, et misericordiam " + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=404,478,222,16", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-03-24 13:57:00.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104710790", + "_tpen_line_id" : "line/104710790", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "consequimur et mundamur et pacificamur et ita perse" + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=404,494,222,13", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-03-24 13:57:09.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104710791", + "_tpen_line_id" : "line/104710791", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "cutiones exterius illatas, et molestias pro ueritate et " + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=404,507,222,15", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-03-24 13:57:19.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104710792", + "_tpen_line_id" : "line/104710792", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "iusti[ci]a sustinemus. " + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=404,522,222,16", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-03-24 13:57:21.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104710793", + "_tpen_line_id" : "line/104710793", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "¶Mundicia cordis sexto loco ponitur, ut homo sexta " + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=404,538,222,10", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-03-24 13:59:18.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104710794", + "_tpen_line_id" : "line/104710794", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "die conditus per hanc restituatur, et depulsis uiciis " + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=404,549,222,16", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-03-24 14:00:14.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104710795", + "_tpen_line_id" : "line/104710795", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "preceptum domini illuminet oculos, ut possit uideri. " + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=404,565,222,12", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-03-24 14:00:42.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104710796", + "_tpen_line_id" : "line/104710796", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "Premissis ergo quinque gradibus purgandus est mentis oculos, " + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=404,577,222,15", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-03-24 14:00:55.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104710797", + "_tpen_line_id" : "line/104710797", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "ne mandatorum offensus animus fiat omnium reus. " + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=404,592,222,13", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-03-24 14:02:28.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104710798", + "_tpen_line_id" : "line/104710798", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "Ideo homo se a mundo exuat, eterna diligat. Am" + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=404,605,222,15", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-03-24 14:02:57.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104710799", + "_tpen_line_id" : "line/104710799", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "plectatur deum quo appossit uidere eum, quem tantum " + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=404,620,222,13", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-03-24 14:03:16.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104710800", + "_tpen_line_id" : "line/104710800", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "uidet quantum seculo moritur. In futuro sicut est oculo " + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=404,633,222,16", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-03-24 14:03:42.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104710801", + "_tpen_line_id" : "line/104710801", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "mentis uidebitur. Ideo spiritus intelligentie huic " + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=404,649,222,16", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-03-24 14:03:51.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104710802", + "_tpen_line_id" : "line/104710802", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "gradui aptatur, quia intellectus mentis est oculos, et ex" + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=404,666,222,15", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-03-24 14:04:07.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104710803", + "_tpen_line_id" : "line/104710803", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "pletio mandatorum dei aperit intellectum." + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=404,680,222,13", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-03-24 14:04:09.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104710804", + "_tpen_line_id" : "line/104710804", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "beatitudo martyrio terminatur, que probatum reddit " + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=404,694,222,12", + "_tpen_note" : "This line and the one right after it are a continuation of an interlinear gloss.", + "_tpen_creator" : 2490, + "modified" : "2021-03-24 14:06:18.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104710805", + "_tpen_line_id" : "line/104710805", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "et suppletur precedentibus bonis" + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=404,706,222,4", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-03-24 14:06:20.0" + } ] + }, { + "@id" : "https://t-pen.org/TPEN/project/7162/annotations/13250383", + "@type" : "sc:AnnotationList", + "label" : "https://t-pen.org/TPEN/canvas/13250383 List", + "proj" : 7162, + "on" : "https://t-pen.org/TPEN/canvas/13250383", + "@context" : "http://iiif.io/api/presentation/2/context.json", + "resources" : [ { + "@id" : "https://t-pen.org/TPEN/line/104708459", + "_tpen_line_id" : "line/104708459", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "¶Restat post predicta rimarum colluuionem luctu pur" + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=201,130,240,22", + "_tpen_note" : "This gloss continues onto the next line", + "_tpen_creator" : 2490, + "modified" : "2021-03-05 15:43:32.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104708460", + "_tpen_line_id" : "line/104708460", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "gare. Luctus pro peccatis uel desiderio celesti consolationem meretur " + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=201,152,240,22", + "_tpen_note" : "This gloss continues onto the next line", + "_tpen_creator" : 2490, + "modified" : "2021-03-05 15:44:04.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104708461", + "_tpen_line_id" : "line/104708461", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "et non alius. ¶ propter consolationem beati. ¶Spiritu paracleto de eterna" + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=201,175,240,13", + "_tpen_note" : "\"Spiritu\" starts a gloss which continues onto the next line", + "_tpen_creator" : 2490, + "modified" : "2021-03-05 15:52:25.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104708462", + "_tpen_line_id" : "line/104708462", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "leticia. ¶Amatores ueri boni, quibus non satis est quod iusti sunt, " + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=201,188,240,12", + "_tpen_note" : "\"Amatores\" starts a gloss which continues onto the next line.", + "_tpen_creator" : 2490, + "modified" : "2021-03-05 15:55:17.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104708463", + "_tpen_line_id" : "line/104708463", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "sed semper sitiunt opera iusticie ¶ Esuries iusticie spiritu " + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=201,200,240,12", + "_tpen_note" : "\"Esuries\" starts a gloss which continues onto the next line", + "_tpen_creator" : 2490, + "modified" : "2021-03-05 15:57:59.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104708464", + "_tpen_line_id" : "line/104708464", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "fortitudinis fulcitur, ne in uia lacescat." + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=201,212,240,25", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-03-05 15:58:08.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104708465", + "_tpen_line_id" : "line/104708465", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "¶Ueritia est sua cuiqus tribuere sibi et proximis et deo. Hec iusticia" + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=201,237,240,13", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-04-30 12:43:05.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104708466", + "_tpen_line_id" : "line/104708466", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "non plene in nobis inplebitur donec deus omnia in omnibus. Ideo hec possi" + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=201,250,240,12", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-03-11 11:36:14.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104708467", + "_tpen_line_id" : "line/104708467", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "mus esurire, non satiari. " + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=201,262,240,36", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-03-11 11:37:00.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104708468", + "_tpen_line_id" : "line/104708468", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "¶ Adepto quod desiderant. ¶Qui aliis in quibuslibet assis" + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=201,297,240,13", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-03-11 11:41:14.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104708469", + "_tpen_line_id" : "line/104708469", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "tunt, quibus hoc a deo rependitur, ut a miseria liberentur." + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=201,311,240,10", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-03-11 11:41:24.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104708470", + "_tpen_line_id" : "line/104708470", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "¶Misericordia est dare et dimittere." + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=201,321,240,37", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-03-11 11:42:24.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104708471", + "_tpen_line_id" : "line/104708471", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "" + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=201,358,240,59", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-03-04 14:17:19.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104708472", + "_tpen_line_id" : "line/104708472", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "¶Iudicium sine misericordia illi qui noluit misereri. ¶ Quos non " + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=201,417,240,13", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-03-11 12:01:17.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104708473", + "_tpen_line_id" : "line/104708473", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "arguit conscientia peccati, templum enim dei sanctum est." + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=201,430,240,12", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-03-11 12:01:28.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104708474", + "_tpen_line_id" : "line/104708474", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "¶ Quorum mentes premissa beatitudinis illustrant exercicia, " + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=201,442,240,34", + "_tpen_note" : "This line contains a gloss which goes on to the next line", + "_tpen_creator" : 2490, + "modified" : "2021-03-11 12:02:48.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104708475", + "_tpen_line_id" : "line/104708475", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "et earum mundicia cor ad deum gubernat, ut refor" + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=201,476,240,13", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-03-11 12:02:58.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104708476", + "_tpen_line_id" : "line/104708476", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "metur imago per peccatum deleta. " + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=201,490,240,13", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-03-11 12:03:00.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104708477", + "_tpen_line_id" : "line/104708477", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "¶ hic per spem, illic per rem." + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=201,503,240,31", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-03-11 12:04:16.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104708478", + "_tpen_line_id" : "line/104708478", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "" + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=201,534,240,62", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-03-04 14:17:30.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104708479", + "_tpen_line_id" : "line/104708479", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "Hec pax ubi nisi bona uoluntate nutritur, unde \"et in terra" + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=201,596,240,13", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-06-07 14:11:03.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104708480", + "_tpen_line_id" : "line/104708480", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "pax hominibus bone uoluntatis\" hanc mundus dare non potest, sed Iesus qui ait, \"Pacem" + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=201,609,240,12", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-06-07 14:12:17.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104708481", + "_tpen_line_id" : "line/104708481", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "meam do uobis.\" Hec superat omnem sensum, sed fide capitur, unde et ea" + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=201,621,240,30", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-06-07 14:11:25.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104708482", + "_tpen_line_id" : "line/104708482", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "ea illustramur et ei reconciliati ab omni quod ab ea dissentit nos" + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=201,651,240,16", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-03-11 12:13:32.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104708483", + "_tpen_line_id" : "line/104708483", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "abdicimus, cum autem deus erit omnia in omnibus tunc beatitudo ad" + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=201,667,240,10", + "_tpen_note" : "", + "_tpen_creator" : 2490, + "modified" : "2021-03-11 12:15:53.0" + }, { + "@id" : "https://t-pen.org/TPEN/line/104708484", + "_tpen_line_id" : "line/104708484", + "@type" : "oa:Annotation", + "motivation" : "oad:transcribing", + "resource" : { + "@type" : "cnt:ContentAsText", + "cnt:chars" : "optionis dabitur. ¶Octaua uere circumcisionis" + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383#xywh=201,678,240,35", + "_tpen_note" : "Octaua uere\" starts a gloss which continues in the margin. There is a line pointing to the continuation of the gloss, \"beatitudo martyrio terminatur, que probatum reddit et suppletur", + "_tpen_creator" : 2490, + "modified" : "2021-03-11 12:19:24.0" + } ] + } ], + "images" : [ { + "@type" : "oa:Annotation", + "motivation" : "sc:painting", + "resource" : { + "@id" : "https://t-pen.org/TPEN/pageImage?folio=13250383", + "@type" : "dctypes:Image", + "format" : "image/jpeg", + "width" : 1332, + "height" : 2000 + }, + "on" : "https://t-pen.org/TPEN/canvas/13250383" + } ] +} \ No newline at end of file diff --git a/fixtures/presentation-2/wellcome-ixif-audio.json b/fixtures/presentation-2/wellcome-ixif-audio.json new file mode 100644 index 0000000..0ce78eb --- /dev/null +++ b/fixtures/presentation-2/wellcome-ixif-audio.json @@ -0,0 +1,126 @@ +{ + "@context": [ + "http://wellcomelibrary.org/ld/ixif/0/context.json", + "http://iiif.io/api/presentation/2/context.json" + ], + "@id": "https://iiif.wellcomecollection.org/presentation/v2/b17307922", + "@type": "sc:Manifest", + "label": "Florence Nightingale : greetings to the dear old comrades of Balaclava. Rendition 1/2.", + "metadata": [ + { + "label": "Description", + "value": "

This short recording was made on 30th July 1890 to raise money for the impoverished veterans of the Charge of the Light Brigade. The full transcript of the recording says: 'When I am no longer even a memory, just a name, I hope my voice may perpetuate the great work of my life. God bless my dear old comrades of Balaclava and bring them safe to shore. Florence Nightingale.' In fact, there are two recitations; the second has slightly altered wording to the first, which was presumably a practice session.

" + }, + { + "label": "Publication/creation", + "value": "[Place of publication not identified], 1890." + }, + { + "label": "Physical description", + "value": "1 encoded audio file (01.14 min.) : 44.1kHz + 1 PDF transcript" + }, + { + "label": "Notes", + "value": "This sound recording is a copy made from the original brown wax cylinder on behalf of Wellcome Library by the British Library (18th March, 2004) featuring the voice of Florence Nightingale recorded on 30 July 1890. The wax cylinder recording had been donated to Sir Henry Wellcome in the 1930s but was forgotten in the collection for decades." + }, + { + "label": "Subjects", + "value": "History of Medicine; England; Nightingale, Florence, 1820-1910." + }, + { + "label": "Attribution", + "value": "Wellcome Collection" + }, + { + "label": "Full conditions of use", + "value": "This work has been identified as being free of known restrictions under copyright law, including all related and neighbouring rights and is being made available under the Creative Commons, Public Domain Mark.

You can copy, modify, distribute and perform the work, even for commercial purposes, without asking permission.
" + } + ], + "description": "

This short recording was made on 30th July 1890 to raise money for the impoverished veterans of the Charge of the Light Brigade. The full transcript of the recording says: 'When I am no longer even a memory, just a name, I hope my voice may perpetuate the great work of my life. God bless my dear old comrades of Balaclava and bring them safe to shore. Florence Nightingale.' In fact, there are two recitations; the second has slightly altered wording to the first, which was presumably a practice session.

", + "license": "http://creativecommons.org/publicdomain/mark/1.0/", + "logo": "https://iiif.wellcomecollection.org/logos/wellcome-collection-black.png", + "related": { + "@id": "https://wellcomecollection.org/works/tp9njewm", + "format": "text/html", + "label": "Florence Nightingale : greetings to the dear old comrades of Balaclava. Rendition 1/2." + }, + "seeAlso": { + "@id": "https://api.wellcomecollection.org/catalogue/v2/works/tp9njewm", + "profile": "https://api.wellcomecollection.org/catalogue/v2/context.json", + "format": "application/json", + "label": "Wellcome Collection Catalogue API" + }, + "service": [ + { + "@context": "http://universalviewer.io/context.json", + "@id": "http://wellcomelibrary.org/service/trackingLabels/b17307922", + "profile": "http://universalviewer.io/tracking-extensions-profile", + "trackingLabel": "Format: Audio, Institution: n/a, Identifier: b17307922, Digicode: digaudio, Collection code: n/a" + }, + { + "@context": "http://wellcomelibrary.org/ld/iiif-ext/0/context.json", + "@id": "https://wellcomelibrary.org/iiif/b17307922/access-control-hints-service", + "profile": "http://wellcomelibrary.org/ld/iiif-ext/access-control-hints", + "accessHint": "open" + } + ], + "mediaSequences": [ + { + "@id": "https://iiif.wellcomecollection.org/iiif/b17307922/xsequence/s0", + "@type": "ixif:MediaSequence", + "label": "XSequence 0", + "elements": [ + { + "@id": "https://iiif.wellcomecollection.org/av/b17307922_0056-0000-4402-0102-0-0000-0000-0.mp3/full/max/default.mp3#identity", + "@type": "dctypes:Sound", + "format": "audio/mp3", + "label": "Florence Nightingale : greetings to the dear old comrades of Balaclava. Rendition 1/2.", + "metadata": [ + { + "label": "length", + "value": "74.057144 s" + } + ], + "thumbnail": "https://iiif.wellcomecollection.org/thumb/b17307922", + "rendering": { + "@id": "https://iiif.wellcomecollection.org/av/b17307922_0056-0000-4402-0102-0-0000-0000-0.mp3/full/max/default.mp3", + "format": "audio/mp3" + } + } + ] + } + ], + "sequences": [ + { + "@id": "https://wellcomecollection.org/iiif/ixif-message/sequence/seq", + "@type": "sc:Sequence", + "label": "Unsupported extension. This manifest is being used as a wrapper for non-IIIF content (e.g., audio, video) and is unfortunately incompatible with IIIF viewers.", + "compatibilityHint": "displayIfContentUnsupported", + "canvases": [ + { + "@id": "https://wellcomelibrary.org/iiif/ixif-message/canvas/c1", + "@type": "sc:Canvas", + "label": "Placeholder image", + "thumbnail": "https://wellcomecollection.org/placeholder.jpg", + "height": 600, + "width": 600, + "images": [ + { + "@id": "https://wellcomecollection.org/iiif/ixif-message/imageanno/placeholder", + "@type": "oa:Annotation", + "motivation": "sc:painting", + "resource": { + "@id": "https://wellcomecollection.org/iiif/ixif-message-0/res/placeholder", + "@type": "dctypes:Image", + "height": 600, + "width": 600 + }, + "on": "https://wellcomecollection.org/iiif/ixif-message/canvas/c1" + } + ] + } + ] + } + ], + "within": "https://iiif.wellcomecollection.org/presentation/v2/collections/subjects/cfg7w8cr" +} \ No newline at end of file diff --git a/fixtures/presentation-2/wellcome-ixif-video.json b/fixtures/presentation-2/wellcome-ixif-video.json new file mode 100644 index 0000000..885430a --- /dev/null +++ b/fixtures/presentation-2/wellcome-ixif-video.json @@ -0,0 +1,138 @@ +{ + "@context": [ + "http://wellcomelibrary.org/ld/ixif/0/context.json", + "http://iiif.io/api/presentation/2/context.json" + ], + "@id": "https://iiif.wellcomecollection.org/presentation/v2/b24744669", + "@type": "sc:Manifest", + "label": "The antibiotics and terramycin.", + "metadata": [ + { + "label": "Description", + "value": "

A well articulated and structured history of antibiotics with examples from nature about the value of bacteria and molds. It has high production values such as stock footage and good quality animations. The action of antibiotics is described despite the rise of diseases illustrated via animation. Sulfonamides, then bacitracin, streptomycin and polymyxin were all developed as antigonists to diseases. The film outlines the desired therapeutic properties of antibiotics and discusses their application in the treatment of disease. Chemical and therapeutic properties of terramycin are explained in detail. The film is optimistic about the ongoing development of new and effective antibiotics. A male doctor is shown examining a small boy, then putting his stethoscope away in his medical case.

" + }, + { + "label": "Physical description", + "value": "1 encoded moving image (19:40 min.) : sound, colour." + }, + { + "label": "Contributors", + "value": "Basilaukas, V. F.; Churchill Productions" + }, + { + "label": "Copyright note", + "value": "Pfizer Inc" + }, + { + "label": "Creator/production credits", + "value": "Dr V. F. Basilaukas and Churchill Productions for Pfizer Ltd" + }, + { + "label": "Subjects", + "value": "Pharmacology; Anti-Bacterial Agents; Oxytetracycline; Sulfonamides" + }, + { + "label": "Attribution", + "value": "Wellcome Collection" + }, + { + "label": "Full conditions of use", + "value": "You have permission to make copies of this work under a Creative Commons, Attribution, Non-commercial license.

Non-commercial use includes private study, academic research, teaching, and other activities that are not primarily intended for, or directed towards, commercial advantage or private monetary compensation. See the Legal Code for further information.

Image source should be attributed as specified in the full catalogue record. If no source is given the image should be attributed to Wellcome Collection.
" + } + ], + "description": "

A well articulated and structured history of antibiotics with examples from nature about the value of bacteria and molds. It has high production values such as stock footage and good quality animations. The action of antibiotics is described despite the rise of diseases illustrated via animation. Sulfonamides, then bacitracin, streptomycin and polymyxin were all developed as antigonists to diseases. The film outlines the desired therapeutic properties of antibiotics and discusses their application in the treatment of disease. Chemical and therapeutic properties of terramycin are explained in detail. The film is optimistic about the ongoing development of new and effective antibiotics. A male doctor is shown examining a small boy, then putting his stethoscope away in his medical case.

", + "license": "http://creativecommons.org/licenses/by-nc/4.0/", + "logo": "https://iiif.wellcomecollection.org/logos/wellcome-collection-black.png", + "related": { + "@id": "https://wellcomecollection.org/works/m2raqspe", + "format": "text/html", + "label": "The antibiotics and terramycin." + }, + "seeAlso": { + "@id": "https://api.wellcomecollection.org/catalogue/v2/works/m2raqspe", + "profile": "https://api.wellcomecollection.org/catalogue/v2/context.json", + "format": "application/json", + "label": "Wellcome Collection Catalogue API" + }, + "service": [ + { + "@context": "http://universalviewer.io/context.json", + "@id": "http://wellcomelibrary.org/service/trackingLabels/b24744669", + "profile": "http://universalviewer.io/tracking-extensions-profile", + "trackingLabel": "Format: Video, Institution: n/a, Identifier: b24744669, Digicode: digfilm, Collection code: n/a" + }, + { + "@context": "http://wellcomelibrary.org/ld/iiif-ext/0/context.json", + "@id": "https://wellcomelibrary.org/iiif/b24744669/access-control-hints-service", + "profile": "http://wellcomelibrary.org/ld/iiif-ext/access-control-hints", + "accessHint": "open" + } + ], + "mediaSequences": [ + { + "@id": "https://iiif.wellcomecollection.org/iiif/b24744669/xsequence/s0", + "@type": "ixif:MediaSequence", + "label": "XSequence 0", + "elements": [ + { + "@id": "https://iiif.wellcomecollection.org/av/b24744669_0055-0000-7653-0000-0-0000-0000-0.mpg/full/full/max/max/0/default.mp4#identity", + "@type": "dctypes:MovingImage", + "format": "video/mp4", + "label": "The antibiotics and terramycin.", + "metadata": [ + { + "label": "length", + "value": "1180 s" + } + ], + "thumbnail": "https://iiif.wellcomecollection.org/thumb/b24744669", + "rendering": [ + { + "@id": "https://iiif.wellcomecollection.org/av/b24744669_0055-0000-7653-0000-0-0000-0000-0.mpg/full/full/max/max/0/default.mp4", + "format": "video/mp4" + }, + { + "@id": "https://iiif.wellcomecollection.org/av/b24744669_0055-0000-7653-0000-0-0000-0000-0.mpg/full/full/max/max/0/default.webm", + "format": "video/webm" + } + ], + "width": 720, + "height": 720 + } + ] + } + ], + "sequences": [ + { + "@id": "https://wellcomecollection.org/iiif/ixif-message/sequence/seq", + "@type": "sc:Sequence", + "label": "Unsupported extension. This manifest is being used as a wrapper for non-IIIF content (e.g., audio, video) and is unfortunately incompatible with IIIF viewers.", + "compatibilityHint": "displayIfContentUnsupported", + "canvases": [ + { + "@id": "https://wellcomelibrary.org/iiif/ixif-message/canvas/c1", + "@type": "sc:Canvas", + "label": "Placeholder image", + "thumbnail": "https://wellcomecollection.org/placeholder.jpg", + "height": 600, + "width": 600, + "images": [ + { + "@id": "https://wellcomecollection.org/iiif/ixif-message/imageanno/placeholder", + "@type": "oa:Annotation", + "motivation": "sc:painting", + "resource": { + "@id": "https://wellcomecollection.org/iiif/ixif-message-0/res/placeholder", + "@type": "dctypes:Image", + "height": 600, + "width": 600 + }, + "on": "https://wellcomecollection.org/iiif/ixif-message/canvas/c1" + } + ] + } + ] + } + ], + "within": "https://iiif.wellcomecollection.org/presentation/v2/collections/contributors/Basilaukas,_V._F." +} \ No newline at end of file diff --git a/fixtures/regressions/p2-image-selector-body.json b/fixtures/regressions/p2-image-selector-body.json new file mode 100644 index 0000000..54bcd2e --- /dev/null +++ b/fixtures/regressions/p2-image-selector-body.json @@ -0,0 +1,29 @@ +{ + "@context": "http://iiif.io/api/presentation/2/context.json", + "@id": "https://example.org/annotation/rotation", + "@type": "oa:Annotation", + "motivation": "sc:painting", + "on": "https://example.org/canvas/rotation", + "resource": { + "@type": "oa:SpecificResource", + "full": { + "@id": "https://example.org/iiif/image1", + "@type": "dctypes:Image", + "label": "Image to rotate", + "service": { + "@id": "https://example.org/iiif/image1", + "@context": "http://iiif.io/api/image/2/context.json", + "profile": "http://iiif.io/api/image/2/level1.json" + } + }, + "selector": { + "@context": "http://iiif.io/api/annex/openannotation/context.json", + "@type": "iiif:ImageApiSelector", + "region": "pct:0,0,10,10", + "size": "200,", + "rotation": "90", + "quality": "gray", + "format": "png" + } + } +} diff --git a/package.json b/package.json index c86d170..22c697c 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "license": "MIT", "repository": { "type": "git", - "url": "git+https://github.com/iiif-commons/parser.git" + "url": "https://github.com/IIIF-Commons/parser" }, "bin": { "iiif-parser": "./dist/cli.mjs" diff --git a/src/image-3/parser/parse-image-service-request.ts b/src/image-3/parser/parse-image-service-request.ts index 7b3156e..fc387bc 100644 --- a/src/image-3/parser/parse-image-service-request.ts +++ b/src/image-3/parser/parse-image-service-request.ts @@ -11,9 +11,9 @@ export function parseImageServiceRequest(input: string, _prefix = ""): ImageServ const [fileName, rotation, size, region, ...others] = parts; const identifier = others.reverse().filter(Boolean).join("/"); - if (parts.length === 1 || fileName === "") { + if (parts.filter(Boolean).length === 1 || fileName === "") { // likely the server will want to redirect this - return { type: "base", scheme, server, prefix, identifier }; + return { type: "base", scheme, server, prefix, identifier: path.split("/").filter(Boolean).join("/") }; } if (fileName === "info.json") { diff --git a/src/presentation-2/types/legacy/resources/content-resource.d.ts b/src/presentation-2/types/legacy/resources/content-resource.d.ts index 9a95ac5..f07d135 100644 --- a/src/presentation-2/types/legacy/resources/content-resource.d.ts +++ b/src/presentation-2/types/legacy/resources/content-resource.d.ts @@ -3,27 +3,23 @@ */ import { ImageService, Service as P3Service } from "../../../../presentation-3/types"; +type ImageApiSelectorParameters = { + "@context": "http://iiif.io/api/annex/openannotation/context.json"; + "@type": "iiif:ImageApiSelector"; + size?: string; + quality?: string; + format?: string; +}; + export type ContentResourceSelector = - | { - "@context": "http://iiif.io/api/annex/openannotation/context.json"; - "@type": "iiif:ImageApiSelector"; - region: string; - } + | (ImageApiSelectorParameters & { region: string }) + | (ImageApiSelectorParameters & { rotation: string }) + | (ImageApiSelectorParameters & { region: string; rotation: string }) + | ImageApiSelectorParameters | { "@type": ["oa:SvgSelector", "cnt:ContentAsText"] | ["cnt:ContentAsText", "oa:SvgSelector"] | "oa:SvgSelector"; chars: string; } - | { - "@context": "http://iiif.io/api/annex/openannotation/context.json"; - "@type": "iiif:ImageApiSelector"; - rotation: string; - } - | { - "@context": "http://iiif.io/api/annex/openannotation/context.json"; - "@type": "iiif:ImageApiSelector"; - region: string; - rotation: string; - } | { "@type": "oa:Choice"; default: ContentResourceSelector; diff --git a/src/presentation-2/types/legacy/resources/manifest.d.ts b/src/presentation-2/types/legacy/resources/manifest.d.ts index 870bdc9..e833b63 100644 --- a/src/presentation-2/types/legacy/resources/manifest.d.ts +++ b/src/presentation-2/types/legacy/resources/manifest.d.ts @@ -5,6 +5,7 @@ import { Sequence } from "./sequence"; import { LinkingProperties } from "../iiif/linking"; import { Range } from "./range"; import { RightsProperties } from "../iiif/rights"; +import { ContentResource } from "./content-resource"; type ManifestOmittedTechnical = "format" | "height" | "width"; type ManifestOmittedLinking = "startCanvas"; @@ -18,6 +19,12 @@ export type ManifestStructural = { */ sequences: Sequence[]; structures?: Range[]; + /** Legacy IxIF media sequences, upgraded to temporal canvases when their duration is known. */ + mediaSequences?: Array<{ + "@id"?: string; + "@type"?: "ixif:MediaSequence"; + elements: Array & { duration?: number }>; + }>; }; type ManifestLinking = OmitProperties; diff --git a/src/presentation-2/upgrader.ts b/src/presentation-2/upgrader.ts index d9254d1..f6259f5 100644 --- a/src/presentation-2/upgrader.ts +++ b/src/presentation-2/upgrader.ts @@ -206,6 +206,8 @@ function getNewType(resource: any): string { } switch (oldType) { + case "MovingImage": + return "Video"; case "Layer": return "AnnotationCollection"; case "AnnotationList": @@ -221,6 +223,8 @@ function getNewType(resource: any): string { } if (resource.format) { + if (resource.format.startsWith("audio/")) return "Sound"; + if (resource.format.startsWith("video/")) return "Video"; if (resource.format.startsWith("image/")) { return "Image"; } @@ -443,12 +447,13 @@ function descriptiveProperties ): T { const [rights, extraMetadata] = fixLicense(resource.license); + const label = resource.label || (resource as any)["@label"]; const allMetadata = [...(resource.metadata ? convertMetadata(resource.metadata) : []), ...extraMetadata]; return { rights, metadata: allMetadata.length ? allMetadata : undefined, - label: resource.label ? convertLanguageMapping(resource.label) : undefined, + label: label ? convertLanguageMapping(label) : undefined, requiredStatement: resource.attribution ? { label: convertLanguageMapping(configuration.attributionLabel), @@ -509,24 +514,23 @@ function parseWithin(resource: Presentation2.AbstractResource): undefined | Pres } function linkingProperties(resource: Presentation2.LinkingProperties & Presentation2.RightsProperties) { - // @todo related links to metadata. - const related = resource.related ? (Array.isArray(resource.related) ? resource.related : [resource.related]) : []; const layer = resource.contentLayer as Presentation2.Layer; return { - provider: - resource.logo || related.length - ? [ - { - id: configuration.providerId, - type: "Agent" as const, - homepage: related.length ? [related[0] as any] : undefined, - logo: resource.logo ? (Array.isArray(resource.logo) ? resource.logo : [resource.logo]) : undefined, - label: convertLanguageMapping(configuration.providerName), - }, - ] - : undefined, + provider: resource.logo + ? [ + { + id: configuration.providerId, + type: "Agent" as const, + logo: resource.logo ? (Array.isArray(resource.logo) ? resource.logo : [resource.logo]) : undefined, + label: convertLanguageMapping(configuration.providerName), + }, + ] + : undefined, + homepage: related.length + ? related.map((link: any) => ({ ...link, type: link.type === "unknown" ? "Text" : link.type })) + : undefined, partOf: parseWithin(resource), rendering: resource.rendering, seeAlso: resource.seeAlso, @@ -589,6 +593,15 @@ function paginationProperties(collection: Presentation2.Collection) { } } + if ((collection as any).last) { + const ref = stringOrRefToRef((collection as any).last, "Collection"); + if (ref) additionalProperties.last = ref; + } + + if ((collection as any).startIndex || (collection as any).startIndex === 0) { + additionalProperties.startIndex = (collection as any).startIndex; + } + if ((collection as any).total || (collection as any).total === 0) { additionalProperties.total = (collection as any).total; } @@ -642,10 +655,18 @@ function flattenArray(array: T[][]): T[] { function upgradeManifest(manifest: Presentation2.Manifest): Presentation3.Manifest { const allCanvases = []; + const mediaElements = (manifest.mediaSequences || []).flatMap((sequence) => sequence.elements || []); + const mediaCanvases = upgradeMediaElements(manifest, mediaElements); const behavior = []; let start = undefined; let viewingDirection = undefined; for (const sequence of manifest.sequences || []) { + if ( + mediaCanvases.length && + mediaCanvases.length === mediaElements.length && + (sequence as any).compatibilityHint === "displayIfContentUnsupported" + ) + continue; if (sequence.canvases.length) { allCanvases.push(...sequence.canvases); } @@ -676,11 +697,74 @@ function upgradeManifest(manifest: Presentation2.Manifest): Presentation3.Manife ...linkingProperties(manifest), viewingDirection, start: start, - items: allCanvases, + items: [...allCanvases, ...mediaCanvases], structures: flattenStructures(manifest.structures as any), }); } +function upgradeMediaElements( + manifest: Presentation2.Manifest, + elements: NonNullable[number]["elements"] +): Presentation3.Canvas[] { + const canvases: Presentation3.Canvas[] = []; + for (const [index, element] of elements.entries()) { + const type = getNewType(element); + if (type !== "Sound" && type !== "Video") continue; + const length = element.duration ?? element.metadata?.find((entry) => entry.label === "length")?.value; + const match = String(length ?? "") + .trim() + .match(/^(?:(\d+(?:\.\d+)?)\s*h\s*)?(?:(\d+(?:\.\d+)?)\s*(?:mn|min|m)\s*)?(?:(\d+(?:\.\d+)?)\s*s?)?$/); + const duration = match ? Number(match[1] || 0) * 3600 + Number(match[2] || 0) * 60 + Number(match[3] || 0) : 0; + if (!Number.isFinite(duration) || duration <= 0) continue; + if (type === "Video" && (!(element as any).width || !(element as any).height)) continue; + + const upgraded = presentation2to3.traverseContentResource(structuredClone(element) as any) as any; + const renderings = (upgraded.rendering || []).filter((resource: any) => + resource.format?.startsWith(type === "Sound" ? "audio/" : "video/") + ); + const bodies = (renderings.length ? renderings : [upgraded]).map((resource: any) => + removeUndefinedProperties({ + ...resource, + type, + duration, + width: upgraded.width, + height: upgraded.height, + label: resource.label || upgraded.label, + }) + ); + const id = `${manifest["@id"]}/canvas/av-${index + 1}`; + canvases.push( + removeUndefinedProperties({ + id, + type: "Canvas", + label: upgraded.label, + duration, + width: upgraded.width, + height: upgraded.height, + thumbnail: upgraded.thumbnail, + metadata: upgraded.metadata, + rendering: upgraded.rendering, + items: [ + { + id: `${id}/page`, + type: "AnnotationPage", + items: [ + { + id: `${id}/annotation`, + type: "Annotation", + motivation: "painting", + target: id, + body: bodies.length === 1 ? bodies[0] : { type: "Choice", items: bodies }, + }, + ], + }, + ], + }) as Presentation3.Canvas + ); + } + return canvases; +} + function flattenStructures(structures: Presentation3.Range[]): Presentation3.Range[] { if (!structures) { return structures; @@ -745,6 +829,7 @@ function upgradeSequence(sequence: Presentation2.Sequence): { behavior?: string[]; startCanvas?: Presentation3.Reference<"Canvas"> | undefined; viewingDirection?: "left-to-right" | "right-to-left" | "top-to-bottom" | "bottom-to-top"; + compatibilityHint?: string; } { /* rng = {"id": s.get('@id', self.mint_uri()), "type": "Range"} @@ -776,6 +861,7 @@ function upgradeSequence(sequence: Presentation2.Sequence): { behavior: sequence.viewingHint ? [sequence.viewingHint] : [], viewingDirection: sequence.viewingDirection, startCanvas: sequence.startCanvas as any, + ...((sequence as any).compatibilityHint ? { compatibilityHint: (sequence as any).compatibilityHint } : {}), }; } @@ -824,14 +910,27 @@ function upgradeAnnotation(annotation: Presentation2.Annotation): Presentation3. function upgradeContentResourceOrChoice( resource: Presentation2.ContentResource | Presentation2.ChoiceEmbeddedContent ): Presentation3.ContentResource | Presentation3.ChoiceBody { - if ((resource as any).type === "Choice") { + if ((resource as any).type === "Choice" || (resource as any).type === "SpecificResource") { return resource as any; } return upgradeContentResource(resource); } function upgradeContentResource(inputContentResource: Presentation2.ContentResource): Presentation3.ContentResource { - const contentResource = inputContentResource as Presentation2.CommonContentResource; + if (inputContentResource["@type"] === "oa:SpecificResource" && "full" in inputContentResource) { + const specific = inputContentResource as Presentation2.ImageResourceSegmentWithService; + return removeUndefinedProperties({ + id: specific["@id"], + type: "SpecificResource", + source: presentation2to3.traverseContentResource(specific.full), + selector: specific.selector ? upgradeSelector(specific.selector, true) : undefined, + styleClass: specific.style, + }) as Presentation3.ContentResource; + } + + const contentResource = ( + typeof inputContentResource === "string" ? { "@id": inputContentResource } : inputContentResource + ) as Presentation2.CommonContentResource; // @todo there might be some field dropped here. return removeUndefinedProperties({ @@ -949,7 +1048,8 @@ export function convertPresentation2(entity: any): Presentation3.Manifest | Pres entity["@context"] === "http://iiif.io/api/image/2/context.json" || // No-context is possible. (entity["@id"] && entity["@type"] === "sc:Collection") || - (entity["@id"] && entity["@type"] === "sc:Manifest") + (entity["@id"] && entity["@type"] === "sc:Manifest") || + (entity["@id"] && entity["@type"] === "sc:Canvas") ) { if (!entity["@context"]) { entity["@context"] = "http://iiif.io/api/presentation/2/context.json"; @@ -960,7 +1060,8 @@ export function convertPresentation2(entity: any): Presentation3.Manifest | Pres } function upgradeSelector( - selector: Presentation2.ContentResourceSelector + selector: Presentation2.ContentResourceSelector, + preserveUnknown = false ): Presentation3.Selector | Presentation3.Selector[] { const isSvgSelector = ((Array.isArray(selector["@type"]) && selector["@type"].includes("oa:SvgSelector")) || @@ -980,9 +1081,9 @@ function upgradeSelector( } if (selector["@type"] === "oa:Choice") { return [ - upgradeSelector(selector.default) as Presentation3.Selector, - ...((Array.isArray(selector.item) ? selector.item : [selector.item]).map( - upgradeSelector + upgradeSelector(selector.default, preserveUnknown) as Presentation3.Selector, + ...((Array.isArray(selector.item) ? selector.item : [selector.item]).map((item) => + upgradeSelector(item, preserveUnknown) ) as Presentation3.Selector[]), ]; } @@ -991,7 +1092,14 @@ function upgradeSelector( type: "ImageApiSelector", region: "region" in selector ? selector.region : undefined, rotation: "rotation" in selector ? selector.rotation : undefined, + size: selector.size, + quality: selector.quality, + format: selector.format, }; } + if (preserveUnknown) { + const { "@type": type, ...properties } = selector as any; + return { ...properties, type: typeof type === "string" ? removePrefix(type) : type }; + } throw new Error(`Unsupported selector type: ${selector["@type"]}`); } diff --git a/src/presentation-3/normalize.ts b/src/presentation-3/normalize.ts index 1be06a7..a25fabe 100644 --- a/src/presentation-3/normalize.ts +++ b/src/presentation-3/normalize.ts @@ -1,5 +1,6 @@ import type { Annotation, + AnnotationCollection, AnnotationPage, Canvas, Collection, @@ -86,13 +87,13 @@ function mapToEntities(entities: Record if (resource && resource.id && type) { storeType[resource.id] = storeType[resource.id] ? (mergeEntities(storeType[resource.id]!, resource, { - parent: context.parent, - isTopLevel: topLevel.id === resource.id, - }) as any) + parent: context.parent, + isTopLevel: topLevel.id === resource.id, + }) as any) : mergeEntities({ id: resource.id, type: resource.type } as any, resource, { - parent: context.parent, - isTopLevel: topLevel.id === resource.id, - }); + parent: context.parent, + isTopLevel: topLevel.id === resource.id, + }); return { id: resource.id, type: type === "ContentResource" ? type : resource.type, @@ -103,11 +104,23 @@ function mapToEntities(entities: Record }; } +function equalMergeValues(left: any, right: any): boolean { + if (left === right) return true; + if (!left || !right || typeof left !== "object" || typeof right !== "object") return false; + if (Array.isArray(left) !== Array.isArray(right)) return false; + const keys = Object.keys(left); + return ( + keys.length === Object.keys(right).length && + keys.every((key) => Object.prototype.hasOwnProperty.call(right, key) && equalMergeValues(left[key], right[key])) + ); +} + export function merge(existing: any, incoming: any, context?: { parent?: any; isTopLevel?: boolean }): any { if (!incoming) { // Falsy values are ignored return existing; } + if (existing === null || typeof existing === "undefined") return incoming; if (Array.isArray(existing)) { if (!Array.isArray(incoming)) { throw new Error("Cannot merge array with non-array"); @@ -118,24 +131,24 @@ export function merge(existing: any, incoming: any, context?: { parent?: any; is // merged with the existing value. const merged = [...existing]; for (const item of incoming) { - if (item["@id"] && !item.id) { - item.id = item["@id"]; - } - if (item["@type"] && !item.type) { - item.type = item["@type"]; - } - if (item === null || item === undefined) { - continue; - } + if (item === null || item === undefined) continue; + if (item["@id"] && !item.id) item.id = item["@id"]; + if (item["@type"] && !item.type) item.type = item["@type"]; + const itemId = item.id; + const itemType = item.type; if (Array.isArray(item)) { // FIXME: How to handle this properly? merged.push(item); - } else if (typeof item === "object" && item.id && item.type) { - const existingIdx = merged.findIndex((e) => e.id === item.id && e.type === item.type); + } else if (typeof item === "object" && itemId && itemType) { + const existingIdx = merged.findIndex( + (e) => (e?.id || e?.["@id"]) === itemId && (e?.type || e?.["@type"]) === itemType + ); if (existingIdx >= 0) { merged[existingIdx] = merge(merged[existingIdx], item); + } else { + merged.push(item); } - } else if (existing.indexOf(item) === -1) { + } else if (!existing.some((entry: any) => equalMergeValues(entry, item))) { merged.push(item); } } @@ -258,7 +271,8 @@ export function mergeEntities( } throw new Error( - `Can only merge entities with identical identifiers and type! ${incoming.type}(${incoming.id}) => ${(existing as any).type + `Can only merge entities with identical identifiers and type! ${incoming.type}(${incoming.id}) => ${ + (existing as any).type }(${(existing as any).id})` ); } @@ -536,6 +550,13 @@ export function normalize(unknownEntity: unknown) { addToMapping("Annotation"), addToEntities("Annotation"), ], + annotationCollection: [ + (collection, context) => { + if (context.parent) return collection; + addToMapping("AnnotationCollection")(collection); + return addToEntities("AnnotationCollection")(collection, context); + }, + ], contentResource: [ // This won't be normalized before going into the state. // It will be normalized through selectors and pattern matching. diff --git a/src/presentation-3/serialize-presentation-2.ts b/src/presentation-3/serialize-presentation-2.ts index e55f30d..2f9a456 100644 --- a/src/presentation-3/serialize-presentation-2.ts +++ b/src/presentation-3/serialize-presentation-2.ts @@ -10,6 +10,7 @@ import type { import type { DescriptiveNormalized, LinkingNormalized } from "../presentation-3-normalized/types"; import type * as Presentation2 from "../presentation-2/types"; import { compressSpecificResource } from "../shared/compress-specific-resource"; +import { IS_EXTERNAL } from "./utilities"; export function languageString2to3( value: InternationalString | null | undefined @@ -52,6 +53,7 @@ export function languageString2to3( } function parseCanvasTarget(target: any): any { + if (!target) return undefined; if (Array.isArray(target)) { return target.map((t) => parseCanvasTarget(t)); } @@ -64,9 +66,46 @@ function parseCanvasTarget(target: any): any { return target.id; } + if (target.type === "SpecificResource") { + const source = typeof target.source === "string" ? target.source : target.source.id; + if (!target.selector) return source; + if (target.selector.type === "FragmentSelector" && !target.styleClass) { + return `${source}#${target.selector.value}`; + } + return { + "@type": "oa:SpecificResource", + full: source, + selector: selectorToPresentation2(target.selector), + ...(target.styleClass ? { style: target.styleClass } : {}), + }; + } + + if (target.items) return target.items.map(parseCanvasTarget); + return target; } +function selectorToPresentation2(selector: any): any { + if (Array.isArray(selector)) { + return { + "@type": "oa:Choice", + default: selectorToPresentation2(selector[0]), + item: selector.slice(1).map(selectorToPresentation2), + }; + } + if (selector.type === "SvgSelector") return { "@type": "oa:SvgSelector", chars: selector.value }; + if (selector.type === "ImageApiSelector") { + const { type: _type, ...parameters } = selector; + return { + "@context": "http://iiif.io/api/annex/openannotation/context.json", + "@type": "iiif:ImageApiSelector", + ...parameters, + }; + } + const { type, ...properties } = selector; + return { "@type": `oa:${type}`, ...properties }; +} + function unNestArray(oneOrArray: T[] | undefined, onlyOne = false): T | T[] | undefined { if (!oneOrArray) { return undefined; @@ -105,7 +144,7 @@ function convertService(service: any) { function technicalProperties(props: Partial, type?: string) { return [ - ["@id", props.id], + ["@id", props.id?.startsWith("vault://") ? undefined : props.id], ["@type", type], ["format", props.format], ["height", props.height], @@ -170,7 +209,7 @@ function isFragmentSelector(resource: unknown): resource is FragmentSelector { function specificResourceToString(resource: Reference | SpecificResource) { if (resource && isSpecificResource(resource)) { - let id = resource.id; + let id = typeof resource.source === "string" ? resource.source : resource.source.id; const selector: Selector | undefined = resource.selector ? Array.isArray(resource.selector) ? resource.selector[0] @@ -187,6 +226,9 @@ function specificResourceToString(resource: Reference | SpecificResource) { export const serializeConfigPresentation2: SerializeConfig = { Manifest: function* (entity, state, { isTopLevel }) { + if (!isTopLevel) { + return [...technicalProperties(entity, "sc:Manifest"), ...(yield* descriptiveProperties(entity))]; + } return [ ...(isTopLevel ? [["@context", "http://iiif.io/api/presentation/2/context.json"]] : []), ...technicalProperties(entity, "sc:Manifest"), @@ -210,42 +252,74 @@ export const serializeConfigPresentation2: SerializeConfig = { Canvas: function* (entity) { const paintingPage = yield entity.items; - const resources = paintingPage[0]; + const resources = paintingPage.flatMap((page: any) => page.resources || []); return [ // Items. ...technicalProperties(entity, "sc:Canvas"), ...(yield* descriptiveProperties(entity)), ...(yield* linkingProperties(entity)), - ["images", resources ? [resources.resources] : undefined], + ["images", resources.length ? resources : undefined], [ // @todo use otherContent if they are inlined - "annotations", - entity.annotations && entity.annotations.length ? unNestArray(yield entity.annotations) : undefined, + "otherContent", + entity.annotations && entity.annotations.length ? yield entity.annotations : undefined, ], ]; }, - AnnotationPage: function* (entity) { + AnnotationPage: function* (entity, state, { isTopLevel }) { return [ + ...(isTopLevel ? [["@context", "http://iiif.io/api/presentation/2/context.json"]] : []), ...technicalProperties(entity, "sc:AnnotationList"), ...(yield* descriptiveProperties(entity)), - ["resources", entity.items && entity.items.length ? unNestArray(yield entity.items) : undefined], + ["resources", (entity as any)[IS_EXTERNAL] ? undefined : yield entity.items], ]; }, - Annotation: function* (entity) { + Annotation: function* (entity, state, { isTopLevel }) { + const bodies = []; + const annotationBodies = (Array.isArray(entity.body) ? entity.body : entity.body ? [entity.body] : []) as any[]; + for (const body of annotationBodies) { + if (isSpecificResource(body)) { + bodies.push({ + "@type": "oa:SpecificResource", + full: yield body.source, + ...(body.selector ? { selector: selectorToPresentation2(body.selector) } : {}), + ...(body.styleClass ? { style: body.styleClass } : {}), + }); + } else { + bodies.push(yield body); + } + } + const motivations = ( + Array.isArray(entity.motivation) ? entity.motivation : entity.motivation ? [entity.motivation] : [] + ).map((motivation) => + motivation.includes(":") ? motivation : `${motivation === "painting" ? "sc" : "oa"}:${motivation}` + ); return [ - ["@id", entity.id], - ["@type", "oa:Annotation"], - // This could be improved. - ["motivation", "sc:painting"], + ...(isTopLevel ? [["@context", "http://iiif.io/api/presentation/2/context.json"]] : []), + ...technicalProperties(entity as any, "oa:Annotation"), + ...(yield* descriptiveProperties(entity as any)), + ...(yield* linkingProperties(entity)), + ["motivation", unNestArray(motivations)], ["on", parseCanvasTarget(entity.target)], - ["resource", unNestArray(yield entity.body, true)], + ["resource", unNestArray(bodies)], ]; }, ContentResource: function* (entity: any) { switch (entity.type) { + case "TextualBody": + return [ + ...technicalProperties(entity, "cnt:ContentAsText"), + ["chars", entity.value ?? entity.chars], + ["language", entity.language], + ...(yield* descriptiveProperties(entity)), + ]; + case "Choice": { + const items = yield entity.items; + return [...technicalProperties(entity, "oa:Choice"), ["default", items[0]], ["item", items.slice(1)]]; + } case "Image": return [ // Image properties. @@ -255,8 +329,17 @@ export const serializeConfigPresentation2: SerializeConfig = { ]; case "Text": case "Dataset": + case "Sound": + case "Video": default: - return [...technicalProperties(entity, undefined), ...(yield* descriptiveProperties(entity))]; + return [ + ...technicalProperties( + entity, + entity.type === "Sound" ? "dctypes:Sound" : entity.type === "Video" ? "dctypes:MovingImage" : undefined + ), + ["chars", entity.chars ?? entity.value], + ...(yield* descriptiveProperties(entity)), + ]; } }, @@ -269,12 +352,16 @@ export const serializeConfigPresentation2: SerializeConfig = { ]; }, - Collection: function* (entity) { + Collection: function* (entity, state, { isTopLevel }) { return [ + ...(isTopLevel ? [["@context", "http://iiif.io/api/presentation/2/context.json"]] : []), ...technicalProperties(entity, "sc:Collection"), + ...["first", "last", "prev", "next"].map((key) => [key, (entity as any)[key]?.id || (entity as any)[key]]), + ["total", (entity as any).total], + ["startIndex", (entity as any).startIndex], ...(yield* descriptiveProperties(entity)), ...(yield* linkingProperties(entity)), - ["members", yield* entity.items], + ["members", isTopLevel ? yield entity.items : undefined], ]; }, @@ -289,12 +376,12 @@ export const serializeConfigPresentation2: SerializeConfig = { const canvas = yield item; members.push({ "@id": specificResourceToString(_item), - "@type": item.type, + "@type": `sc:${item.type}`, label: canvas ? canvas.label : undefined, within: entity.id, }); if (item.type === "Canvas") { - canvases.push(item.id); + canvases.push(specificResourceToString(_item)); } } } diff --git a/src/presentation-3/serialize-presentation-3.ts b/src/presentation-3/serialize-presentation-3.ts index 3842fc1..4dfb0b9 100644 --- a/src/presentation-3/serialize-presentation-3.ts +++ b/src/presentation-3/serialize-presentation-3.ts @@ -107,10 +107,15 @@ function* descriptiveProperties( function* linkingProperties( entity: Partial, - parent?: any + parent?: any, + omitPartOf = false ): Generator> { let filteredPartOf = []; - for (let partOf of entity.partOf || []) { + for (let partOf of omitPartOf ? [] : entity.partOf || []) { + if (parent?.type === "AnnotationCollection" && partOf.id === parent.id) { + filteredPartOf.push({ id: partOf.id, type: "AnnotationCollection" }); + continue; + } if (partOf.type === "Manifest" && parent?.type === "Manifest") continue; filteredPartOf.push(yield partOf); } @@ -141,6 +146,7 @@ export const serializeConfigPresentation3: SerializeConfig = { // Only a snippet. ...technicalProperties(entity), ...(yield* descriptiveProperties(entity)), + ...(yield* linkingProperties(entity, undefined, true)), ["navPlace", (entity as any).navPlace], ]; } @@ -192,7 +198,7 @@ export const serializeConfigPresentation3: SerializeConfig = { ]; }, - AnnotationPage: function* (entity) { + AnnotationPage: function* (entity, state, { parent }) { const entries = Object.entries(entity) .map(([key, item]) => { return [key, Array.isArray(item) ? filterEmpty(item as any) : item]; @@ -207,8 +213,8 @@ export const serializeConfigPresentation3: SerializeConfig = { // Any more properties? ["id", !entity.id?.startsWith("vault://") ? entity.id : undefined], ...entries, - ...(yield* linkingProperties(entity)), - ["items", items.length || (entity as any)[IS_EXTERNAL] === false ? items : UNSET], + ...(yield* linkingProperties(entity, parent)), + ["items", items.length || (entity as any)[IS_EXTERNAL] !== true ? items : UNSET], ]; }, @@ -301,10 +307,13 @@ export const serializeConfigPresentation3: SerializeConfig = { AnnotationCollection: function* (entity) { return [ - // @todo expand properties if they are actually used. - ["id", entity.id], - ["type", "AnnotationCollection"], - ["label", entity.label], + ["@context", (entity as any)["@context"]], + ...technicalProperties(entity), + ...(yield* descriptiveProperties(entity)), + ...(yield* linkingProperties(entity)), + ["total", (entity as any).total], + ["first", typeof (entity as any).first === "string" ? (entity as any).first : yield (entity as any).first], + ["last", typeof (entity as any).last === "string" ? (entity as any).last : yield (entity as any).last], ]; }, @@ -322,6 +331,12 @@ export const serializeConfigPresentation3: SerializeConfig = { return [ ["@context", context], ...technicalProperties(entity), + ["first", (entity as any).first], + ["last", (entity as any).last], + ["startIndex", (entity as any).startIndex], + ["prev", (entity as any).prev], + ["next", (entity as any).next], + ["total", (entity as any).total], ...(yield* descriptiveProperties(entity)), ...(yield* linkingProperties(entity)), ["items", filterEmpty(yield entity.items)], @@ -330,7 +345,14 @@ export const serializeConfigPresentation3: SerializeConfig = { } return [ ...technicalProperties(entity), + ["first", (entity as any).first], + ["last", (entity as any).last], + ["startIndex", (entity as any).startIndex], + ["prev", (entity as any).prev], + ["next", (entity as any).next], + ["total", (entity as any).total], ...(yield* descriptiveProperties(entity)), + ...(yield* linkingProperties(entity, undefined, true)), ["navPlace", (entity as any).navPlace], ]; }, diff --git a/src/presentation-3/serialize.ts b/src/presentation-3/serialize.ts index 3645549..d5a0715 100644 --- a/src/presentation-3/serialize.ts +++ b/src/presentation-3/serialize.ts @@ -96,15 +96,20 @@ export function serialize(state: CompatibleStore, subject: Reference, co } function flatten(sub: Reference, parent?: any, depth = 0) { - const generator = config[sub.type as keyof SerializeConfig]; + const generator = + config[sub.type as keyof SerializeConfig] || + (typeof sub?.type === "string" && + typeof sub.id === "string" && + !state.entities[sub.type] && + config.ContentResource); if (!generator) { return UNSET; } if (depth > 20) { throw new Error("Circular reference: " + sub.id + " " + sub.type); } - const [resource, fullResource] = - resolveIfExists(state, sub.type ? sub : sub.id, parent) || (sub.id && sub.type ? sub : null); + const [storedResource, fullResource] = state.entities[sub.type] ? resolveIfExists(state, sub, parent) : [sub, sub]; + const resource = storedResource || (sub.type === "AnnotationCollection" ? sub : undefined); if (!resource) { return UNSET; } diff --git a/src/presentation-3/traverse.ts b/src/presentation-3/traverse.ts index b2d6946..f17365b 100644 --- a/src/presentation-3/traverse.ts +++ b/src/presentation-3/traverse.ts @@ -22,6 +22,7 @@ import type { } from "./types"; import type { GeoJSON } from "../shared/geojson"; import { isSpecificResource } from "../shared/is-specific-resource"; +import { splitCanvasFragment } from "../shared/canvas-fragments"; import { ensureArray } from "../shared/ensure-array"; import { compose } from "../shared/compose"; @@ -349,6 +350,20 @@ export class Traverse { return annotationPage; } + traverseAnnotationCollection(collection: AnnotationCollection, parent?: Presentation3Resource): AnnotationCollection { + for (const key of ["first", "last"] as const) { + const page = collection[key]; + if (page && typeof page !== "string") { + collection[key] = this.traverseAnnotationPage(page as AnnotationPage, collection); + } + } + return this.traverseType( + this.traverseDescriptive(this.traverseLinking(collection as any)) as AnnotationCollection, + { parent }, + this.traversals.annotationCollection + ); + } + _traverseAnnotationPage: (page: AnnotationPage) => AnnotationPage = compose( this.traverseAnnotationPageItems.bind(this), this.traverseLinking.bind(this), @@ -466,8 +481,22 @@ export class Traverse { traverseRangeRanges(range: Range): Range { if (range.items) { range.items = range.items.map((rangeOrManifest: RangeItems) => { - if (typeof rangeOrManifest === "string") { - return this.traverseCanvas({ id: rangeOrManifest, type: "Canvas" }, range); + if (typeof rangeOrManifest === "string" || rangeOrManifest.type === "Canvas") { + const canvas = + typeof rangeOrManifest === "string" ? { id: rangeOrManifest, type: "Canvas" as const } : rangeOrManifest; + const [id, fragment] = splitCanvasFragment(canvas.id); + if (fragment) { + return this.traverseSpecificResource( + { + type: "SpecificResource", + source: { ...canvas, id }, + selector: { type: "FragmentSelector", value: fragment }, + } as SpecificResource, + "Canvas", + range + ); + } + return this.traverseCanvas(canvas as Canvas, range); } if (isSpecificResource(rangeOrManifest)) { return this.traverseSpecificResource(rangeOrManifest, "Canvas", range); @@ -552,6 +581,8 @@ export class Traverse { return this.traverseCanvas(resource as Canvas, parent); case "AnnotationPage": return this.traverseAnnotationPage(resource as AnnotationPage, parent); + case "AnnotationCollection": + return this.traverseAnnotationCollection(resource as AnnotationCollection, parent) as any; case "Annotation": return this.traverseAnnotation(resource as Annotation, parent); case "ContentResource": diff --git a/src/shared/canvas-fragments.ts b/src/shared/canvas-fragments.ts index 014aedb..58cf856 100644 --- a/src/shared/canvas-fragments.ts +++ b/src/shared/canvas-fragments.ts @@ -7,5 +7,5 @@ export function splitCanvasFragment(originalUrl?: string): string[] { } export function isValidCanvasFragment(fragment: string): boolean { - return fragment.includes("xywh=") || fragment.includes("t="); + return /(?:^|&)(?:xywh|t)=/.test(fragment); } diff --git a/src/shared/expand-target.ts b/src/shared/expand-target.ts index 1ec158c..975b853 100644 --- a/src/shared/expand-target.ts +++ b/src/shared/expand-target.ts @@ -10,7 +10,7 @@ export function expandTargetToSpecificResource( ): SpecificResource { if (Array.isArray(target)) { // Don't support multiple targets for now. - return expandTargetToSpecificResource(target[0]!); + return expandTargetToSpecificResource(target[0]!, options); } if (typeof target === "string") { @@ -29,7 +29,7 @@ export function expandTargetToSpecificResource( return { type: "SpecificResource", - source: { id, type: options.typeHint || "Unknown" }, + source: { id, type: (options.typeMap && options.typeMap[id!]) || options.typeHint || "Unknown" } as any, selector: { type: "FragmentSelector", value: fragment, @@ -45,7 +45,7 @@ export function expandTargetToSpecificResource( target.type === "Independents" ) { // we also don't support these, just choose the first. - return expandTargetToSpecificResource(target.items[0]!); + return expandTargetToSpecificResource(target.items[0]!, options); } if (!target.type && "source" in target) {