From a010744b4028f7ec2356746cf2a2cd15c73ccf47 Mon Sep 17 00:00:00 2001 From: Romain Deltour Date: Wed, 20 Dec 2017 21:53:32 +0100 Subject: [PATCH] feat(data): extract and report link info from the Package Document (#130) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Publication links (defined as `link` elements in the Package Document) are extracted and copied under a new `links` property of the `earl:testSubject` object. For instance, the following OPF snippet: ``` ``` Would be reported as: ``` "earl:testSubject" { … "links": { "a11y:certifierReport": "http://www.example.com/report.html" } } ``` Only publication-level links (i.e. with no `refines` attribute) are extracted. Fixes #96 --- packages/ace-report/src/report-builders.js | 7 +- .../ace-report/src/report-builders.test.js | 73 ++++++++++++++++++- packages/ace-report/src/report.js | 8 +- packages/epub-utils/src/epub-parse.js | 19 +++++ tests/__tests__/report_json.test.js | 9 +++ tests/data/feat-links/EPUB/content_001.xhtml | 9 +++ tests/data/feat-links/EPUB/nav.xhtml | 12 +++ tests/data/feat-links/EPUB/package.opf | 25 +++++++ tests/data/feat-links/META-INF/container.xml | 6 ++ tests/data/feat-links/mimetype | 1 + 10 files changed, 157 insertions(+), 12 deletions(-) create mode 100644 tests/data/feat-links/EPUB/content_001.xhtml create mode 100644 tests/data/feat-links/EPUB/nav.xhtml create mode 100644 tests/data/feat-links/EPUB/package.opf create mode 100644 tests/data/feat-links/META-INF/container.xml create mode 100644 tests/data/feat-links/mimetype diff --git a/packages/ace-report/src/report-builders.js b/packages/ace-report/src/report-builders.js index 067fbefc..9b60806a 100644 --- a/packages/ace-report/src/report-builders.js +++ b/packages/ace-report/src/report-builders.js @@ -34,11 +34,12 @@ function withAssertions(obj, assertions) { return obj; } -function withTestSubject(obj, url, title = '', identifier = '', metadata = null) { +function withTestSubject(obj, url, title = '', identifier = '', metadata = null, links = null) { const testSubject = { url }; if (title.length > 0) testSubject['dct:title'] = title; if (identifier.length > 0) testSubject['dct:identifier'] = identifier; if (metadata !== undefined && metadata != null) testSubject.metadata = metadata; + if (links !== undefined && links != null) testSubject.links = links; obj['earl:testSubject'] = testSubject; return obj; } @@ -149,8 +150,8 @@ class ReportBuilder { }); return this; } - withTestSubject(url, title, identifier, metadata) { - withTestSubject(this._json, url, title, identifier, metadata); + withTestSubject(url, title, identifier, metadata, links) { + withTestSubject(this._json, url, title, identifier, metadata, links); return this; } } diff --git a/packages/ace-report/src/report-builders.test.js b/packages/ace-report/src/report-builders.test.js index 0e59d067..6524c54c 100644 --- a/packages/ace-report/src/report-builders.test.js +++ b/packages/ace-report/src/report-builders.test.js @@ -70,7 +70,8 @@ describe('report builder', () => { }); }); }); - describe('adding assertions', () => { + + describe('withAssertions', () => { let assertions; test('adding null assertion is ignored', () => { expect(report.withAssertions(null)).toBeDefined(); @@ -119,7 +120,8 @@ describe('report builder', () => { expect(assertions[1]).toEqual({ foo: 'foo' }); }); }); - describe('adding data', () => { + + describe('withData', () => { let data; beforeEach(() => { data = report.build().data; @@ -152,7 +154,8 @@ describe('report builder', () => { expect(data).toEqual({ foo: ['foo', 'bar'] }); }); }); - describe('adding properties', () => { + + describe('withProperties', () => { let properties; beforeEach(() => { properties = report.build().properties; @@ -189,4 +192,68 @@ describe('report builder', () => { expect(properties).toEqual({ foo: true }); }); }); + + describe('withTestSubject', () => { + test('with URL', () => { + expect(report.withTestSubject('https://example.com')).toBeDefined(); + const testSubject = report.build()['earl:testSubject']; + expect(testSubject).toBeDefined(); + expect(testSubject).toEqual({ + url: 'https://example.com' + }) + }); + test('with title', () => { + expect(report.withTestSubject('https://example.com', 'title')).toBeDefined(); + const testSubject = report.build()['earl:testSubject']; + expect(testSubject).toBeDefined(); + expect(testSubject).toEqual({ + url: 'https://example.com', + 'dct:title': 'title', + }) + }); + test('with identifier', () => { + expect(report.withTestSubject('https://example.com', '', 'uid')).toBeDefined(); + const testSubject = report.build()['earl:testSubject']; + expect(testSubject).toBeDefined(); + expect(testSubject).toEqual({ + url: 'https://example.com', + 'dct:identifier': 'uid', + }) + }); + test('with metadata null', () => { + expect(report.withTestSubject('https://example.com', '', '', null)).toBeDefined(); + const testSubject = report.build()['earl:testSubject']; + expect(testSubject).toBeDefined(); + expect(testSubject).toEqual({ + url: 'https://example.com', + }) + }); + test('with metadata', () => { + expect(report.withTestSubject('https://example.com', '', '', { foo: 'bar' })).toBeDefined(); + const testSubject = report.build()['earl:testSubject']; + expect(testSubject).toBeDefined(); + expect(testSubject).toEqual({ + url: 'https://example.com', + metadata: { foo: 'bar' } + }) + }); + + test('with link null', () => { + expect(report.withTestSubject('https://example.com', '', '', null, null)).toBeDefined(); + const testSubject = report.build()['earl:testSubject']; + expect(testSubject).toBeDefined(); + expect(testSubject).toEqual({ + url: 'https://example.com', + }) + }); + test('with links', () => { + expect(report.withTestSubject('https://example.com', '', '', null, { foo: 'bar' })).toBeDefined(); + const testSubject = report.build()['earl:testSubject']; + expect(testSubject).toBeDefined(); + expect(testSubject).toEqual({ + url: 'https://example.com', + links: { foo: 'bar' } + }) + }); + }); }); diff --git a/packages/ace-report/src/report.js b/packages/ace-report/src/report.js index b4052397..a7f17245 100644 --- a/packages/ace-report/src/report.js +++ b/packages/ace-report/src/report.js @@ -41,8 +41,8 @@ function aggregateHTMLOutlines(outlines) { module.exports = class Report { constructor(epub) { this._builder = new builders.ReportBuilder() - .withTestSubject(epub.path, '', '', epub.metadata) - .withA11yMeta(a11yMetaChecker.analyze(epub.metadata)); + .withTestSubject(epub.path, '', '', epub.metadata, epub.links) + .withA11yMeta(a11yMetaChecker.analyze(epub.metadata)) } get json() { @@ -56,10 +56,6 @@ module.exports = class Report { addData(data) { this._builder.withData(data); } - addOutline(outline) { - this._builder.withHOutline(outline); - return this; - } addHeadings(headings) { this._builder.withHeadingsOutline(headingsToOutline(headings)); return this; diff --git a/packages/epub-utils/src/epub-parse.js b/packages/epub-utils/src/epub-parse.js index 5ed153eb..6fc3c156 100644 --- a/packages/epub-utils/src/epub-parse.js +++ b/packages/epub-utils/src/epub-parse.js @@ -83,6 +83,24 @@ function parseMetadata(doc, select) { return result; } + +function addLink(rel, href, link) { + if (!link[rel]) { + link[rel] = href; + } else if (!(link[rel] instanceof Array)) { + link[rel] = [link[rel], href]; + } else { + link[rel].push(href); + } +} +function parseLinks(doc, select) { + const result = {}; + select('//opf:link[not(@refines)]', doc).forEach((link) => { + addLink(link.getAttribute('rel'), link.getAttribute('href'), result); + }); + return result; +} + // override the default of XHTML EpubParser.prototype.setContentDocMediaType = function(mediaType) { this.contentDocMediaType = mediaType; @@ -114,6 +132,7 @@ EpubParser.prototype.parseData = function(packageDocPath, epubDir) { { opf: 'http://www.idpf.org/2007/opf', dc: 'http://purl.org/dc/elements/1.1/'}); this.metadata = parseMetadata(doc, select); + this.links = parseLinks(doc, select); const spineItemIdrefs = select('//opf:itemref/@idref', doc); spineItemIdrefs.forEach((idref) => { diff --git a/tests/__tests__/report_json.test.js b/tests/__tests__/report_json.test.js index e7a8c962..5ab72dd6 100644 --- a/tests/__tests__/report_json.test.js +++ b/tests/__tests__/report_json.test.js @@ -145,6 +145,15 @@ describe('check data', () => { }); }); + test('extract links', async () => { + const report = await ace(path.join(__dirname, '../data/feat-links')); + expect(report['earl:testSubject']).toMatchObject({ + links: { + "a11y:certifierReport": "http://www.example.com/report.html" + }, + }); + }); + test('extract videos', async () => { const report = await ace(path.join(__dirname, '../data/feat-video')); expect(report.data).toMatchObject({ diff --git a/tests/data/feat-links/EPUB/content_001.xhtml b/tests/data/feat-links/EPUB/content_001.xhtml new file mode 100644 index 00000000..c55a9e8b --- /dev/null +++ b/tests/data/feat-links/EPUB/content_001.xhtml @@ -0,0 +1,9 @@ + + +Minimal EPUB + + +

Loomings

+

Call me Ishmael.

+ + diff --git a/tests/data/feat-links/EPUB/nav.xhtml b/tests/data/feat-links/EPUB/nav.xhtml new file mode 100644 index 00000000..1d538c19 --- /dev/null +++ b/tests/data/feat-links/EPUB/nav.xhtml @@ -0,0 +1,12 @@ + + +Minimal Nav + + + + + diff --git a/tests/data/feat-links/EPUB/package.opf b/tests/data/feat-links/EPUB/package.opf new file mode 100644 index 00000000..5e11ea0d --- /dev/null +++ b/tests/data/feat-links/EPUB/package.opf @@ -0,0 +1,25 @@ + + + + Minimal EPUB 3.0 + en + NOID + 2017-01-01T00:00:01Z + structuralNavigation + everything OK! + noFlashingHazard + noSoundHazard + noMotionSimulationHazard + textual + textual + + + + + + + + + + diff --git a/tests/data/feat-links/META-INF/container.xml b/tests/data/feat-links/META-INF/container.xml new file mode 100644 index 00000000..2cf00654 --- /dev/null +++ b/tests/data/feat-links/META-INF/container.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/tests/data/feat-links/mimetype b/tests/data/feat-links/mimetype new file mode 100644 index 00000000..57ef03f2 --- /dev/null +++ b/tests/data/feat-links/mimetype @@ -0,0 +1 @@ +application/epub+zip \ No newline at end of file