Skip to content

Commit

Permalink
Replace URL parser with RegEx with configurable path prefix
Browse files Browse the repository at this point in the history
  • Loading branch information
mbklein committed Jul 13, 2022
1 parent a8af5d6 commit 2a96ad4
Show file tree
Hide file tree
Showing 7 changed files with 54 additions and 37 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

Only features and major fixes are listed. Everything else can be considered a minor bugfix or maintenance release.

##### v3.0.0

- Add `pathPrefix` option (default: `/iiif/2/`) to constructor instead of popping a specific number of path segments off of the end of the URL

##### v2.0.0

- Pass `baseUrl` to `streamResolver` and `dimension` functions
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ const processor = new IIIF.Processor(url, streamResolver, opts);
* `density` (integer) – the pixel density to be included in the result image in pixels per inch
* This has no effect whatsoever on the size of the image that gets returned; it's simply for convenience when using
the resulting image in software that calculates a default print size based on the height, width, and density
* `pathPrefix` (string) – the default prefix that precedes the `id` part of the URL path (default: `/iiif/2/`)

## Examples

Expand Down
56 changes: 28 additions & 28 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,39 +3,19 @@ const mime = require('mime-types');
const transform = require('./lib/transform');
const IIIFError = require('./lib/error');

const filenameRe = /(color|gray|bitonal|default)\.(jpe?g|tiff?|gif|png|webp)/;

function parseUrl (url) {
const result = {};
const segments = url.split('/');
result.filename = segments.pop();
if (result.filename.match(filenameRe)) {
result.rotation = segments.pop();
result.size = segments.pop();
result.region = segments.pop();
result.quality = RegExp.$1;
result.format = RegExp.$2;
}
result.id = decodeURIComponent(segments.pop());
result.baseUrl = segments.join('/');
return result;
}
const DefaultPathPrefix = '/iiif/2/';

class Processor {
constructor (url, streamResolver, ...args) {
const opts = this.parseOpts(args);

this
.initialize(url, streamResolver)
.setOpts(opts);

if (!filenameRe.test(this.filename) && this.filename !== 'info.json') {
throw new IIIFError(`Invalid IIIF URL: ${url}`);
}

if (typeof streamResolver !== 'function') {
throw new IIIFError('streamResolver option must be specified');
}

this
.setOpts(opts)
.initialize(url, streamResolver);
}

parseOpts (args) {
Expand All @@ -59,19 +39,39 @@ class Processor {
this.maxWidth = opts.maxWidth;
this.includeMetadata = !!opts.includeMetadata;
this.density = opts.density || null;
this.pathPrefix = opts.pathPrefix?.replace(/^\/*/, '/').replace(/\/*$/, '/') || DefaultPathPrefix;

return this;
}

initialize (url, streamResolver) {
let params = url;
parseUrl (url) {
let result;

if (typeof url === 'string') {
params = parseUrl(params);
const parser = new RegExp(`(?<baseUrl>https?://[^/]+${this.pathPrefix})(?<path>.+)$`);
const { baseUrl, path } = parser.exec(url).groups;
result = transform.IIIFRegExp.exec(path)?.groups;
if (result === undefined) {
throw new IIIFError(`Invalid IIIF URL: ${url}`);
}
result.baseUrl = baseUrl;
} else {
result = url;
}

return result;
}

initialize (url, streamResolver) {
const params = this.parseUrl(url);

Object.assign(this, params);
this.streamResolver = streamResolver;

if (this.quality && this.format) {
this.filename = [this.quality, this.format].join('.');
} else if (this.info) {
this.filename = 'info.json';
}
return this;
}
Expand Down
16 changes: 13 additions & 3 deletions lib/transform.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,12 @@ function validator (type) {
if (result instanceof Array) {
result = result.join('|');
}
return new RegExp('^(' + result + ')$');
return `(?<${type}>${result})`;
}

function validate (type, v) {
if (!validator(type).test(v)) {
const re = new RegExp(`^${validator(type)}$`);
if (!re.test(v)) {
throw new IIIFError(`Invalid ${type}: ${v}`);
}
return true;
Expand All @@ -36,7 +37,15 @@ function validateDensity (v) {
throw new IIIFError(`Invalid density value: ${v}`);
}
return true;
};
}

function iiifRegExp () {
const transformation =
['region', 'size', 'rotation'].map(type => validator(type)).join('/') +
'/' + validator('quality') + '.' + validator('format');

return new RegExp(`^/?(?<id>.+?)/(?:(?<info>info.json)|${transformation})$`);
}

class Operations {
constructor (dims) {
Expand Down Expand Up @@ -207,6 +216,7 @@ class Operations {
module.exports = {
Qualities: Validators.quality,
Formats: Validators.format,
IIIFRegExp: iiifRegExp(),
Operations,
IIIFError
};
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "iiif-processor",
"version": "2.0.1",
"version": "3.0.0",
"description": "IIIF 2.1 Image API modules for NodeJS",
"main": "index.js",
"repository": "https://github.com/samvera-labs/node-iiif",
Expand Down
2 changes: 1 addition & 1 deletion tests/integration.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ let consoleWarnMock;

describe('info.json', () => {
beforeEach(() => {
subject = new iiif.Processor(`${base}/info.json`, streamResolver);
subject = new iiif.Processor(`${base}/info.json`, streamResolver, { pathPrefix: 'iiif/2/ab/cd/ef/gh' });
});

it('produces a valid info.json', async () => {
Expand Down
10 changes: 6 additions & 4 deletions tests/processor.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ describe('IIIF Processor', () => {
});

it('Parse URL', () => {
assert.strictEqual(subject.id, 'ab/cd/ef/gh/i');
assert.strictEqual(subject.baseUrl, 'https://example.org/iiif/2/');
assert.strictEqual(subject.rotation, '45');
assert.strictEqual(subject.size, 'pct:50');
assert.strictEqual(subject.region, '10,20,30,40');
Expand Down Expand Up @@ -166,14 +168,14 @@ describe('stream processor', () => {

const streamResolver = ({ id, baseUrl }) => {
expect(id).toEqual('i');
expect(baseUrl).toEqual('https://example.org/iiif/2/ab/cd/ef/gh');
expect(baseUrl).toEqual('https://example.org/iiif/2/ab/cd/ef/gh/');

return new Stream.Readable({
read() {}
});
}

const subject = new iiif.Processor(`https://example.org/iiif/2/ab/cd/ef/gh/i/10,20,30,40/pct:50/45/default.png`, streamResolver);
const subject = new iiif.Processor(`https://example.org/iiif/2/ab/cd/ef/gh/i/10,20,30,40/pct:50/45/default.png`, streamResolver, {pathPrefix: 'iiif/2/ab/cd/ef/gh'});
subject.execute();
})
})
Expand All @@ -190,14 +192,14 @@ describe('dimension function', () => {

const dimensionFunction = ({ id, baseUrl }) => {
expect(id).toEqual('i');
expect(baseUrl).toEqual('https://example.org/iiif/2/ab/cd/ef/gh');
expect(baseUrl).toEqual('https://example.org/iiif/2/ab/cd/ef/gh/');
return { w: 100, h: 100 }
}

const subject = new iiif.Processor(
`https://example.org/iiif/2/ab/cd/ef/gh/i/10,20,30,40/pct:50/45/default.png`,
streamResolver,
{ dimensionFunction }
{ dimensionFunction, pathPrefix: 'iiif/2/ab/cd/ef/gh' }
);
subject.execute();
})
Expand Down

0 comments on commit 2a96ad4

Please sign in to comment.