UNPKG

epub-cfi-generator

Version:

Generates CFI for each text node in each spine for given EPUB

156 lines (134 loc) 4.71 kB
const fs = require('fs'); const path = require('path'); const xmldom = require('@xmldom/xmldom'); const AdmZip = require('adm-zip'); const tmp = require('tmp'); const xpathUtils = require('./readium-cfi/xpathUtils'); const cfiGenerator = require('./readium-cfi/cfi_generator').Generator; const { DEBUG } = process.env; (() => { const domParser = new xmldom.DOMParser(); function validateSingleNode(nodes) { if (nodes.length !== 1) { if (DEBUG) { nodes.forEach(console.log); } throw new Error(`Ambiguous nodes, size: ${nodes.length}`); } return nodes[0]; } function getPackageFilePath(epubPath) { return new Promise((resolve, reject) => { const filePath = path.join(epubPath, 'META-INF/container.xml'); fs.readFile(filePath, 'utf8', (err, data) => { try { if (err) { throw new Error(`Error reading container.xml file ${filePath}: ${err}`); } resolve(path.join(epubPath, validateSingleNode(xpathUtils.nsXPath( xpathUtils.NS_CONTAINER, '//main:rootfile', domParser.parseFromString(data) )).getAttribute('full-path'))); } catch (e) { reject(e); } }); }); } function getSpinesInfo(packageFilePath) { const rootDir = path.dirname(packageFilePath); return new Promise((resolve, reject) => { fs.readFile(packageFilePath, 'utf8', (err, data) => { try { if (err) { throw new Error(`Error reading package file ${packageFilePath}: ${err}`); } const doc = domParser.parseFromString(data); const itemrefs = xpathUtils.nsXPath(xpathUtils.NS_PACKAGE_DOC, '//main:spine//main:itemref', doc); if (DEBUG) { console.log(`spines found: ${itemrefs.length}`); } resolve(itemrefs.map((itemref) => { const item = validateSingleNode(xpathUtils.nsXPath( xpathUtils.NS_PACKAGE_DOC, `//main:item[@id="${itemref.getAttribute('idref')}"]`, doc )); return { spineCfi: cfiGenerator.createCFIElementSteps(itemref, 'package', null, null, null), idref: itemref.getAttribute('idref'), href: item.getAttribute('href'), path: `${rootDir}/${item.getAttribute('href')}` }; })); } catch (e) { reject(e); } }); }); } function getSpineNodesCfi(spineFilePath, spineNodeCfi) { return new Promise((resolve, reject) => { fs.readFile(spineFilePath, 'utf8', (err, data) => { try { if (err) { throw new Error(`Error reading spine file ${spineFilePath}: ${err}`); } const doc = domParser.parseFromString(data); const textNodes = xpathUtils.htmlXPath('/main:html/main:body//text()[normalize-space()]', doc); if (DEBUG) { console.log(`spine file ${spineFilePath}, length: ${data.length}, number of text nodes: ${textNodes.length}`); } resolve(textNodes.map((node) => { const cfi = cfiGenerator.generateCharacterOffsetCFIComponent(node, null, null, null); return { node: node.toString(), cfi: `${spineNodeCfi}!${cfi}` }; })); } catch (e) { reject(e); } }); }); } function EpubCfiGenerator() { if (!(this instanceof EpubCfiGenerator)) { return new EpubCfiGenerator(); } } EpubCfiGenerator.prototype.parse = (inputPath) => new Promise((resolve, reject) => { fs.lstat(inputPath, (err, stats) => { if (!err) { resolve(stats); } else { reject(err); } }); }).then((stats) => { if (!stats.isDirectory()) { tmp.setGracefulCleanup(); const tmpDirObj = tmp.dirSync({}); const zip = new AdmZip(inputPath); zip.extractAllTo(tmpDirObj.name.toString(), true); return tmpDirObj.name; } return inputPath; }).then(getPackageFilePath).then((packageFilePath) => getSpinesInfo(packageFilePath)) .then( (spinesInfo) => Promise.all( spinesInfo.map( (spineInfo) => getSpineNodesCfi(spineInfo.path, spineInfo.spineCfi).then((nodes) => { // eslint-disable-next-line no-param-reassign spineInfo.content = nodes; /* eslint-disable no-param-reassign */ delete spineInfo.path; delete spineInfo.spineCfi; /* eslint-enable */ }) ) ).then(() => spinesInfo) ); module.exports = EpubCfiGenerator; })();