UNPKG

@allmaps/iiif-parser

Version:

Allmaps IIIF parser

174 lines (173 loc) 6.5 kB
import { Manifest2Schema, Manifest3Schema, ManifestSchema } from '../schemas/iiif.js'; import { Image } from './image.js'; import { Canvas } from './canvas.js'; import { parseVersion2String, parseVersion3String, parseVersion2Metadata, parseVersion3Metadata, parseVersion2Attribution, parseVersion2Thumbnail, parseVersion2Rendering, parseVersion2Related } from '../lib/convert.js'; const ManifestTypeString = 'manifest'; /** * Parsed IIIF Manifest, embedded in a Collection * @property embedded - Whether the Manifest is embedded in a Collection * @property uri - URI of Manifest * @property label - Label of Manifest * @property majorVersion - IIIF API version of Manifest * @property type - Resource type, equals 'manifest' */ export class EmbeddedManifest { embedded = true; uri; type = ManifestTypeString; label; description; metadata; navDate; navPlace; thumbnail; majorVersion; constructor(parsedManifest) { if ('@type' in parsedManifest) { // IIIF Presentation API 2.0 this.uri = parsedManifest['@id']; this.majorVersion = 2; this.label = parseVersion2String(parsedManifest.label); this.description = parseVersion2String(parsedManifest.description); this.metadata = parseVersion2Metadata(parsedManifest.metadata); this.navDate = parsedManifest.navDate; this.navPlace = parsedManifest.navPlace; } else if ('type' in parsedManifest) { // IIIF Presentation API 3.0 this.uri = parsedManifest.id; this.majorVersion = 3; this.label = parseVersion3String(parsedManifest.label); this.description = parseVersion3String(parsedManifest.description); this.metadata = parseVersion3Metadata(parsedManifest.metadata); this.navDate = parsedManifest.navDate; this.navPlace = parsedManifest.navPlace; this.thumbnail = parsedManifest.thumbnail; } else { throw new Error('Unsupported Manifest'); } } } /** * Parsed IIIF Manifest * * @property canvases - Array of parsed canvases * @property description - Description of Manifest * @property metadata - Metadata of Manifest */ export class Manifest extends EmbeddedManifest { source; #itemParseOptions = {}; canvases = []; homepage; rendering; seeAlso; summary; requiredStatement; annotations; embedded = false; constructor(parsedManifest, options) { super(parsedManifest); this.source = options?.source; if ('@type' in parsedManifest) { // IIIF Presentation API 2.0 const sequence = parsedManifest.sequences[0]; this.canvases = this.#canvasesConstructor(sequence.canvases); this.requiredStatement = parseVersion2Attribution(parsedManifest.attribution); this.thumbnail = parseVersion2Thumbnail(parsedManifest.thumbnail); this.rendering = parseVersion2Rendering(parsedManifest.rendering); this.homepage = parseVersion2Related(parsedManifest.related); } else if ('type' in parsedManifest) { // IIIF Presentation API 3.0 this.homepage = parsedManifest.homepage; this.rendering = parsedManifest.rendering; this.seeAlso = parsedManifest.seeAlso; this.summary = parsedManifest.summary; this.requiredStatement = parsedManifest.requiredStatement; this.annotations = parsedManifest.annotations; this.canvases = this.#canvasesConstructor(parsedManifest.items); } else { throw new Error('Unsupported Manifest'); } } #canvasesConstructor(parsedCanvases) { return parsedCanvases.flatMap((canvas) => { try { return new Canvas(canvas); } catch { return []; } }); } /** * Parses a IIIF resource and returns a [Manifest](#manifest) containing the parsed version * @param iiifManifest - Source data of IIIF Manifest * @param majorVersion - IIIF API version of Manifest. If not provided, it will be determined automatically * @returns Parsed IIIF Manifest */ static parse(iiifManifest, options) { const { majorVersion, keepSource } = options || {}; let parsedManifest; if (majorVersion === 2) { parsedManifest = Manifest2Schema.parse(iiifManifest); } else if (majorVersion === 3) { parsedManifest = Manifest3Schema.parse(iiifManifest); } else { parsedManifest = ManifestSchema.parse(iiifManifest); } return new Manifest(parsedManifest, keepSource ? { source: iiifManifest } : {}); } get images() { return this.canvases.map((canvas) => canvas.image); } async #fetchImage(image, fetchFn) { if (image instanceof Image) { return image; } else { const url = `${image.uri}/info.json`; const iiifImage = await fetchFn(url).then((response) => response.json()); const fetchedImage = Image.parse(iiifImage, { keepSource: this.source !== undefined }); return fetchedImage; } } async fetchAllItems(fetchFn = globalThis.fetch) { const results = []; for await (const next of this.fetchNextItem(fetchFn)) { results.push(next); } return results; } async *fetchNextItem(fetchFn = globalThis.fetch, depth = 0) { for (const canvas of this.canvases) { const fetchedImage = await this.#fetchImage(canvas.image, fetchFn); canvas.image = fetchedImage; yield { item: fetchedImage, depth, parent: { uri: this.uri, type: this.type } }; } } // TODO: implement fetchByUri function, also for collections async fetchImageByUri(imageUri, fetchFn = globalThis.fetch) { for (const canvas of this.canvases) { if (canvas.image.uri === imageUri) { const fetchedImage = await this.#fetchImage(canvas.image, fetchFn); canvas.image = fetchedImage; return fetchedImage; } } } }