Compare commits

...

3 Commits

4 changed files with 66 additions and 33 deletions

View File

@@ -1,6 +1,6 @@
# IIIF Manifest service for the GreekSchools Project # IIIF Manifest service for the GreekSchools Project
This repository holds the code for a NodeJS/Express service that implements dynamic generation of IIIF manifests, compliant with version 2 of the Presentation API. Support for version 3 should be added in the future. This repository holds the code for a NodeJS/Express service that implements dynamic generation of IIIF manifests for images produced by the [GreekSchools ERC project](https://greekschools.eu). The service is compliant with version 2 of the Presentation API, support for version 3 should be added in the future.
The project uses `yarn` for dependency management and an `.env` file to set environment variables, an example of which can be found in `.env.example`. The project uses `yarn` for dependency management and an `.env` file to set environment variables, an example of which can be found in `.env.example`.
@@ -8,19 +8,19 @@ The project uses `yarn` for dependency management and an `.env` file to set envi
`GreekManifests` requires NodeJS v. >= 20 to be installed on the system, as well as `yarn` as a package manager, which can be installed globally via `npm`: `GreekManifests` requires NodeJS v. >= 20 to be installed on the system, as well as `yarn` as a package manager, which can be installed globally via `npm`:
``` ```shell
npm install -g yarn npm install -g yarn
``` ```
To install the service itself, clone this repository on the target host (replace `<target_dir>` with a suitable path, or remove to install in `./greek-manifests`): To install the service itself, clone this repository on the target host (replace `<target_dir>` with a suitable path, or remove to install in `./greek-manifests`):
``` ```shell
git clone https://git.electricmandarine.cloud/nicolo/greek-manifests <target_dir> git clone https://git.electricmandarine.cloud/nicolo/greek-manifests <target_dir>
``` ```
then run the following commands from the root folder: then run the following commands from the root folder:
``` ```shell
yarn yarn
node app.mjs node app.mjs
``` ```
@@ -30,9 +30,9 @@ This will start the [Express](https://expressjs.com) web server, which will rema
## Documentation ## Documentation
Automatic JSDoc documentation for the codebase can be generated by running the following command from the root folder: Automatic [JSDoc](https://jsdoc.app) documentation for the codebase can be generated by running the following command from the project's root folder:
``` ```shell
jsdoc -c jsdoc.json jsdoc -c jsdoc.json
``` ```

View File

@@ -28,7 +28,7 @@ import { authors, COPYRIGHT } from '../constants.js';
* @property {String} subfolder * @property {String} subfolder
*/ */
const namePos = { export const namePos = {
hiroxnir: 1, hiroxnir: 1,
nir: 1, nir: 1,
visr: 1, visr: 1,
@@ -37,6 +37,28 @@ const namePos = {
dn: 2 dn: 2
}; };
/**
* Remove leading zeroes
* from canvas and image file
* name segments (parts)
* @see getCanvasName()
* @see ManifestBuilder.getImageName()
* @param {String} segment
*/
export const normaliseSegment = (segment) => {
return segment.replaceAll(/^([a-z]+)?0*(\d+)/ig, '$1$2');
}
/**
* Normalize a full canvas name
* @param {String} name
*/
export const normaliseName = (name) => {
// Account for separate parts in name
// and rejoin them after normalisation
// also replace any "slipping" file extension
return name.split('&').map(normaliseSegment).join('&').replace(/\.[a-z\d]+$/ig,'');
}
/** /**
* @param {string} imgFilename * @param {string} imgFilename
* @returns {ParsedMetadata} * @returns {ParsedMetadata}
@@ -162,7 +184,7 @@ const extractors = {
* @param {string} technique * @param {string} technique
* @returns {ParsedMetadata} * @returns {ParsedMetadata}
*/ */
export function parse (imgFilename, technique) { export function parse(imgFilename, technique) {
return extractors[technique](imgFilename); return extractors[technique](imgFilename);
} }
/** /**
@@ -180,7 +202,7 @@ export function getCanvasName(imgFilename, technique) {
canvasName += imgFilename.split('_')[3].replace(/\..*$/,''); canvasName += imgFilename.split('_')[3].replace(/\..*$/,'');
} }
return canvasName; return normaliseName(canvasName);
} }
/** /**
* Generate canvas label from canvasName * Generate canvas label from canvasName

View File

@@ -1,6 +1,8 @@
'use strict'; 'use strict';
import * as fs from 'fs'; import * as fs from 'fs';
import Manifest from '../iiif/Manifest.js';
import { normaliseName, normaliseSegment, namePos } from './FilenameParser.js';
/** /**
* Handles filesystem for image repository * Handles filesystem for image repository
* @module ImageRepository * @module ImageRepository
@@ -58,4 +60,35 @@ export async function getParamsFromFolders () {
} }
return params; return params;
}
/**
* This extacts a segment to be used
* in getImageName(), accounting for the
* HSI special case
* @param {string} filename The image filename
* @param {string} technique
* @returns
*/
function getMatchableSegment(filename, technique) {
const parts = filename.split('_');
if (technique === 'hsi') {
const name = normaliseSegment(parts[1]);
const pca = parts[3].replace(/\.[a-z\d]+$/i, '').toLowerCase();
return `${name}${pca}`;
}
return normaliseName(parts[namePos[technique]]);
}
/**
* Get image name for given canvas
* @param {string} name The Canvas name
* @param {Manifest} manifest The Manifest object
* @returns {string}
*/
export async function getImageName(name, manifest) {
const images = await getImageList(manifest.resourceId);
let adjustedCanvasName = name;
return images.filter(i => {
return getMatchableSegment(i, manifest.technique) === adjustedCanvasName;
})[0];
} }

View File

@@ -6,7 +6,7 @@ import Canvas from '../iiif/Canvas.js';
import Image from '../iiif/Image.js'; import Image from '../iiif/Image.js';
import ManifestMetadata from '../iiif/Metadata.js'; import ManifestMetadata from '../iiif/Metadata.js';
import { parse, getCanvasLabel, getCanvasName } from './FilenameParser.js'; import { parse, getCanvasLabel, getCanvasName } from './FilenameParser.js';
import { getImageList } from './ImageRepository.js'; import { getImageList, getImageName } from './ImageRepository.js';
import { TECH_NAMES } from '../constants.js'; import { TECH_NAMES } from '../constants.js';
/** /**
@@ -52,7 +52,7 @@ export async function buildManifest(manifestId) {
export async function buildCanvas(manifestId, name) { export async function buildCanvas(manifestId, name) {
const manifest = new Manifest(IIIF_API_VERSION, BASE_URL); const manifest = new Manifest(IIIF_API_VERSION, BASE_URL);
manifest.generateID(manifestId); manifest.generateID(manifestId);
let filename = await getImageName(name, manifestId); let filename = await getImageName(name, manifest);
return createCanvas(manifest, filename); return createCanvas(manifest, filename);
} }
@@ -141,28 +141,6 @@ async function populateCanvases (manifest, images) {
return manifest; return manifest;
} }
/**
* Get image name for given canvas
* @todo Use regex in filter!!
* @param {String} name The canvas name
* @param {String} manifestId The manifest (resource) id
* @returns {string}
*/
async function getImageName(name, manifestId) {
const images = await getImageList(manifestId);
// Adjust canvas name for HSI with PCA...
if (/pc(1|3)/.test(name)) {
name = name.replace(
/pc((1|3))/,
function (match, group1) {
return `_HSI_PC${group1}`;
}
);
}
return images.filter(i => i.includes(name))[0];
}
/** /**
* @param {Manifest} manifest The Manifest object * @param {Manifest} manifest The Manifest object
* @param {string} imgFilename * @param {string} imgFilename