Generating epub.
This commit is contained in:
3
.vscode/launch.json
vendored
3
.vscode/launch.json
vendored
@@ -37,7 +37,8 @@
|
|||||||
"name": "Launch Node.JS",
|
"name": "Launch Node.JS",
|
||||||
"type": "node",
|
"type": "node",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"program": "${file}"
|
"program": "${workspaceFolder}/test/app.js"
|
||||||
|
// "program": "${file}"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
289
src/epub.js
289
src/epub.js
@@ -1,22 +1,267 @@
|
|||||||
|
var isNode=new Function("try {return this===global;}catch(e){return false;}");
|
||||||
|
if (isNode()){
|
||||||
|
var JSZip = require('jszip');
|
||||||
|
const { TextDecoder } = require('string_decoder');
|
||||||
|
var generatedBufferType = 'uint8array'
|
||||||
|
} else {
|
||||||
|
var generatedBufferType = 'uint8array'
|
||||||
|
}
|
||||||
|
|
||||||
|
class EpubXhtml{
|
||||||
|
constructor(filename) {
|
||||||
|
let name = filename.split('.')
|
||||||
|
name[name.length-1] = 'xhtml'
|
||||||
|
this.filename = name.join('.')
|
||||||
|
this.mime = 'application/xhtml+xml'
|
||||||
|
}
|
||||||
|
|
||||||
|
convert(content){
|
||||||
|
// var blob = new Blob([content],{type:'text/plain'});
|
||||||
|
// var reader = new FileReader();
|
||||||
|
// reader.onload = function(evt){callback(evt.target.result);};
|
||||||
|
// reader.readAsText(blob, encoding);
|
||||||
|
|
||||||
|
let cnt = `<?xml version="1.0" encoding="utf-8" standalone="no"?>
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html xmlns="http://www.w3.org/1999/xhtml"
|
||||||
|
xmlns:epub="http://www.idpf.org/2007/ops">
|
||||||
|
<head>
|
||||||
|
<title></title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
${new TextDecoder("utf-8").write(content)}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`
|
||||||
|
return cnt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class EpubOpf{
|
||||||
|
constructor(filename, meta) {
|
||||||
|
this.filename = filename
|
||||||
|
this.meta = meta
|
||||||
|
}
|
||||||
|
|
||||||
|
generate(){
|
||||||
|
let m = this.meta
|
||||||
|
let cnt = `<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<package xmlns="http://www.idpf.org/2007/opf"
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:dcterms="http://purl.org/dc/terms/"
|
||||||
|
unique-identifier="pub-id"
|
||||||
|
version="3.0">
|
||||||
|
<metadata>
|
||||||
|
<meta property="dcterms:modified">2013-03-22T12:24:00Z</meta>
|
||||||
|
<dc:rights>Please read the legal notice included in this e-book and/or check the copyright status in your country.</dc:rights>
|
||||||
|
<dc:identifier id="pub-id">${m.metadata.book_id}</dc:identifier>
|
||||||
|
<dc:title>${m.metadata.title}</dc:title>
|
||||||
|
<dc:language>${m.metadata.language}</dc:language>\n`
|
||||||
|
|
||||||
|
if (m.metadata.publisher){
|
||||||
|
cnt += ` <dc:publisher>${m.metadata.publisher}</dc:publisher>\n`
|
||||||
|
}
|
||||||
|
if (m.metadata.cover_image){
|
||||||
|
cnt += ` <meta content="cover-image" name="${m.metadata.cover_image}"/>\n`
|
||||||
|
}
|
||||||
|
// <dc:creator opf:file-as="Greatness, Georgia's" opf:role="aut">Lauren Baratz-Logsted</dc:creator>
|
||||||
|
|
||||||
|
cnt +=' </metadata>\n <manifest>\n'
|
||||||
|
|
||||||
|
for (let id in m.manifest){
|
||||||
|
let f = m.manifest[id]
|
||||||
|
if (f.hasOwnProperty('property')){
|
||||||
|
cnt += ` <item id="${f.id}" media-type="${f.mime}" href="${f.filename}" properties="${f.property}"/>\n`
|
||||||
|
}else{
|
||||||
|
cnt += ` <item id="${f.id}" media-type="${f.mime}" href="${f.filename}"/>\n`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cnt += ` </manifest>\n <spine toc="${m.getId(m.ncx().filename)}">\n`
|
||||||
|
for (let i in m.spine){
|
||||||
|
cnt += ` <itemref idref="${m.spine[i]}" />\n`
|
||||||
|
}
|
||||||
|
cnt += ' </spine>\n <guide>\n'
|
||||||
|
cnt += ` <reference href="${m.nav().filename}" title="Table of Contents" type="toc"/>\n`
|
||||||
|
cnt += ' </guide>\n</package>\n'
|
||||||
|
return cnt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class EpubNcx{
|
||||||
|
constructor(filename, meta) {
|
||||||
|
this.filename = filename
|
||||||
|
this.meta = meta
|
||||||
|
this.mime = 'application/x-dtbncx+xml'
|
||||||
|
}
|
||||||
|
|
||||||
|
generate(){
|
||||||
|
let m = this.meta
|
||||||
|
let cnt = `<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ncx xmlns="http://www.daisy.org/z3986/2005/ncx/" xml:lang="en" version="2005-1">
|
||||||
|
<head>
|
||||||
|
<meta name="dtb:uid" content="${m.metadata.book_id}"/>
|
||||||
|
<meta name="dtb:depth" content="2"/>
|
||||||
|
<meta name="dtb:totalPageCount" content="0"/>
|
||||||
|
<meta name="dtb:maxPageNumber" content="0"/>
|
||||||
|
</head>
|
||||||
|
<docTitle>
|
||||||
|
<text>${m.metadata.title}</text>
|
||||||
|
</docTitle>
|
||||||
|
<navMap>
|
||||||
|
`
|
||||||
|
for (let i in this.meta.toc){
|
||||||
|
let toc_item = this.meta.toc[i]
|
||||||
|
cnt +=
|
||||||
|
` <navPoint class="chapter" id="navpoint-${i}" playOrder="${i}">\n`
|
||||||
|
+` <navLabel><text>${toc_item.label}</text></navLabel>\n`
|
||||||
|
+` <content src="${toc_item.filename}"/>\n`
|
||||||
|
+' </navPoint>\n'
|
||||||
|
}
|
||||||
|
|
||||||
|
cnt += ' </navMap>\n</ncx>\n'
|
||||||
|
return cnt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class EpubNav{
|
||||||
|
constructor(filename, meta) {
|
||||||
|
this.filename = filename
|
||||||
|
this.meta = meta
|
||||||
|
this.mime = 'application/xhtml+xml'
|
||||||
|
}
|
||||||
|
|
||||||
|
generate(){
|
||||||
|
let m = this.meta
|
||||||
|
let cnt = `<?xml version="1.0" encoding="utf-8" standalone="no"?>
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html xmlns="http://www.w3.org/1999/xhtml"
|
||||||
|
xmlns:epub="http://www.idpf.org/2007/ops">
|
||||||
|
<head>
|
||||||
|
<title>${m.metadata.title}</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>${m.metadata.title}</h1>
|
||||||
|
<nav epub:type="toc" id="toc">
|
||||||
|
<h2>Table Of Content</h2>
|
||||||
|
<ol>
|
||||||
|
`
|
||||||
|
for (let i in this.meta.toc){
|
||||||
|
let toc_item = this.meta.toc[i]
|
||||||
|
cnt += ` <li><a href="${toc_item.filename}">${toc_item.label}</a></li>\n`
|
||||||
|
}
|
||||||
|
|
||||||
|
cnt += ' </ol>\n </nav>\n</body>\n</html>\n'
|
||||||
|
return cnt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class EpubMeta{
|
||||||
|
constructor() {
|
||||||
|
this.zip = new JSZip();
|
||||||
|
|
||||||
|
this.metadata = {
|
||||||
|
book_id: 'book_id',
|
||||||
|
title: 'title',
|
||||||
|
language: 'en',
|
||||||
|
author: 'author',
|
||||||
|
publisher: 'publisher',
|
||||||
|
cover_image: 'cover.img'
|
||||||
|
}
|
||||||
|
this.manifest = {}
|
||||||
|
this.spine = []
|
||||||
|
this.guide = []
|
||||||
|
this.toc = []
|
||||||
|
this.override = {}
|
||||||
|
|
||||||
|
this.addManifest(this.ncx().filename, this.ncx().mime)
|
||||||
|
this.addManifest(this.nav().filename, this.nav().mime, 'nav')
|
||||||
|
this.manifest[this.nav().filename].property = 'nav' //TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
addBookId(book_id){
|
||||||
|
this.metadata.book_id = book_id
|
||||||
|
}
|
||||||
|
addTitle(title){
|
||||||
|
this.metadata.title = title
|
||||||
|
}
|
||||||
|
addAuthor(author){
|
||||||
|
this.metadata.author = author
|
||||||
|
}
|
||||||
|
addPublisher(publisher){
|
||||||
|
this.metadata.publisher = publisher
|
||||||
|
}
|
||||||
|
addLanguage(lang){
|
||||||
|
this.metadata.language = lang
|
||||||
|
}
|
||||||
|
|
||||||
|
addMetaData(){
|
||||||
|
}
|
||||||
|
|
||||||
|
addManifest(filename, mime){
|
||||||
|
let key = filename
|
||||||
|
if (mime.startsWith('text/html')){
|
||||||
|
let o = new EpubXhtml(filename)
|
||||||
|
this.override[key] = o
|
||||||
|
filename = o.filename
|
||||||
|
mime = o.mime
|
||||||
|
}
|
||||||
|
let id = 'id_' + filename.replace(/[^a-z0-9]/gi, '_').toLowerCase()
|
||||||
|
this.manifest[key] =
|
||||||
|
{
|
||||||
|
filename : filename,
|
||||||
|
mime : mime,
|
||||||
|
id : id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getId(filename){
|
||||||
|
return this.manifest[filename].id
|
||||||
|
}
|
||||||
|
|
||||||
|
addSpine(filename){
|
||||||
|
this.spine.push(this.manifest[filename].id)
|
||||||
|
}
|
||||||
|
|
||||||
|
addCover(filename){
|
||||||
|
this.manifest[filename].property = 'cover-image'
|
||||||
|
}
|
||||||
|
|
||||||
|
addToc(filename, label, depth){
|
||||||
|
if (filename in this.override){
|
||||||
|
filename = this.override[filename].filename
|
||||||
|
}
|
||||||
|
this.toc.push({
|
||||||
|
filename: filename,
|
||||||
|
label: label,
|
||||||
|
depth: depth
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
opf(){
|
||||||
|
return new EpubOpf("content.opf", this)
|
||||||
|
}
|
||||||
|
|
||||||
|
ncx(){
|
||||||
|
return new EpubNcx("content.ncx", this)
|
||||||
|
}
|
||||||
|
|
||||||
|
nav(){
|
||||||
|
return new EpubNav("content.xhtml", this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class EpubWriter{
|
class EpubWriter{
|
||||||
constructor(book_id) {
|
constructor(book_id) {
|
||||||
this.zip = new JSZip();
|
this.zip = new JSZip();
|
||||||
|
this.meta = new EpubMeta();
|
||||||
this.filename = "test";
|
this.override = {}
|
||||||
|
|
||||||
this.createEpubStruct();
|
this.createEpubStruct();
|
||||||
|
|
||||||
// this.image
|
|
||||||
|
|
||||||
// this.book_id = book_id;
|
|
||||||
// this.raw_book = [];
|
|
||||||
// this.chapter_list = [];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
createEpubStruct(){
|
createEpubStruct(){
|
||||||
this.zip.file("mimetype", "application/epub+zip", {compression: "STORE"});
|
this.zip.file("mimetype", "application/epub+zip", {compression: "STORE"});
|
||||||
this.zip.file("META-INF/container.xml", `
|
this.zip.file("META-INF/container.xml",
|
||||||
<?xml version='1.0' encoding='utf-8'?>
|
`<?xml version='1.0' encoding='utf-8'?>
|
||||||
<container xmlns="urn:oasis:names:tc:opendocument:xmlns:container" version="1.0">
|
<container xmlns="urn:oasis:names:tc:opendocument:xmlns:container" version="1.0">
|
||||||
<rootfiles>
|
<rootfiles>
|
||||||
<rootfile media-type="application/oebps-package+xml" full-path="EPUB/content.opf"/>
|
<rootfile media-type="application/oebps-package+xml" full-path="EPUB/content.opf"/>
|
||||||
@@ -25,14 +270,28 @@ class EpubWriter{
|
|||||||
` );
|
` );
|
||||||
}
|
}
|
||||||
|
|
||||||
addFile(name, content){
|
addFile(filename, content){
|
||||||
console.log(`epub ${name}: ${content}`)
|
console.log(`epub ${filename}: ${content}`)
|
||||||
this.zip.file("EPUB/"+name, content)
|
|
||||||
|
if (filename in this.meta.override){
|
||||||
|
let o = this.meta.override[filename]
|
||||||
|
filename = o.filename
|
||||||
|
content = o.convert(content)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.zip.file("EPUB/"+filename, content)
|
||||||
}
|
}
|
||||||
|
|
||||||
generateAsync(){
|
generateAsync(){
|
||||||
|
this.addFile(this.meta.ncx().filename, this.meta.ncx().generate())
|
||||||
|
this.addFile(this.meta.nav().filename, this.meta.nav().generate())
|
||||||
|
this.addFile(this.meta.opf().filename, this.meta.opf().generate())
|
||||||
return this.zip.generateAsync({
|
return this.zip.generateAsync({
|
||||||
type: 'blob',
|
type: generatedBufferType,
|
||||||
compression: "DEFLATE"})
|
compression: "DEFLATE"})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isNode()){
|
||||||
|
module.exports = EpubWriter
|
||||||
|
}
|
||||||
|
|||||||
@@ -39,9 +39,10 @@ function extractBookId(url){
|
|||||||
class Book{
|
class Book{
|
||||||
|
|
||||||
constructor(book_id, epub) {
|
constructor(book_id, epub) {
|
||||||
this.book_id = book_id;
|
this.book_id = book_id
|
||||||
this.raw_book = {};
|
this.raw_book = {}
|
||||||
this.chapter_list = [];
|
this.chapter_list = []
|
||||||
|
this.chapter_info = {}
|
||||||
this.book_files = {}
|
this.book_files = {}
|
||||||
this.book_info = null
|
this.book_info = null
|
||||||
this.book_toc = null
|
this.book_toc = null
|
||||||
@@ -116,6 +117,7 @@ class Book{
|
|||||||
renderProgress(`${chapter}`)
|
renderProgress(`${chapter}`)
|
||||||
return this.downloadJson(chapter)
|
return this.downloadJson(chapter)
|
||||||
.then((json) => {
|
.then((json) => {
|
||||||
|
this.chapter_info[chapter] = json
|
||||||
return this.extractChapterAssets(json)
|
return this.extractChapterAssets(json)
|
||||||
})
|
})
|
||||||
},{concurrency: 10}))
|
},{concurrency: 10}))
|
||||||
|
|||||||
121
test/app.js
121
test/app.js
@@ -1,6 +1,8 @@
|
|||||||
var fs = require("fs");
|
let fs = require("fs");
|
||||||
var JSZip = require('jszip');
|
let JSZip = require('jszip');
|
||||||
|
let EpubWriter = require('../src/epub')
|
||||||
let zip = new JSZip()
|
let zip = new JSZip()
|
||||||
|
let epub = new EpubWriter()
|
||||||
|
|
||||||
new JSZip.external.Promise((resolve, reject)=>{
|
new JSZip.external.Promise((resolve, reject)=>{
|
||||||
fs.readFile('test/mythical_man_month__the__essays_on_software_engineering__anniversary_edition.zip',
|
fs.readFile('test/mythical_man_month__the__essays_on_software_engineering__anniversary_edition.zip',
|
||||||
@@ -18,15 +20,122 @@ new JSZip.external.Promise((resolve, reject)=>{
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
.then((zip)=>{
|
.then((zip)=>{
|
||||||
console.log(zip)
|
return loadBookInfo(zip)
|
||||||
zip.folder('EPUB').forEach((fileName, _)=>{
|
.then((book_info)=>{
|
||||||
console.log(`${fileName}`)
|
createEbook(epub, book_info)
|
||||||
|
return copyContent(epub, zip)
|
||||||
|
})
|
||||||
|
.then(()=>{
|
||||||
|
return epub.generateAsync()
|
||||||
|
})
|
||||||
|
.then((file)=>{
|
||||||
|
fs.writeFile('ttttest.epub', file, (err) => {
|
||||||
|
if (err)
|
||||||
|
return console.log(err);
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.catch((reason)=>{
|
.catch((reason)=>{
|
||||||
console.log(`Error: ${reason}`)
|
console.log(`Error: ${reason}`)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
function copyContent(epub, zip)
|
||||||
|
{
|
||||||
|
let jobs = []
|
||||||
|
zip.folder('EPUB').forEach((fileName, zipObj)=>{
|
||||||
|
console.log(`${fileName}`)
|
||||||
|
jobs.push(
|
||||||
|
zipObj.async('uint8array')
|
||||||
|
.then((buffer)=>{
|
||||||
|
epub.addFile(fileName, buffer)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
return Promise.all(jobs)
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadBookInfo(zip)
|
||||||
|
{
|
||||||
|
let book_json = zip.file('EPUB/book.json')
|
||||||
|
if (!book_json){
|
||||||
|
book_json = zip.file('META-INF/book.json')
|
||||||
|
}
|
||||||
|
return book_json.async('string')
|
||||||
|
.then((blob)=>{
|
||||||
|
return JSON.parse(blob)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
console.log('test')
|
function createEbook(epub, book_info)
|
||||||
|
{
|
||||||
|
console.log(`${book_info}`)
|
||||||
|
|
||||||
|
// OPF file info.
|
||||||
|
fillManifest(epub, book_info)
|
||||||
|
fillSpine(epub, book_info)
|
||||||
|
fillGuide(epub, book_info)
|
||||||
|
fillMetadata(epub, book_info)
|
||||||
|
|
||||||
|
// NCX and NAV files.
|
||||||
|
fillToc(epub, book_info)
|
||||||
|
}
|
||||||
|
|
||||||
|
function fillMetadata(epub, book_info)
|
||||||
|
{
|
||||||
|
epub.meta.addTitle(book_info.book_info.title)
|
||||||
|
epub.meta.addLanguage(book_info.book_info.language)
|
||||||
|
epub.meta.addBookId(book_info.book_info.isbn)
|
||||||
|
for (let i in book_info.book_info.authors){
|
||||||
|
let author = book_info.book_info.authors[i]
|
||||||
|
epub.meta.addAuthor(author)
|
||||||
|
}
|
||||||
|
for (let i in book_info.book_info.publishers){
|
||||||
|
let publisher = book_info.book_info.publishers[i]
|
||||||
|
epub.meta.addPublisher(publisher.name)
|
||||||
|
}
|
||||||
|
// # The metadata element or deprecated dc-metadata element contains
|
||||||
|
// # at least one identifier element, at least one title element,
|
||||||
|
// # and at least one language element drawn from the Dublin Core tag
|
||||||
|
// # set.
|
||||||
|
// epub.set_title('Test Title')
|
||||||
|
// epub.set_language('en')
|
||||||
|
// epub.set_direction('ltr')
|
||||||
|
// # epub.set_cover(file_name, content, create_page=True):
|
||||||
|
// # epub.add_author(author, file_as=None, role=None, uid='creator'):
|
||||||
|
// # epub.add_metadata(namespace, name, value, others=None):
|
||||||
|
// # epub.set_unique_metadata(namespace, name, value, others=None):
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function fillManifest(epub, book_info)
|
||||||
|
{
|
||||||
|
for (let key in book_info.book_files){
|
||||||
|
let f = book_info.book_files[key]
|
||||||
|
epub.meta.addManifest(f.filename, f.mime)
|
||||||
|
}
|
||||||
|
|
||||||
|
let f = book_info.book_files[book_info.book_info.cover]
|
||||||
|
epub.meta.addCover(f.filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
function fillToc(epub, book_info)
|
||||||
|
{
|
||||||
|
for (let i in book_info.book_toc){
|
||||||
|
let toc_item = book_info.book_toc[i]
|
||||||
|
epub.meta.addToc(
|
||||||
|
toc_item.filename,
|
||||||
|
toc_item.label,
|
||||||
|
toc_item.depth)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function fillSpine(epub, book_info)
|
||||||
|
{
|
||||||
|
for (let i in book_info.book_info.chapters){
|
||||||
|
let s = book_info.book_info.chapters[i].split('/')
|
||||||
|
epub.meta.addSpine(s[s.length-1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function fillGuide(epub, book_info)
|
||||||
|
{}
|
||||||
Reference in New Issue
Block a user