From 71da5a43e94d88da0792b40859532400287b2f22 Mon Sep 17 00:00:00 2001 From: Steve Ridout Date: Wed, 3 Dec 2014 10:57:07 +0100 Subject: [PATCH 1/7] fix bug where we were trying to iterate through undefined 'navPoints' variable --- lib/epub-parser.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/epub-parser.js b/lib/epub-parser.js index c29b05d..0c006fa 100644 --- a/lib/epub-parser.js +++ b/lib/epub-parser.js @@ -231,8 +231,10 @@ EpubParser = (function() { var navPoints = ncx[ncxPrefix+"navMap"][0][ncxPrefix+"navPoint"]; - for(var i = 0; i < navPoints.length; i++) { - processNavPoint(navPoints[i]); + if (navPoints) { + for(var i = 0; i < navPoints.length; i++) { + processNavPoint(navPoints[i]); + } } htmlNav += ''+"\n"; epubdata = getEpubDataBlock(); @@ -545,4 +547,4 @@ EpubParser = (function() { }; })(); -module.exports = EpubParser; \ No newline at end of file +module.exports = EpubParser; From a6d8e3dbdb1bb4036d737aabbff40dcf5d40864e Mon Sep 17 00:00:00 2001 From: Steve Ridout Date: Tue, 4 Aug 2015 11:34:54 +0200 Subject: [PATCH 2/7] code formatting --- lib/epub-parser.js | 910 +++++++++++++++++++++------------------------ 1 file changed, 422 insertions(+), 488 deletions(-) diff --git a/lib/epub-parser.js b/lib/epub-parser.js index 6b26dea..410ef67 100644 --- a/lib/epub-parser.js +++ b/lib/epub-parser.js @@ -1,557 +1,491 @@ var EpubParser; -//var sax = require('./sax'); - EpubParser = (function() { + var jszip = require('node-zip'); + var zip, zipEntries; + var xml2js = require('xml2js'); + var parser = new xml2js.Parser(); + var request = require('request'); + var fs = require('fs'); + + function extractText(filename) { + var file = zip.file(filename); + if (typeof file !== 'undefined' || file !== null) { + return file.asText(); + } else { + throw 'file ' + filename + ' not found in zip'; + } + } - - var jszip = require('node-zip'); - var zip, zipEntries; - var xml2js = require('xml2js'); - var parser = new xml2js.Parser(); - var request = require('request'); - var fs = require('fs'); - - function extractText(filename) { - //console.log('extracting '+filename); - var file = zip.file(filename); - if(typeof file !== 'undefined' || file !== null) { - return file.asText(); - } else { - throw 'file '+filename+' not found in zip'; - } - } - - function extractBinary(filename) { - var file = zip.file(filename); - if(typeof file !== 'undefined') { - return file.asBinary(); - } else { - return ''; - } - } + function extractBinary(filename) { + var file = zip.file(filename); + if (typeof file !== 'undefined') { + return file.asBinary(); + } else { + return ''; + } + } function safeAccess(supposedArray) { // a quick bandaid to handle undefined lists // coming back from the parser - poor fix TODO - if(typeof supposedArray === 'undefined') { + if (typeof supposedArray === 'undefined') { return new Array(); } else { return supposedArray; } } - function open(filename, cb) { - - - /* - - "filename" is still called "filename" but now it can be - a full file path, a full URL, or a Buffer object - we should eventually change its name... - - */ - - - var epubdata = {}; - var md5hash; - var htmlNav = ''+"\n"; - epubdata = getEpubDataBlock(); - cb(null,epubdata); + var id = itemreflist[itemref].$.idref; - }); - } - }); - } + spineOrder.push(itemreflist[itemref].$); + if (itemreflist[itemref].$.linear == 'yes' || typeof itemreflist[itemref].$.linear == 'undefined') { + itemreflist[itemref].$.item = itemHashById[id]; + linearSpine[id] = itemreflist[itemref].$; + } - function processNavPoint(np) { + } + } - var text = 'Untitled'; - var src = "#"; + function buildMetadataLists() { + var metas = metadata; + for (prop in metas) { + + if (prop == 'meta') { // process a list of meta tags + + for (var i = 0; i < safeAccess(metas[prop]).length; i++) { + + var m = metas[prop][i].$; + + if (typeof m.name !== 'undefined') { + var md = {}; + md[m.name] = m.content; + simpleMeta.push(md); + } else if (typeof m.property !== 'undefined') { + var md = {}; + md[m.property] = metas[prop][i]._; + simpleMeta.push(md); + } + + if (m.name == 'cover') { + if (typeof itemHashById[m.content] !== 'undefined') { + epub2CoverUrl = opsRoot + itemHashById[m.content].$.href; + } + } + + } + + } else if (prop != '$') { + + var content = ''; + var atts = {}; + if (metas[prop][0]) { + if (metas[prop][0].$ || metas[prop][0]._) { // complex tag + content = (metas[prop][0]._) ? + metas[prop][0]._ : + metas[prop][0]; + + if (metas[prop][0].$) { // has attributes + for (att in metas[prop][0].$) { + atts[att] = metas[prop][0].$[att]; + } + } + + } else { // simple one, if object, assume empty + content = (typeof metas[prop][0] == 'object') ? '' : metas[prop][0]; + } + } + if (typeof prop !== 'undefined') { + var md = {}; + md[prop] = content; + simpleMeta.push(md); + } + + if (prop.match(/identifier$/i)) { + if (typeof metas[prop][0].$.id) { + if (metas[prop][0].$.id == uniqueIdentifier) { + if (typeof content == 'object') { + console.log('warning - content not fully parsed'); + console.log(content); + console.log(metas[prop][0].$.id); + + } else { + uniqueIdentifierValue = content; + if (typeof metas[prop][0].$.scheme !== 'undefined') { + uniqueIdentifierScheme = metas[prop][0].$.scheme; + } + } + + } + }; + } + } + } + } - if(typeof np.navLabel !== 'undefined') { - text = np.navLabel[0].text[0]; - } - if(typeof np.content !== 'undefined') { - src = np.content[0]["$"].src; - } + function parsePackageElements() { + // operates on global vars + + if (typeof opf[opfPrefix + "manifest"] === 'undefined') { + // it's a problem + // gutenberg files, for example will lead to this condition + // we must assume that tags are not actually namespaced + + opfPrefix = ''; + } + + try { + metadata = opf[opfPrefix + "metadata"][0]; + } catch (e) { + console.log('metadata element error: ' + e.message); + console.log('are the tags really namespaced with ' + opfPrefix + ' or not? file indicates they should be.'); + } + try { + manifest = opf[opfPrefix + "manifest"][0]; + } catch (e) { + console.log('manifest element error: ' + e.message); + console.log('are the tags really namespaced with ' + opfPrefix + ' or not? file indicates they should be.'); + console.log(opfDataXML); + console.log(opf); + console.log('must throw this - unrecoverable'); + throw (e); + } + try { + spine = opf[opfPrefix + "spine"][0]; + } catch (e) { + console.log('spine element error: ' + e.message); + console.log('must throw this'); + throw (e); + } + try { + guide = opf[opfPrefix + "guide"][0]; + } catch (e) {; + } + } - htmlNav += '
  • '+text+''; + function getEpubDataBlock() { + return { + easy: { + primaryID: { + name: uniqueIdentifier, + value: uniqueIdentifierValue, + scheme: uniqueIdentifierScheme + }, + epubVersion: epubVersion, + isEpub3: isEpub3, + md5: md5hash, + epub3NavHtml: epub3NavHtml, + navMapHTML: htmlNav, + linearSpine: linearSpine, + itemHashById: itemHashById, + itemHashByHref: itemHashByHref, + linearSpine: linearSpine, + simpleMeta: simpleMeta, + epub3CoverId: epub3CoverId, + epub3NavId: epub3NavId, + epub2CoverUrl: epub2CoverUrl + }, + paths: { + opfPath: opfPath, + ncxPath: ncxPath, + opsRoot: opsRoot + }, + raw: { + json: { + prefixes: { + opfPrefix: opfPrefix, + dcPrefix: dcPrefix, + ncxPrefix: ncxPrefix + }, + container: container, + opf: opf, + ncx: ncx, + nav: nav + }, + xml: { + opfXML: opfDataXML, + ncxXML: ncxDataXML + } + } + }; + } - if(typeof np.navPoint !== 'undefined') { - htmlNav += '
      '; - for(var i = 0; i < safeAccess(np.navPoint).length; i++) { - processNavPoint(np.navPoint[i]); - } - htmlNav += '
    '+"\n"; - - } - htmlNav += '
  • '+"\n"; - } - - - - function buildItemHashes() { - - for(item in itemlist) { - - var href = itemlist[item].$.href; - var id = itemlist[item].$.id; - var mediaType = itemlist[item].$['media-type']; - var properties = itemlist[item].$['properties']; - if(typeof properties !== 'undefined') { - if(properties == 'cover-image') { - epub3CoverId = id; - } else if (properties == 'nav') { - epub3NavId = id; - epub3NavHtml = extractText(opsRoot+href); - } - } - itemHashByHref[href] = itemlist[item]; - itemHashById[id] = itemlist[item]; - - } - var itemrefs = itemreflist; - - try { - ncxId = spine.$.toc; - } catch(e) { - ; - } - - } - - function buildLinearSpine() { - for(itemref in itemreflist) { - - var id = itemreflist[itemref].$.idref; - - spineOrder.push(itemreflist[itemref].$); - - if(itemreflist[itemref].$.linear=='yes' || typeof itemreflist[itemref].$.linear == 'undefined') { - itemreflist[itemref].$.item = itemHashById[id]; - linearSpine[id] = itemreflist[itemref].$; - } - - } - } - - function buildMetadataLists() { - var metas = metadata; - for(prop in metas) { - - if(prop == 'meta') { // process a list of meta tags - - - for(var i = 0; i < safeAccess(metas[prop]).length; i++) { - - var m = metas[prop][i].$; - - if(typeof m.name !== 'undefined') { - var md = {}; - md[m.name] = m.content; - simpleMeta.push(md); - } else if (typeof m.property !== 'undefined') { - var md = {}; - md[m.property] = metas[prop][i]._; - simpleMeta.push(md); - } - - if(m.name == 'cover') { - if (typeof itemHashById[m.content] !== 'undefined') { - epub2CoverUrl = opsRoot + itemHashById[m.content].$.href; - } - } - - - } - - - - } else if(prop != '$') { - - var content = ''; - var atts = {}; - if(metas[prop][0]) { - if(metas[prop][0].$ || metas[prop][0]._) { // complex tag - content = (metas[prop][0]._) ? - metas[prop][0]._ : - metas[prop][0]; - - if(metas[prop][0].$) { // has attributes - for(att in metas[prop][0].$) { - atts[att]=metas[prop][0].$[att]; - } - } - - } else { // simple one, if object, assume empty - content = (typeof metas[prop][0] == 'object') ? '' : metas[prop][0]; - } - } - if(typeof prop !== 'undefined') { - var md = {}; - md[prop] = content; - simpleMeta.push(md); - } - - if(prop.match(/identifier$/i)) { - if(typeof metas[prop][0].$.id) { - if(metas[prop][0].$.id==uniqueIdentifier) { - if(typeof content == 'object') { - console.log('warning - content not fully parsed'); - console.log(content); - console.log(metas[prop][0].$.id); - - - } else { - uniqueIdentifierValue = content; - if(typeof metas[prop][0].$.scheme !== 'undefined') { - uniqueIdentifierScheme= metas[prop][0].$.scheme; - } - } - - } - }; - } - - } - - } - } - - function parsePackageElements() { - - // operates on global vars - - - if(typeof opf[opfPrefix+"manifest"] === 'undefined') { - - // it's a problem - // gutenberg files, for example will lead to this condition - // we must assume that tags are not actually namespaced - - opfPrefix = ''; - } - - try { - metadata = opf[opfPrefix+"metadata"][0]; - } catch(e) { - console.log('metadata element error: '+e.message); - console.log('are the tags really namespaced with '+opfPrefix+' or not? file indicates they should be.'); - } - try { - manifest = opf[opfPrefix+"manifest"][0]; - } catch (e) { - console.log('manifest element error: '+e.message); - console.log('are the tags really namespaced with '+opfPrefix+' or not? file indicates they should be.'); - console.log(opfDataXML); - console.log(opf); - console.log('must throw this - unrecoverable'); - throw (e); - } - try { - spine = opf[opfPrefix+"spine"][0]; - } catch(e) { - console.log('spine element error: '+e.message); - console.log('must throw this'); - throw (e); - } - try { - guide = opf[opfPrefix+"guide"][0]; - } catch (e) { - ; - } - } - - function getEpubDataBlock() - { - return { - easy: { - primaryID: { - name:uniqueIdentifier, - value:uniqueIdentifierValue, - scheme:uniqueIdentifierScheme - }, - epubVersion: epubVersion, - isEpub3: isEpub3, - md5: md5hash, - epub3NavHtml: epub3NavHtml, - navMapHTML: htmlNav, - linearSpine: linearSpine, - itemHashById: itemHashById, - itemHashByHref: itemHashByHref, - linearSpine: linearSpine, - simpleMeta: simpleMeta, - epub3CoverId: epub3CoverId, - epub3NavId: epub3NavId, - epub2CoverUrl: epub2CoverUrl - }, - paths: { - opfPath: opfPath, - ncxPath: ncxPath, - opsRoot: opsRoot - }, - raw: { - json: { - prefixes: { - opfPrefix:opfPrefix, - dcPrefix:dcPrefix, - ncxPrefix:ncxPrefix - }, - container: container, - opf: opf, - ncx: ncx, - nav: nav - }, - xml: { - opfXML: opfDataXML, - ncxXML: ncxDataXML - } - } - }; - } - - - if(Buffer.isBuffer(filename)) { - - console.log('epub-parser parsing from buffer, not file'); - - readAndParseData(filename, cb); - - } else if(filename.match(/^https?:\/\//i)) { // is a URL - - request({ - uri:filename, - encoding:null /* sets the response to be a buffer */ - }, function (error, response, body) { - - - if (!error && response.statusCode == 200) { - - var b = body; - - readAndParseData(b, cb); - - } else { - - cb(error,null); - - } - - }); - - - } else { // assume local full path to file - - fs.readFile(filename, 'binary', function (err, data) { - - if(err) return cb(err); - - readAndParseData(data, cb); - - }); - - } - - } // end #open function definition block - - - return { - open:open, - getZip:function () { return zip; }, - getJsZip: function () { return jszip; }, - extractBinary: extractBinary, - extractText: extractText - }; + if (Buffer.isBuffer(filename)) { + console.log('epub-parser parsing from buffer, not file'); + readAndParseData(filename, cb); + } else if (filename.match(/^https?:\/\//i)) { // is a URL + request({ + uri: filename, + encoding: null // sets the response to be a buffer + }, function(error, response, body) { + if (!error && response.statusCode == 200) { + var b = body; + readAndParseData(b, cb); + } else { + cb(error, null); + } + }); + + } else { // assume local full path to file + fs.readFile(filename, 'binary', function(err, data) { + if (err) return cb(err); + readAndParseData(data, cb); + }); + } + } // end #open function definition block + + return { + open: open, + getZip: function() { + return zip; + }, + getJsZip: function() { + return jszip; + }, + extractBinary: extractBinary, + extractText: extractText + }; })(); + module.exports = EpubParser; From fe535218fe450bb6c59c2438a468e9d52ac3cffd Mon Sep 17 00:00:00 2001 From: Steve Ridout Date: Tue, 4 Aug 2015 11:38:36 +0200 Subject: [PATCH 3/7] Fix for epub which used "opf:" prefix for only one of the item elements --- lib/epub-parser.js | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/lib/epub-parser.js b/lib/epub-parser.js index 410ef67..df506cb 100644 --- a/lib/epub-parser.js +++ b/lib/epub-parser.js @@ -173,11 +173,20 @@ EpubParser = (function() { } else { // epub 2, use ncx doc - for (item in manifest[opfPrefix + "item"]) { - if (manifest[opfPrefix + "item"][item]["$"].id == ncxId) { - ncxPath = opsRoot + manifest[opfPrefix + "item"][item]["$"].href; + var allItems = manifest[opfPrefix+"item"]; + + // Fix for epub which used "opf:" prefix for only one of the item elements: + if (opfPrefix !== 'opf' && manifest['opf:item']) { + allItems = allItems.concat(manifest['opf:item']); + } + + for (itemKey in allItems) { + var item = allItems[itemKey]; + if(item["$"].id == ncxId) { + ncxPath = opsRoot + item["$"].href; } } + //console.log('determined ncxPath:'+ncxPath); ncxDataXML = extractText(ncxPath); From e9a3c87c8c33c985ee13e161e9963e2a56c78ad1 Mon Sep 17 00:00:00 2001 From: Steve Ridout Date: Tue, 4 Aug 2015 11:38:59 +0200 Subject: [PATCH 4/7] code cleanup --- lib/epub-parser.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/epub-parser.js b/lib/epub-parser.js index df506cb..98849d7 100644 --- a/lib/epub-parser.js +++ b/lib/epub-parser.js @@ -187,7 +187,6 @@ EpubParser = (function() { } } - //console.log('determined ncxPath:'+ncxPath); ncxDataXML = extractText(ncxPath); parser.parseString(ncxDataXML.toString(), function(err, ncxJSON) { @@ -196,7 +195,6 @@ EpubParser = (function() { function setPrefix(ncxJSON) { for (att in ncxJSON["$"]) { - //console.log(att); if (att.match(/^xmlns\:/)) { var ns = att.replace(/^xmlns\:/, ''); if (ncxJSON["$"][att] == 'http://www.daisy.org/z3986/2005/ncx/') ncxPrefix = ns + ':'; @@ -205,7 +203,6 @@ EpubParser = (function() { } // grab the correct ns prefix for ncx - for (prop in ncxJSON) { if (prop === '$') { // normal parse result setPrefix(ncxJSON); From 1c254978a7e211451619a6a6fe945c6518af7f62 Mon Sep 17 00:00:00 2001 From: Steve Ridout Date: Fri, 14 Aug 2015 10:48:37 +0200 Subject: [PATCH 5/7] handle epubs which put all their package files within a single root directory --- lib/epub-parser.js | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/lib/epub-parser.js b/lib/epub-parser.js index 98849d7..d25bd11 100644 --- a/lib/epub-parser.js +++ b/lib/epub-parser.js @@ -8,8 +8,10 @@ EpubParser = (function() { var request = require('request'); var fs = require('fs'); + var zipFilesPrefix = ''; + function extractText(filename) { - var file = zip.file(filename); + var file = zip.file(zipFilesPrefix + filename); if (typeof file !== 'undefined' || file !== null) { return file.asText(); } else { @@ -18,7 +20,7 @@ EpubParser = (function() { } function extractBinary(filename) { - var file = zip.file(filename); + var file = zip.file(zipFilesPrefix + filename); if (typeof file !== 'undefined') { return file.asBinary(); } else { @@ -36,6 +38,22 @@ EpubParser = (function() { } } + // some packages contain one root folder which contains META-INF/ and all the other + // stuff we're interested in, detect this and set the zipFilesPrefix accordingly + function detectZipFilesPrefix() { + var uniqueRootFolders = {}; + var filePaths = Object.keys(zip.files); + + for(var i = 0; i < filePaths.length; i++) { + uniqueRootFolders[filePaths[i].split('/')[0]] = true; + } + var uniqueRootFoldersList = Object.keys(uniqueRootFolders); + + if (uniqueRootFoldersList.length === 1) { + zipFilesPrefix = uniqueRootFoldersList[0] + '/'; + } + } + function open(filename, cb) { // "filename" is still called "filename" but now it can be // a full file path, a full URL, or a Buffer object @@ -71,6 +89,7 @@ EpubParser = (function() { base64: false, checkCRC32: true }); + detectZipFilesPrefix(); var containerData = extractText('META-INF/container.xml'); parseEpub(containerData, function(err, epubData) { From c0df6db585bf30d1f607b156aee402291cd801a3 Mon Sep 17 00:00:00 2001 From: steveridout Date: Tue, 14 Mar 2023 19:37:14 +0100 Subject: [PATCH 6/7] Fix bug where $ was undefined --- lib/epub-parser.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/epub-parser.js b/lib/epub-parser.js index d25bd11..152524a 100644 --- a/lib/epub-parser.js +++ b/lib/epub-parser.js @@ -367,7 +367,7 @@ EpubParser = (function() { } if (prop.match(/identifier$/i)) { - if (typeof metas[prop][0].$.id) { + if (metas[prop][0].$ && typeof metas[prop][0].$.id) { if (metas[prop][0].$.id == uniqueIdentifier) { if (typeof content == 'object') { console.log('warning - content not fully parsed'); From e3cb30d37de70c8dbd89574d5705cfb603770e85 Mon Sep 17 00:00:00 2001 From: steveridout Date: Wed, 15 Mar 2023 11:34:11 +0100 Subject: [PATCH 7/7] Bump version number, see if that helps "npm install" pull the latest version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 61ac87a..baddab5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "epub-parser", - "version": "0.2.5", + "version": "0.2.6", "description": "Epub parser", "keywords": [ "epub"