diff --git a/.vscode/launch.json b/.vscode/launch.json index 653bdc3..2ea791c 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -37,7 +37,8 @@ "name": "Launch Node.JS", "type": "node", "request": "launch", - "program": "${file}" + "program": "${workspaceFolder}/test/app.js" + // "program": "${file}" } ] } \ No newline at end of file diff --git a/src/epub.js b/src/epub.js index 7c26892..e1415ea 100644 --- a/src/epub.js +++ b/src/epub.js @@ -1,38 +1,297 @@ +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 = ` + + + + + + +${new TextDecoder("utf-8").write(content)} + + +` + return cnt + } +} + +class EpubOpf{ + constructor(filename, meta) { + this.filename = filename + this.meta = meta + } + + generate(){ + let m = this.meta + let cnt = ` + + + 2013-03-22T12:24:00Z + Please read the legal notice included in this e-book and/or check the copyright status in your country. + ${m.metadata.book_id} + ${m.metadata.title} + ${m.metadata.language}\n` + + if (m.metadata.publisher){ + cnt += ` ${m.metadata.publisher}\n` + } + if (m.metadata.cover_image){ + cnt += ` \n` + } + // Lauren Baratz-Logsted + + cnt +=' \n \n' + + for (let id in m.manifest){ + let f = m.manifest[id] + if (f.hasOwnProperty('property')){ + cnt += ` \n` + }else{ + cnt += ` \n` + } + } + cnt += ` \n \n` + for (let i in m.spine){ + cnt += ` \n` + } + cnt += ' \n \n' + cnt += ` \n` + cnt += ' \n\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 = ` + + + + + + + + + ${m.metadata.title} + + +` + for (let i in this.meta.toc){ + let toc_item = this.meta.toc[i] + cnt += + ` \n` + +` ${toc_item.label}\n` + +` \n` + +' \n' + } + + cnt += ' \n\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 = ` + + + + ${m.metadata.title} + + +

${m.metadata.title}

+ \n\n\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{ constructor(book_id) { this.zip = new JSZip(); - - this.filename = "test"; - + this.meta = new EpubMeta(); + this.override = {} this.createEpubStruct(); - - // this.image - - // this.book_id = book_id; - // this.raw_book = []; - // this.chapter_list = []; } createEpubStruct(){ this.zip.file("mimetype", "application/epub+zip", {compression: "STORE"}); - this.zip.file("META-INF/container.xml", ` - - - - - - - `); + this.zip.file("META-INF/container.xml", +` + + + + + +` ); } - addFile(name, content){ - console.log(`epub ${name}: ${content}`) - this.zip.file("EPUB/"+name, content) + addFile(filename, content){ + console.log(`epub ${filename}: ${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(){ + 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({ - type: 'blob', - compression: "DEFLATE"}) + type: generatedBufferType, + compression: "DEFLATE"}) } -} \ No newline at end of file +} + +if (isNode()){ + module.exports = EpubWriter +} diff --git a/src/sidebar.js b/src/sidebar.js index ebdf53a..2daec42 100644 --- a/src/sidebar.js +++ b/src/sidebar.js @@ -39,9 +39,10 @@ function extractBookId(url){ class Book{ constructor(book_id, epub) { - this.book_id = book_id; - this.raw_book = {}; - this.chapter_list = []; + this.book_id = book_id + this.raw_book = {} + this.chapter_list = [] + this.chapter_info = {} this.book_files = {} this.book_info = null this.book_toc = null @@ -116,7 +117,8 @@ class Book{ renderProgress(`${chapter}`) return this.downloadJson(chapter) .then((json) => { - return this.extractChapterAssets(json) + this.chapter_info[chapter] = json + return this.extractChapterAssets(json) }) },{concurrency: 10})) diff --git a/test/app.js b/test/app.js index 98f7681..fafc7e6 100644 --- a/test/app.js +++ b/test/app.js @@ -1,6 +1,8 @@ -var fs = require("fs"); -var JSZip = require('jszip'); +let fs = require("fs"); +let JSZip = require('jszip'); +let EpubWriter = require('../src/epub') let zip = new JSZip() +let epub = new EpubWriter() new JSZip.external.Promise((resolve, reject)=>{ 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)=>{ - console.log(zip) - zip.folder('EPUB').forEach((fileName, _)=>{ - console.log(`${fileName}`) + return loadBookInfo(zip) + .then((book_info)=>{ + 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)=>{ 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') \ No newline at end of file +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) +{} \ No newline at end of file