Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IIIF validation #45

Open
wants to merge 5 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 81 additions & 14 deletions src/iiif.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const fs = require('node:fs')
const process = require('node:process')
const axios = require('axios')
const { getFacsString } = require('./lib/images')
const { processTextFiles } = require('./text')
Expand All @@ -16,14 +17,14 @@ function facsTemplate(facsData, textPath) {

if (type === 'iiif') {
surfaceEls.push(
`<surface xml:id="${id}" ulx="0" uly="0" lrx="${width}" lry="${height}" sameAs="${canvasURI}" >${labelEls}<graphic mimeType="application/json" url="${imageAPIURL}"/>${zoneEls}</surface>`,
` <surface xml:id="${id}" ulx="0" uly="0" lrx="${width}" lry="${height}" sameAs="${canvasURI}" >${labelEls}<graphic mimeType="application/json" url="${imageAPIURL}"/>${zoneEls}</surface>\n`,
)
}
else {
const ext = getExtensionForMIMEType(mimeType)
const filename = `${id}.${ext}`
surfaceEls.push(
`<surface xml:id="${id}" ulx="0" uly="0" lrx="${width}" lry="${height}">${labelEls}<graphic sameAs="${resourceEntryID}" mimeType="${mimeType}" url="${filename}"/>${zoneEls}</surface>`,
` <surface xml:id="${id}" ulx="0" uly="0" lrx="${width}" lry="${height}">${labelEls}<graphic sameAs="${resourceEntryID}" mimeType="${mimeType}" url="${filename}"/>${zoneEls}</surface>\n`,
)
}
}
Expand All @@ -37,6 +38,14 @@ function facsTemplate(facsData, textPath) {
return getFacsString(sameAs, surfaceEls, bodyTei)
};

function validateManifest(contents) {
if (!contents['@context']) {
return 'Missing @context property.'
}

return 'ok'
}

// FUNCTION TO GET THE IIIF DATA FROM THE PROVIDED URL AND PASS IT TO THE APPROPRIATE PARSERS

async function importPresentationEndpoint(manifestURL, onSuccess, nextSurfaceID = 0) {
Expand All @@ -46,26 +55,34 @@ async function importPresentationEndpoint(manifestURL, onSuccess, nextSurfaceID
const iiifTree = parseIIIFPresentation(resp.data, nextSurfaceID)
onSuccess(iiifTree)
}
catch (error) {
throw new Error(`Unable to parse IIIF manifest: '${error}`)
catch (e) {
console.error(`Unable to parse IIIF manifest: '${e.message}`)
process.exit(1)
}
}
catch {
throw new Error(`Unable to load IIIF manifest.`)
catch (e) {
console.error(`Unable to load IIIF manifest: ${e.message}`)
process.exit(1)
}
};

// THESE FIVE FUNCTIONS TAKE IN THE MANIFEST DATA AND RETURN A JSON OBJECT WITH THE FACSIMILE DATA

function parseIIIFPresentation(presentation, nextSurfaceID) {
const status = validateManifest(presentation)

if (status !== 'ok') {
throw new Error(`Manifest validation error: ${status}`)
}

const context = presentation['@context']
if (context.includes('http://iiif.io/api/presentation/2/context.json')) {
return parsePresentation2(presentation, nextSurfaceID)
}
else if (context.includes('http://iiif.io/api/presentation/3/context.json')) {
return parsePresentation3(presentation, nextSurfaceID)
}
throw new Error('Expected IIIF Presentation API context 2.')
throw new Error('Unknown presentation context.')
};

function parsePresentation2(presentation, nextSurfaceID) {
Expand Down Expand Up @@ -107,23 +124,50 @@ function manifestToFacsimile3(manifestData, nextSurfaceID) {
const manifestID = val('id', manifestData)
const manifestLabel = str(manifestData.label)

if (!canvases || canvases.length === 0) {
throw new Error('Expected manifest to contain at least one canvas.')
}

if (!manifestLabel) {
throw new Error('Expected manifest to have a label.')
}

if (!manifestID) {
throw new Error('Expected manifest to have an ID.')
}

const surfaceIDs = []
const surfaces = []
let n = nextSurfaceID
for (const canvas of canvases) {
if (canvas.type !== 'Canvas')
throw new Error('Expected a Canvas item.')

const canvasURI = canvas.id

if (!canvas.items || canvas.items.length === 0) {
throw new Error('Missing items in canvas')
}

const annotationPage = canvas.items[0]

const { width: canvasWidth, height: canvasHeight } = canvas
if (!annotationPage || annotationPage.type !== 'AnnotationPage')

if (!annotationPage || annotationPage.type !== 'AnnotationPage') {
throw new Error('Expected an Annotation Page item.')
}

const annotations = annotationPage.items
for (const annotation of annotations) {
if (annotation.type !== 'Annotation')
throw new Error('Expected an Annotation item.')
if (annotation.motivation === 'painting' && annotation.body && annotation.body.type === 'Image') {
const { body } = annotation

if (!body) {
throw new Error('Expected annotation to have a body.')
}

// width and height might be on Annotation or the Canvas
const width = Number.isNaN(body.width) ? canvasWidth : body.width
const height = Number.isNaN(body.height) ? canvasHeight : body.height
Expand All @@ -141,8 +185,10 @@ function manifestToFacsimile3(manifestData, nextSurfaceID) {
else {
imageAPIURL = val('id', body)
}

let localLabels = str(canvas.label)
const id = generateOrdinalID('f', n)

localLabels = !localLabels ? { none: [id] } : localLabels
surfaceIDs.push(id)
n++ // page count
Expand Down Expand Up @@ -180,9 +226,25 @@ function manifestToFacsimile2(manifestData, nextSurfaceID) {
const manifestID = val('id', manifestData)
const manifestLabel = str(manifestData.label)

if (!sequences || sequences.length === 0) {
throw new Error('Expected manifest to contain at least one sequence.')
}

if (!manifestLabel) {
throw new Error('Expected manifest to have a label.')
}

if (!manifestID) {
throw new Error('Expected manifest to have an ID.')
}

const sequence = sequences[0]
const { canvases } = sequence

if (!canvases || canvases.length === 0) {
throw new Error('Expected sequence to contain at least one canvas.')
}

const texts = sequence.rendering ? gatherRenderings2(sequence.rendering) : []

const surfaceIDs = []
Expand All @@ -191,8 +253,18 @@ function manifestToFacsimile2(manifestData, nextSurfaceID) {
for (const canvas of canvases) {
const { images, width, height } = canvas
const canvasURI = val('id', canvas)

if (!Array.isArray(images) || images.length === 0) {
throw new Error('Expected canvas to contain at least one image.')
}

const image = images[0]
const { resource } = image

if (!resource || !resource.service) {
throw new Error('Expected image resource to contain a service object.')
}

const imageAPIURL = resource.service ? val('id', resource.service) : val('id', resource)
const localLabels = str(canvas.label)
const id = generateOrdinalID('f', n)
Expand Down Expand Up @@ -364,12 +436,7 @@ function parseSeeAlso2(seeAlso) {
}

function parseFormat(rend) {
let format = rend.format === 'text/plain' ? 'text' : rend.format === 'application/tei+xml' ? 'tei' : null

if (!format && rend.profile === fromThePageTEIXML) {
format = 'tei'
}
return format
return rend.format === 'text/plain' ? 'text' : rend.format === 'application/tei+xml' ? 'tei' : null
}

function getLocalString(values, lang) {
Expand Down
2 changes: 2 additions & 0 deletions src/lib/images.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
const defaultBody = `
<body>
<div>
<p>Modify xml:id as desired and replace this with your text.</p>
</div>
</body>
`

Expand Down