nodepub
Version:
Create valid EPUB (v2) ebooks with metadata, contents and cover image.
201 lines (171 loc) • 8.66 kB
JavaScript
/* eslint-disable no-plusplus */
const path = require('path');
const replacements = require('./replacements');
const util = require('../utility.js');
const structural = {
// Provide the contents of the mimetype file (which should not be compressed).
getMimetype: () => 'application/epub+zip',
// Provide the contents of the container XML file.
getContainer: (document) => {
let result = '';
result += "[[EOL]]";
result += "<container version='1.0' xmlns='urn:oasis:names:tc:opendocument:xmlns:container'>[[EOL]]";
result += ' <rootfiles>[[EOL]]';
result += " <rootfile full-path='OEBPF/ebook.opf' media-type='application/oebps-package+xml'/>[[EOL]]";
result += ' </rootfiles>[[EOL]]';
result += '</container>';
return replacements(document, replacements(document, result));
},
// Provide the contents of the OPF (spine) file.
getOPF: (document) => {
const coverFilename = path.basename(document.coverImage);
let i;
let result = '';
result += "[[EOL]]";
result += "<package xmlns='http://www.idpf.org/2007/opf' version='2.0' unique-identifier='BookId'>[[EOL]]";
result += " <metadata xmlns:dc='http://purl.org/dc/elements/1.1/' xmlns:opf='http://www.idpf.org/2007/opf'>[[EOL]]";
if (document.metadata.series && document.metadata.sequence) {
result += ' <dc:title>[[TITLE]] ([[SERIES]] #[[SEQUENCE]])</dc:title>[[EOL]]';
} else if (document.metadata.series) {
result += ' <dc:title>[[TITLE]] ([[SERIES]])</dc:title>[[EOL]]';
} else if (document.metadata.sequence) {
result += ' <dc:title>[[TITLE]] (#[[SEQUENCE]])</dc:title>[[EOL]]';
} else {
result += ' <dc:title>[[TITLE]]</dc:title>[[EOL]]';
}
result += " <dc:identifier id='BookId' opf:scheme='URI'>[[ID]]</dc:identifier>[[EOL]]";
result += ' <dc:language>[[LANGUAGE]]</dc:language>[[EOL]]';
result += " <dc:creator opf:role='aut' opf:file-as='[[FILEAS]]'>[[AUTHOR]]</dc:creator>[[EOL]]";
result += ' <dc:publisher>[[PUBLISHER]]</dc:publisher>[[EOL]]';
result += ' <dc:description>[[DESCRIPTION]]</dc:description>[[EOL]]';
result += ' <dc:coverage></dc:coverage>[[EOL]]';
result += ' <dc:source>[[SOURCE]]</dc:source>[[EOL]]';
result += " <dc:date opf:event='publication'>[[PUBLISHED]]</dc:date>[[EOL]]";
result += " <dc:date opf:event='modification'>[[MODIFIED]]</dc:date>[[EOL]]";
result += ' <dc:rights>[[COPYRIGHT]]</dc:rights>[[EOL]]';
if (document.metadata.genre) {
result += ' <dc:subject>[[GENRE]]</dc:subject>[[EOL]]';
}
if (document.metadata.tags) {
const tags = document.metadata.tags.split(',');
for (i = 0; i < tags.length; i += 1) {
result += ` <dc:subject>${tags[i]}</dc:subject>[[EOL]]`;
}
}
if (document.metadata.series && document.metadata.sequence) {
result += " <meta name='calibre:series' content='[[SERIES]]'/>[[EOL]]";
result += " <meta name='calibre:series_index' content='[[SEQUENCE]]'/>[[EOL]]";
}
result += " <meta name='cover' content='cover-image'/>[[EOL]]";
result += ' </metadata>[[EOL]]';
result += ' <manifest>[[EOL]]';
result += ` <item id='cover-image' media-type='${util.getImageType(coverFilename)}' href='images/${coverFilename}'/>[[EOL]]`;
result += " <item id='cover' media-type='application/xhtml+xml' href='cover.xhtml'/>[[EOL]]";
result += " <item id='navigation' media-type='application/x-dtbncx+xml' href='navigation.ncx'/>[[EOL]]";
for (i = 1; i <= document.sections.length; i += 1) {
const fname = document.sections[i - 1].filename;
result += ` <item id='s${i}' media-type='application/xhtml+xml' href='content/${fname}'/>[[EOL]]`;
}
if (document.showContents) {
result += " <item id='toc' media-type='application/xhtml+xml' href='content/toc.xhtml'/>[[EOL]]";
}
result += " <item id='css' media-type='text/css' href='css/ebook.css'/>[[EOL]]";
if (document.metadata.images) {
for (i = 0; i < document.metadata.images.length; i += 1) {
const image = document.metadata.images[i];
const imageFile = path.basename(image);
const imageType = util.getImageType(image);
if (imageType.length > 0) {
result += ` <item id='img${i}' media-type='${imageType}' href='images/${imageFile}'/>[[EOL]]`;
}
}
}
result += ' </manifest>[[EOL]]';
result += " <spine toc='navigation'>[[EOL]]";
result += " <itemref idref='cover' linear='yes' />[[EOL]]";
for (i = 1; i <= document.sections.length; i += 1) {
if (document.sections[i - 1].isFrontMatter) {
result += ` <itemref idref='s${i}' />[[EOL]]`;
}
}
if (document.showContents) {
result += " <itemref idref='toc'/>[[EOL]]";
}
for (i = 1; i <= document.sections.length; i += 1) {
if (!(document.sections[i - 1].isFrontMatter)) {
result += ` <itemref idref='s${i}' />[[EOL]]`;
}
}
result += ' </spine>[[EOL]]';
if (document.showContents) {
result += ' <guide>[[EOL]]';
result += " <reference type='toc' title='Contents' href='content/toc.xhtml'></reference>[[EOL]]";
result += ' </guide>[[EOL]]';
}
result += '</package>[[EOL]]';
return replacements(document, replacements(document, result));
},
// Provide the contents of the NCX file.
getNCX: (document) => {
let i;
let title;
let order;
let result = '';
let playOrder = 1;
result += "[[EOL]]";
result += "[[EOL]]";
result += "<ncx xmlns='http://www.daisy.org/z3986/2005/ncx/'>[[EOL]]";
result += '<head>[[EOL]]';
result += " <meta name='dtb:uid' content='[[ID]]'/>[[EOL]]";
result += " <meta name='dtb:depth' content='1'/>[[EOL]]";
result += " <meta name='dtb:totalPageCount' content='0'/>[[EOL]]";
result += " <meta name='dtb:maxPageNumber' content='0'/>[[EOL]]";
result += '</head>[[EOL]]';
result += '<docTitle><text>[[TITLE]]</text></docTitle>[[EOL]]';
result += '<docAuthor><text>[[AUTHOR]]</text></docAuthor>[[EOL]]';
result += '<navMap>[[EOL]]';
result += ` <navPoint id='cover' playOrder='${playOrder++}'>[[EOL]]`;
result += ' <navLabel><text>Cover</text></navLabel>[[EOL]]';
result += " <content src='cover.xhtml'/>[[EOL]]";
result += ' </navPoint>[[EOL]]';
for (i = 1; i <= document.sections.length; i += 1) {
if (!(document.sections[i - 1].excludeFromContents)) {
if (document.sections[i - 1].isFrontMatter) {
const fname = document.sections[i - 1].filename;
title = document.sections[i - 1].title;
order = playOrder++;
document.filesForTOC.push({ title, link: `${fname}`, itemType: 'front' });
result += ` <navPoint class='section' id='s${i}' playOrder='${order}'>[[EOL]]`;
result += ` <navLabel><text>${title}</text></navLabel>[[EOL]]`;
result += ` <content src='content/${fname}'/>[[EOL]]`;
result += ' </navPoint>[[EOL]]';
}
}
}
if (document.showContents) {
document.filesForTOC.push({ title: document.metadata.contents, link: 'toc.xhtml', itemType: 'contents' });
result += ` <navPoint class='toc' id='toc' playOrder='${playOrder++}'>[[EOL]]`;
result += ' <navLabel><text>[[CONTENTS]]</text></navLabel>[[EOL]]';
result += " <content src='content/toc.xhtml'/>[[EOL]]";
result += ' </navPoint>[[EOL]]';
}
for (i = 1; i <= document.sections.length; i += 1) {
if (!(document.sections[i - 1].excludeFromContents)) {
if (!document.sections[i - 1].isFrontMatter) {
const fname = document.sections[i - 1].filename;
title = document.sections[i - 1].title;
order = playOrder++;
document.filesForTOC.push({ title, link: `${fname}`, itemType: 'main' });
result += ` <navPoint class='section' id='s${i}' playOrder='${order}'>[[EOL]]`;
result += ` <navLabel><text>${title}</text></navLabel>[[EOL]]`;
result += ` <content src='content/${fname}'/>[[EOL]]`;
result += ' </navPoint>[[EOL]]';
}
}
}
result += '</navMap>[[EOL]]';
result += '</ncx>[[EOL]]';
return replacements(document, replacements(document, result));
},
};
module.exports = structural;