From c0f15919dc85e886ec7cb07263c6234ee1141cb8 Mon Sep 17 00:00:00 2001 From: Edward Silverton Date: Tue, 30 Dec 2025 11:56:09 +0000 Subject: [PATCH] st andrews manifest fix --- .../presentation-2-parser/upgrade.test.ts | 21 ++++++++++ .../malformed-image-annotation.json | 39 +++++++++++++++++++ src/presentation-2/traverse.ts | 9 ++++- 3 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 fixtures/presentation-2/malformed-image-annotation.json diff --git a/__tests__/presentation-2-parser/upgrade.test.ts b/__tests__/presentation-2-parser/upgrade.test.ts index 2597405..ebbf288 100644 --- a/__tests__/presentation-2-parser/upgrade.test.ts +++ b/__tests__/presentation-2-parser/upgrade.test.ts @@ -33,6 +33,7 @@ import stanfordManifest from '../../fixtures/presentation-2/stanford-manifest.js import goettingen from '../../fixtures/presentation-2/uni-goettingen.json'; import villanovaManifest from '../../fixtures/presentation-2/villanova-manifest.json'; import wikimediaProxy from '../../fixtures/presentation-2/wikimedia-proxy.json'; +import malformedImageAnnotation from '../../fixtures/presentation-2/malformed-image-annotation.json'; import { convertPresentation2, presentation2to3 } from '../../src/presentation-2'; describe('Presentation 2 to 3', () => { @@ -2615,4 +2616,24 @@ describe('Presentation 2 to 3', () => { } `); }); + + test('Malformed annotation with dctypes:Image instead of oa:Annotation', () => { + // Some manifests (e.g., St. Andrews) have @type: "dctypes:Image" on annotations + // instead of the correct @type: "oa:Annotation". The converter should fix this. + const result = presentation2to3.traverseManifest(malformedImageAnnotation as any); + const isValid = validator.validateManifest(result); + + expect(validator.validators.manifest!.errors).toEqual(null); + expect(isValid).toEqual(true); + + // Verify the annotation was correctly converted + const canvas = result.items?.[0]; + const annotationPage = canvas?.items?.[0]; + const annotation = annotationPage?.items?.[0]; + + expect(annotation).toBeDefined(); + expect(annotation?.type).toEqual('Annotation'); + expect(annotation?.motivation).toEqual('painting'); + expect(annotation?.body).toBeDefined(); + }); }); diff --git a/fixtures/presentation-2/malformed-image-annotation.json b/fixtures/presentation-2/malformed-image-annotation.json new file mode 100644 index 0000000..8fc0913 --- /dev/null +++ b/fixtures/presentation-2/malformed-image-annotation.json @@ -0,0 +1,39 @@ +{ + "@context": "http://iiif.io/api/presentation/2/context.json", + "@type": "sc:Manifest", + "@id": "https://example.org/manifest", + "label": "Test manifest with malformed annotation @type", + "sequences": [ + { + "@type": "sc:Sequence", + "canvases": [ + { + "@type": "sc:Canvas", + "@id": "https://example.org/canvas/1", + "label": "Canvas 1", + "width": 1000, + "height": 800, + "images": [ + { + "@type": "dctypes:Image", + "motivation": "sc:painting", + "on": "https://example.org/canvas/1", + "resource": { + "@type": "dctypes:Image", + "@id": "https://example.org/image/1/full/full/0/default.jpg", + "format": "image/jpeg", + "width": 1000, + "height": 800, + "service": { + "@context": "http://iiif.io/api/image/2/context.json", + "@id": "https://example.org/image/1", + "profile": "http://iiif.io/api/image/2/level1.json" + } + } + } + ] + } + ] + } + ] +} diff --git a/src/presentation-2/traverse.ts b/src/presentation-2/traverse.ts index 88f48a8..20b501c 100644 --- a/src/presentation-2/traverse.ts +++ b/src/presentation-2/traverse.ts @@ -261,7 +261,14 @@ export class Traverse< traverseCanvasItems(canvas: Canvas): Canvas { if (canvas.images) { - canvas.images = canvas.images.map((image) => this.traverseAnnotation(image)); + canvas.images = canvas.images.map((image) => { + // Fix malformed annotations where @type is missing or incorrect (e.g., "dctypes:Image" instead of "oa:Annotation"). + // Detect by presence of "on" property which is unique to annotations. + if (image.on && image['@type'] !== 'oa:Annotation' && image['@type'] !== 'Annotation') { + image['@type'] = 'oa:Annotation'; + } + return this.traverseAnnotation(image); + }); } if (canvas.otherContent) { canvas.otherContent = canvas.otherContent.map((annotationList) => this.traverseAnnotationList(annotationList));