Skip to content
This repository has been archived by the owner on Feb 15, 2022. It is now read-only.

Commit

Permalink
Added proper purpose-built normalizing
Browse files Browse the repository at this point in the history
  • Loading branch information
stephenwf committed Feb 2, 2019
1 parent 668074d commit 7b32425
Show file tree
Hide file tree
Showing 5 changed files with 490 additions and 44 deletions.
114 changes: 114 additions & 0 deletions __tests__/utility/iiif-normalize-test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import { normalize } from '../../src/utility/iiif-normalize';
import { Manifest } from '../../src';

describe('utility/normalize', () => {
const manifest = (): Manifest => ({
'@context': ['http://www.w3.org/ns/anno.jsonld', 'http://iiif.io/api/presentation/{{ page.major }}/context.json'],
id: 'https://example.org/iiif/book1/manifest',
type: 'Manifest',
label: { en: ['Image 1'] },
items: [
{
id: 'https://example.org/iiif/book1/canvas/p1',
type: 'Canvas',
height: 1800,
width: 1200,
items: [
{
id: 'https://example.org/iiif/book1/page/p1/1',
type: 'AnnotationPage',
items: [
{
id: 'https://example.org/iiif/book1/annotation/p0001-image',
type: 'Annotation',
motivation: 'painting',
body: {
id: 'http://iiif.io/api/presentation/2.1/example/fixtures/resources/page1-full.png',
type: 'Image',
format: 'image/png',
height: 1800,
width: 1200,
},
target: 'https://example.org/iiif/book1/canvas/p1',
},
],
},
],
},
],
});

test('it can normalize a simple manifest', () => {
const result = normalize(manifest());

expect(result.entities).toEqual({
Annotation: {
'https://example.org/iiif/book1/annotation/p0001-image': {
body: {
id: 'http://iiif.io/api/presentation/2.1/example/fixtures/resources/page1-full.png',
type: 'ContentResource',
},
id: 'https://example.org/iiif/book1/annotation/p0001-image',
motivation: 'painting',
target: { id: 'https://example.org/iiif/book1/canvas/p1', type: 'ContentResource' },
type: 'Annotation',
},
},
AnnotationCollection: {},
AnnotationPage: {
'https://example.org/iiif/book1/page/p1/1': {
id: 'https://example.org/iiif/book1/page/p1/1',
items: [{ id: 'https://example.org/iiif/book1/annotation/p0001-image', type: 'Annotation' }],
type: 'AnnotationPage',
},
},
Canvas: {
'https://example.org/iiif/book1/canvas/p1': {
height: 1800,
id: 'https://example.org/iiif/book1/canvas/p1',
items: [{ id: 'https://example.org/iiif/book1/page/p1/1', type: 'AnnotationPage' }],
type: 'Canvas',
width: 1200,
},
},
Collection: {},
ContentResource: {
'http://iiif.io/api/presentation/2.1/example/fixtures/resources/page1-full.png': {
format: 'image/png',
height: 1800,
id: 'http://iiif.io/api/presentation/2.1/example/fixtures/resources/page1-full.png',
type: 'Image',
width: 1200,
},
'https://example.org/iiif/book1/canvas/p1': {
id: 'https://example.org/iiif/book1/canvas/p1',
type: 'ContentResource',
},
},
Manifest: {
'https://example.org/iiif/book1/manifest': {
'@context': [
'http://www.w3.org/ns/anno.jsonld',
'http://iiif.io/api/presentation/{{ page.major }}/context.json',
],
id: 'https://example.org/iiif/book1/manifest',
items: [{ id: 'https://example.org/iiif/book1/canvas/p1', type: 'Canvas' }],
label: { en: ['Image 1'] },
type: 'Manifest',
},
},
Range: {},
Service: {},
});

expect(result.mapping).toEqual({
'http://iiif.io/api/presentation/2.1/example/fixtures/resources/page1-full.png': 'ContentResource',
'https://example.org/iiif/book1/annotation/p0001-image': 'Annotation',
'https://example.org/iiif/book1/canvas/p1': 'Canvas',
'https://example.org/iiif/book1/manifest': 'Manifest',
'https://example.org/iiif/book1/page/p1/1': 'AnnotationPage',
});

expect(result.resource).toEqual({ id: 'https://example.org/iiif/book1/manifest', type: 'Manifest' });
});
});
89 changes: 80 additions & 9 deletions __tests__/utility/iiif-traverse-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Manifest } from '../../src';
import { Canvas } from '../../src/types/resources/canvas';

describe('utility/iiif-traverse', () => {
const manifest: Manifest = {
const manifest = (): Manifest => ({
'@context': ['http://www.w3.org/ns/anno.jsonld', 'http://iiif.io/api/presentation/{{ page.major }}/context.json'],
id: 'https://example.org/iiif/book1/manifest',
type: 'Manifest',
Expand Down Expand Up @@ -37,7 +37,7 @@ describe('utility/iiif-traverse', () => {
],
},
],
};
});

test('it can traverse simple manifest', () => {
const canvasList: string[] = [];
Expand All @@ -57,7 +57,7 @@ describe('utility/iiif-traverse', () => {
],
});

traversal.traverseManifest(manifest);
traversal.traverseManifest(manifest());

expect(canvasList).toEqual(['https://example.org/iiif/book1/canvas/p1']);
expect(annotationList).toEqual(['https://example.org/iiif/book1/annotation/p0001-image']);
Expand All @@ -74,7 +74,7 @@ describe('utility/iiif-traverse', () => {
],
});

const newManifest = traversal.traverseManifest(manifest);
const newManifest = traversal.traverseManifest(manifest());

expect(newManifest.items).toEqual([
{
Expand All @@ -84,6 +84,77 @@ describe('utility/iiif-traverse', () => {
]);
});

it('it can do a hack-job normalize', () => {
const store: any = {};
const traversal = Traverse.all(
(resource: any): any => {
if (resource.id && resource.type) {
store[resource.type] = store[resource.type] ? store[resource.type] : {};
store[resource.type][resource.id] = store[resource.type][resource.id]
? {
...store[resource.type][resource.id],
...resource,
}
: Object.assign({}, resource);
return { id: resource.id, type: resource.type };
}
return resource;
}
);

const result = traversal.traverseManifest(manifest());

expect(store).toEqual({
Annotation: {
'https://example.org/iiif/book1/annotation/p0001-image': {
body: { id: 'http://iiif.io/api/presentation/2.1/example/fixtures/resources/page1-full.png', type: 'Image' },
id: 'https://example.org/iiif/book1/annotation/p0001-image',
motivation: 'painting',
target: 'https://example.org/iiif/book1/canvas/p1',
type: 'Annotation',
},
},
AnnotationPage: {
'https://example.org/iiif/book1/page/p1/1': {
id: 'https://example.org/iiif/book1/page/p1/1',
items: [{ id: 'https://example.org/iiif/book1/annotation/p0001-image', type: 'Annotation' }],
type: 'AnnotationPage',
},
},
Canvas: {
'https://example.org/iiif/book1/canvas/p1': {
height: 1800,
id: 'https://example.org/iiif/book1/canvas/p1',
items: [{ id: 'https://example.org/iiif/book1/page/p1/1', type: 'AnnotationPage' }],
type: 'Canvas',
width: 1200,
},
},
Image: {
'http://iiif.io/api/presentation/2.1/example/fixtures/resources/page1-full.png': {
format: 'image/png',
height: 1800,
id: 'http://iiif.io/api/presentation/2.1/example/fixtures/resources/page1-full.png',
type: 'Image',
width: 1200,
},
},
Manifest: {
'https://example.org/iiif/book1/manifest': {
'@context': [
'http://www.w3.org/ns/anno.jsonld',
'http://iiif.io/api/presentation/{{ page.major }}/context.json',
],
id: 'https://example.org/iiif/book1/manifest',
items: [{ id: 'https://example.org/iiif/book1/canvas/p1', type: 'Canvas' }],
label: { en: ['Image 1'] },
type: 'Manifest',
},
},
});
expect(result).toEqual({ id: 'https://example.org/iiif/book1/manifest', type: 'Manifest' });
});

test('it can traverse all', () => {
const ids: string[] = [];
const traversal = Traverse.all(
Expand All @@ -95,14 +166,14 @@ describe('utility/iiif-traverse', () => {
}
);

traversal.traverseManifest(manifest);
traversal.traverseManifest(manifest());

expect(ids).toEqual([
'https://example.org/iiif/book1/manifest',
'https://example.org/iiif/book1/canvas/p1',
'https://example.org/iiif/book1/page/p1/1',
'https://example.org/iiif/book1/annotation/p0001-image',
'http://iiif.io/api/presentation/2.1/example/fixtures/resources/page1-full.png',
'https://example.org/iiif/book1/annotation/p0001-image',
'https://example.org/iiif/book1/page/p1/1',
'https://example.org/iiif/book1/canvas/p1',
'https://example.org/iiif/book1/manifest',
]);
});
});
1 change: 1 addition & 0 deletions src/types/iiif/structural.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Reference, SingleReference } from '../reference';
import { AnnotationPage } from '../resources/annotationPage';
import { Range } from '../resources/range';

export type StructuralProperties<T> = {
items: T[];
Expand Down
137 changes: 137 additions & 0 deletions src/utility/iiif-normalize.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import hash from 'object-hash';
import { TraversableEntityTypes, Traverse } from './iiif-traverse';
import { Canvas } from '../types/resources/canvas';
import { Collection, Manifest } from '..';
import { AnnotationPage } from '../types/resources/annotationPage';
import { AnnotationCollection } from '../types/resources/annotationCollection';
import { Annotation } from '../types/resources/annotation';
import { ContentResource } from '../types/resources/contentResource';
import { Service } from '../types/resources/service';
import { Range } from '../types/resources/range';

type Entities = {
Collection: {
[id: string]: Collection;
};
Manifest: {
[id: string]: Manifest;
};
Canvas: {
[id: string]: Canvas;
};
AnnotationPage: {
[id: string]: AnnotationPage;
};
AnnotationCollection: {
[id: string]: AnnotationCollection;
};
Annotation: {
[id: string]: Annotation;
};
ContentResource: {
[id: string]: ContentResource;
};
Range: {
[id: string]: Range;
};
Service: {
[id: string]: Service;
};
};

type Mapping = {
[id: string]: TraversableEntityTypes;
};

const defaultEntities: Entities = {
Collection: {},
Manifest: {},
Canvas: {},
AnnotationPage: {},
AnnotationCollection: {},
Annotation: {},
ContentResource: {},
Range: {},
Service: {},
};

type EntityReference = { id?: string; type?: string };
type PolyEntity = EntityReference | string;

function getResource(entityOrString: PolyEntity, type: string): EntityReference {
if (typeof entityOrString === 'string') {
return { id: entityOrString, type };
}
if (!entityOrString.id) {
throw new Error('Invalid resource does not have an ID');
}
return entityOrString as EntityReference;
}

function mapToEntities(entities: Entities) {
return <T extends EntityReference | string>(type: TraversableEntityTypes) => {
const storeType = entities[type] ? entities[type] : {};
return (r: T): T => {
const resource = getResource(r, type);
if (resource && resource.id && type) {
storeType[resource.id] = storeType[resource.id]
? Object.assign({}, storeType[resource.id], resource)
: Object.assign({}, resource);
return { id: resource.id, type: type } as T;
}
return resource as T;
};
};
}

function recordTypeInMapping(mapping: Mapping) {
return <T extends EntityReference | string>(type: TraversableEntityTypes) => {
return (r: T): T => {
const { id } = getResource(r, type);
if (typeof id === 'undefined') {
throw new Error('Found invalid entity without an ID.');
}
mapping[id] = type;
return r;
};
};
}

function addMissingIdToContentResource() {
return (resource: ContentResource): ContentResource => {
if (typeof resource === 'string') {
return { id: resource, type: 'ContentResource' } as ContentResource;
}
if (!resource.id) {
return { id: hash(resource), type: 'ContentResource', ...resource };
}
if (!resource.type) {
return { type: 'ContentResource', ...resource };
}
return resource;
};
}

export function normalize(entity: unknown) {
const entities: Entities = { ...defaultEntities };
const mapping: Mapping = {};
const addToEntities = mapToEntities(entities);
const addToMapping = recordTypeInMapping(mapping);
const traversal = new Traverse({
collection: [addToMapping<Collection>('Collection'), addToEntities<Collection>('Collection')],
manifest: [addToMapping<Manifest>('Manifest'), addToEntities<Manifest>('Manifest')],
canvas: [addToMapping<Canvas>('Canvas'), addToEntities<Canvas>('Canvas')],
annotationPage: [addToMapping<AnnotationPage>('AnnotationPage'), addToEntities<AnnotationPage>('AnnotationPage')],
annotation: [addToMapping<Annotation>('Annotation'), addToEntities<Annotation>('Annotation')],
contentResource: [
addMissingIdToContentResource(),
addToMapping<ContentResource>('ContentResource'),
addToEntities<ContentResource>('ContentResource'),
],
range: [addToMapping<Range>('Range'), addToEntities<Range>('Range')],
service: [addToMapping<Service>('Service'), addToEntities<Service>('Service')],
});
const resource = traversal.traverseUnknown(entity) as EntityReference;

return { entities, resource, mapping };
}
Loading

0 comments on commit 7b32425

Please sign in to comment.