diff --git a/server/scanner/OpfFileScanner.js b/server/scanner/OpfFileScanner.js index 87c4f565..13f6cc16 100644 --- a/server/scanner/OpfFileScanner.js +++ b/server/scanner/OpfFileScanner.js @@ -2,24 +2,26 @@ const { parseOpfMetadataXML } = require('../utils/parsers/parseOpfMetadata') const { readTextFile } = require('../utils/fileUtils') class OpfFileScanner { - constructor() { } + constructor() {} /** * Parse metadata from .opf file found in library scan and update bookMetadata - * - * @param {import('../models/LibraryItem').LibraryFileObject} opfLibraryFileObj - * @param {Object} bookMetadata + * + * @param {import('../models/LibraryItem').LibraryFileObject} opfLibraryFileObj + * @param {Object} bookMetadata */ async scanBookOpfFile(opfLibraryFileObj, bookMetadata) { const xmlText = await readTextFile(opfLibraryFileObj.metadata.path) const opfMetadata = xmlText ? await parseOpfMetadataXML(xmlText) : null if (opfMetadata) { for (const key in opfMetadata) { - if (key === 'tags') { // Add tags only if tags are empty + if (key === 'tags') { + // Add tags only if tags are empty if (opfMetadata.tags.length) { bookMetadata.tags = opfMetadata.tags } - } else if (key === 'genres') { // Add genres only if genres are empty + } else if (key === 'genres') { + // Add genres only if genres are empty if (opfMetadata.genres.length) { bookMetadata.genres = opfMetadata.genres } @@ -42,4 +44,4 @@ class OpfFileScanner { } } } -module.exports = new OpfFileScanner() \ No newline at end of file +module.exports = new OpfFileScanner() diff --git a/server/utils/parsers/parseOpfMetadata.js b/server/utils/parsers/parseOpfMetadata.js index 8cf768cd..9a55c1f2 100644 --- a/server/utils/parsers/parseOpfMetadata.js +++ b/server/utils/parsers/parseOpfMetadata.js @@ -22,11 +22,22 @@ function parseCreators(metadata) { Object.keys(c['$']) .find((key) => key.startsWith('xmlns:')) ?.split(':')[1] || 'opf' - return { + const creator = { value: c['_'], role: c['$'][`${namespace}:role`] || null, fileAs: c['$'][`${namespace}:file-as`] || null } + + const id = c['$']['id'] + if (id && metadata.meta.refines?.some((r) => r.refines === `#${id}`)) { + const creatorMeta = metadata.meta.refines.filter((r) => r.refines === `#${id}`) + if (creatorMeta) { + creator.role = creatorMeta.find((r) => r.property === 'role')?.value || creator.role || null + creator.fileAs = creatorMeta.find((r) => r.property === 'file-as')?.value || creator.fileAs || null + } + } + + return creator }) } @@ -187,7 +198,6 @@ module.exports.parseOpfMetadataJson = (json) => { const prefix = packageKey.split(':').shift() let metadata = prefix ? json[packageKey][`${prefix}:metadata`] || json[packageKey].metadata : json[packageKey].metadata if (!metadata) return null - if (Array.isArray(metadata)) { if (!metadata.length) return null metadata = metadata[0] @@ -198,12 +208,22 @@ module.exports.parseOpfMetadataJson = (json) => { metadata.meta = {} if (metadataMeta?.length) { metadataMeta.forEach((meta) => { - if (meta && meta['$'] && meta['$'].name) { + if (meta?.['$']?.name) { metadata.meta[meta['$'].name] = [meta['$'].content || ''] + } else if (meta?.['$']?.refines) { + // https://www.w3.org/TR/epub-33/#sec-meta-elem + + if (!metadata.meta.refines) { + metadata.meta.refines = [] + } + metadata.meta.refines.push({ + value: meta._, + refines: meta['$'].refines, + property: meta['$'].property + }) } }) } - const creators = parseCreators(metadata) const authors = (fetchCreators(creators, 'aut') || []).map((au) => au?.trim()).filter((au) => au) const narrators = (fetchNarrators(creators, metadata) || []).map((nrt) => nrt?.trim()).filter((nrt) => nrt) @@ -227,5 +247,6 @@ module.exports.parseOpfMetadataJson = (json) => { module.exports.parseOpfMetadataXML = async (xml) => { const json = await xmlToJSON(xml) if (!json) return null + return this.parseOpfMetadataJson(json) } diff --git a/test/server/utils/parsers/parseOpfMetadata.test.js b/test/server/utils/parsers/parseOpfMetadata.test.js index ca033cca..32dae922 100644 --- a/test/server/utils/parsers/parseOpfMetadata.test.js +++ b/test/server/utils/parsers/parseOpfMetadata.test.js @@ -3,8 +3,8 @@ const expect = chai.expect const { parseOpfMetadataXML } = require('../../../../server/utils/parsers/parseOpfMetadata') describe('parseOpfMetadata - test series', async () => { - it('test one series', async () => { - const opf = ` + it('test one series', async () => { + const opf = ` @@ -13,12 +13,12 @@ describe('parseOpfMetadata - test series', async () => { ` - const parsedOpf = await parseOpfMetadataXML(opf) - expect(parsedOpf.series).to.deep.equal([{ "name": "Serie", "sequence": "1" }]) - }) + const parsedOpf = await parseOpfMetadataXML(opf) + expect(parsedOpf.series).to.deep.equal([{ name: 'Serie', sequence: '1' }]) + }) - it('test more then 1 series - in correct order', async () => { - const opf = ` + it('test more then 1 series - in correct order', async () => { + const opf = ` @@ -31,16 +31,16 @@ describe('parseOpfMetadata - test series', async () => { ` - const parsedOpf = await parseOpfMetadataXML(opf) - expect(parsedOpf.series).to.deep.equal([ - { "name": "Serie 1", "sequence": "1" }, - { "name": "Serie 2", "sequence": "2" }, - { "name": "Serie 3", "sequence": "3" }, - ]) - }) + const parsedOpf = await parseOpfMetadataXML(opf) + expect(parsedOpf.series).to.deep.equal([ + { name: 'Serie 1', sequence: '1' }, + { name: 'Serie 2', sequence: '2' }, + { name: 'Serie 3', sequence: '3' } + ]) + }) - it('test messed order of series content and index', async () => { - const opf = ` + it('test messed order of series content and index', async () => { + const opf = ` @@ -52,15 +52,15 @@ describe('parseOpfMetadata - test series', async () => { ` - const parsedOpf = await parseOpfMetadataXML(opf) - expect(parsedOpf.series).to.deep.equal([ - { "name": "Serie 1", "sequence": "1" }, - { "name": "Serie 3", "sequence": null }, - ]) - }) + const parsedOpf = await parseOpfMetadataXML(opf) + expect(parsedOpf.series).to.deep.equal([ + { name: 'Serie 1', sequence: '1' }, + { name: 'Serie 3', sequence: null } + ]) + }) - it('test different values of series content and index', async () => { - const opf = ` + it('test different values of series content and index', async () => { + const opf = ` @@ -73,16 +73,16 @@ describe('parseOpfMetadata - test series', async () => { ` - const parsedOpf = await parseOpfMetadataXML(opf) - expect(parsedOpf.series).to.deep.equal([ - { "name": "Serie 1", "sequence": null }, - { "name": "Serie 2", "sequence": "abc" }, - { "name": "Serie 3", "sequence": null }, - ]) - }) + const parsedOpf = await parseOpfMetadataXML(opf) + expect(parsedOpf.series).to.deep.equal([ + { name: 'Serie 1', sequence: null }, + { name: 'Serie 2', sequence: 'abc' }, + { name: 'Serie 3', sequence: null } + ]) + }) - it('test empty series content', async () => { - const opf = ` + it('test empty series content', async () => { + const opf = ` @@ -91,12 +91,12 @@ describe('parseOpfMetadata - test series', async () => { ` - const parsedOpf = await parseOpfMetadataXML(opf) - expect(parsedOpf.series).to.deep.equal([]) - }) + const parsedOpf = await parseOpfMetadataXML(opf) + expect(parsedOpf.series).to.deep.equal([]) + }) - it('test series and index using an xml namespace', async () => { - const opf = ` + it('test series and index using an xml namespace', async () => { + const opf = ` @@ -105,14 +105,12 @@ describe('parseOpfMetadata - test series', async () => { ` - const parsedOpf = await parseOpfMetadataXML(opf) - expect(parsedOpf.series).to.deep.equal([ - { "name": "Serie 1", "sequence": null } - ]) - }) + const parsedOpf = await parseOpfMetadataXML(opf) + expect(parsedOpf.series).to.deep.equal([{ name: 'Serie 1', sequence: null }]) + }) - it('test series and series index not directly underneath', async () => { - const opf = ` + it('test series and series index not directly underneath', async () => { + const opf = ` @@ -122,9 +120,21 @@ describe('parseOpfMetadata - test series', async () => { ` - const parsedOpf = await parseOpfMetadataXML(opf) - expect(parsedOpf.series).to.deep.equal([ - { "name": "Serie 1", "sequence": "1" } - ]) - }) + const parsedOpf = await parseOpfMetadataXML(opf) + expect(parsedOpf.series).to.deep.equal([{ name: 'Serie 1', sequence: '1' }]) + }) + + it('test author is parsed from refines meta', async () => { + const opf = ` + + + Nevil Shute + aut + Shute, Nevil + + + ` + const parsedOpf = await parseOpfMetadataXML(opf) + expect(parsedOpf.authors).to.deep.equal(['Nevil Shute']) + }) })