mirror of
				https://github.com/advplyr/audiobookshelf.git
				synced 2025-11-01 19:07:02 -04:00 
			
		
		
		
	Merge pull request #4113 from advplyr/parsing-opf-v3
Update opf parser to support refines meta elements
This commit is contained in:
		
						commit
						607f143861
					
				| @ -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() | ||||
| module.exports = new OpfFileScanner() | ||||
|  | ||||
| @ -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) | ||||
| } | ||||
|  | ||||
| @ -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 = ` | ||||
|             <?xml version='1.0' encoding='UTF-8'?> | ||||
|             <package xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:opf="http://www.idpf.org/2007/opf" xml:lang="en" version="3.0" unique-identifier="bookid"> | ||||
|               <metadata> | ||||
| @ -13,12 +13,12 @@ describe('parseOpfMetadata - test series', async () => { | ||||
|               </metadata> | ||||
|             </package> | ||||
|         ` | ||||
|         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 = ` | ||||
|             <?xml version='1.0' encoding='UTF-8'?> | ||||
|             <package xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:opf="http://www.idpf.org/2007/opf" xml:lang="en" version="3.0" unique-identifier="bookid"> | ||||
|               <metadata> | ||||
| @ -31,16 +31,16 @@ describe('parseOpfMetadata - test series', async () => { | ||||
|               </metadata> | ||||
|             </package> | ||||
|         ` | ||||
|         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 = ` | ||||
|             <?xml version='1.0' encoding='UTF-8'?> | ||||
|             <package xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:opf="http://www.idpf.org/2007/opf" xml:lang="en" version="3.0" unique-identifier="bookid"> | ||||
|               <metadata> | ||||
| @ -52,15 +52,15 @@ describe('parseOpfMetadata - test series', async () => { | ||||
|               </metadata> | ||||
|             </package> | ||||
|         ` | ||||
|         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 = ` | ||||
|             <?xml version='1.0' encoding='UTF-8'?> | ||||
|             <package xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:opf="http://www.idpf.org/2007/opf" xml:lang="en" version="3.0" unique-identifier="bookid"> | ||||
|               <metadata> | ||||
| @ -73,16 +73,16 @@ describe('parseOpfMetadata - test series', async () => { | ||||
|               </metadata> | ||||
|             </package> | ||||
|         ` | ||||
|         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 = ` | ||||
|             <?xml version='1.0' encoding='UTF-8'?> | ||||
|             <package xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:opf="http://www.idpf.org/2007/opf" xml:lang="en" version="3.0" unique-identifier="bookid"> | ||||
|               <metadata> | ||||
| @ -91,12 +91,12 @@ describe('parseOpfMetadata - test series', async () => { | ||||
|               </metadata> | ||||
|             </package> | ||||
|         ` | ||||
|         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 = ` | ||||
|             <?xml version='1.0' encoding='UTF-8'?> | ||||
|             <ns0:package xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:opf="http://www.idpf.org/2007/opf" xml:lang="en" version="3.0" unique-identifier="bookid"> | ||||
|               <ns0:metadata> | ||||
| @ -105,14 +105,12 @@ describe('parseOpfMetadata - test series', async () => { | ||||
|               </ns0:metadata> | ||||
|             </ns0:package> | ||||
|         ` | ||||
|         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 = ` | ||||
|             <?xml version='1.0' encoding='UTF-8'?> | ||||
|             <package xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:opf="http://www.idpf.org/2007/opf" xml:lang="en" version="3.0" unique-identifier="bookid"> | ||||
|               <metadata> | ||||
| @ -122,9 +120,21 @@ describe('parseOpfMetadata - test series', async () => { | ||||
|               </metadata> | ||||
|             </package> | ||||
|         ` | ||||
|         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 = ` | ||||
|         <package version="3.0" unique-identifier="uuid_id" prefix="rendition: http://www.idpf.org/vocab/rendition/#" xmlns="http://www.idpf.org/2007/opf"> | ||||
|           <metadata> | ||||
|             <dc:creator id="create1">Nevil Shute</dc:creator> | ||||
|             <meta refines="#create1" property="role" scheme="marc:relators">aut</meta> | ||||
|             <meta refines="#create1" property="file-as">Shute, Nevil</meta> | ||||
|           </metadata> | ||||
|         </package> | ||||
|       ` | ||||
|     const parsedOpf = await parseOpfMetadataXML(opf) | ||||
|     expect(parsedOpf.authors).to.deep.equal(['Nevil Shute']) | ||||
|   }) | ||||
| }) | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user