// Generated by CoffeeScript 2.3.2 (function() { var EPub, Q, _, archiver, cheerio, ejs, entities, fs, fsextra, mime, path, removeDiacritics, request, rimraf, uslug, uuid, indexOf = [].indexOf; path = require("path"); fs = require("fs"); Q = require("q"); _ = require("underscore"); uslug = require("uslug"); ejs = require("ejs"); cheerio = require("cheerio"); entities = require("entities"); request = require("superagent"); fsextra = require("fs-extra"); removeDiacritics = require("diacritics").remove; mime = require("mime"); archiver = require("archiver"); // provides rm -rf for deleting temp directory across various platforms. rimraf = require("rimraf"); uuid = function() { return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { var r; r = Math.random() * 16 | 0; return (c === 'x' ? r : r & 0x3 | 0x8).toString(16); }); }; EPub = class EPub { constructor(options, output) { var self; this.options = options; self = this; this.defer = new Q.defer(); if (output) { this.options.output = output; } if (!this.options.output) { console.error(new Error("No Output Path")); this.defer.reject(new Error("No output path")); return; } if (!options.title || !options.content) { console.error(new Error("Title and content are both required")); this.defer.reject(new Error("Title and content are both required")); return; } this.options = _.extend({ description: options.title, publisher: "anonymous", author: ["anonymous"], tocTitle: "Table Of Contents", appendChapterTitles: true, date: new Date().toISOString(), lang: "en", fonts: [], customOpfTemplatePath: null, customNcxTocTemplatePath: null, customHtmlTocTemplatePath: null, version: 3 }, options); if (this.options.version === 2) { this.options.docHeader = `\n\n`; } else { this.options.docHeader = `\n\n`; } if (_.isString(this.options.author)) { this.options.author = [this.options.author]; } if (_.isEmpty(this.options.author)) { this.options.author = ["anonymous"]; } if (!this.options.tempDir) { this.options.tempDir = path.resolve(__dirname, "../tempDir/"); } this.id = uuid(); this.uuid = path.resolve(this.options.tempDir, this.id); this.options.uuid = this.uuid; this.options.id = this.id; this.options.images = []; this.options.content = _.map(this.options.content, function(content, index) { var $, allowedAttributes, allowedXhtml11Tags, titleSlug; if (!content.filename) { titleSlug = uslug(removeDiacritics(content.title || "no title")); content.href = `${index}_${titleSlug}.xhtml`; content.filePath = path.resolve(self.uuid, `./OEBPS/${index}_${titleSlug}.xhtml`); } else { content.href = content.filename.match(/\.xhtml$/) ? content.filename : `${content.filename}.xhtml`; if (content.filename.match(/\.xhtml$/)) { content.filePath = path.resolve(self.uuid, `./OEBPS/${content.filename}`); } else { content.filePath = path.resolve(self.uuid, `./OEBPS/${content.filename}.xhtml`); } } content.id = `item_${index}`; content.dir = path.dirname(content.filePath); content.excludeFromToc || (content.excludeFromToc = false); content.beforeToc || (content.beforeToc = false); //fix Author Array content.author = content.author && _.isString(content.author) ? [content.author] : !content.author || !_.isArray(content.author) ? [] : content.author; allowedAttributes = ["content", "alt", "id", "title", "src", "href", "about", "accesskey", "aria-activedescendant", "aria-atomic", "aria-autocomplete", "aria-busy", "aria-checked", "aria-controls", "aria-describedat", "aria-describedby", "aria-disabled", "aria-dropeffect", "aria-expanded", "aria-flowto", "aria-grabbed", "aria-haspopup", "aria-hidden", "aria-invalid", "aria-label", "aria-labelledby", "aria-level", "aria-live", "aria-multiline", "aria-multiselectable", "aria-orientation", "aria-owns", "aria-posinset", "aria-pressed", "aria-readonly", "aria-relevant", "aria-required", "aria-selected", "aria-setsize", "aria-sort", "aria-valuemax", "aria-valuemin", "aria-valuenow", "aria-valuetext", "class", "content", "contenteditable", "contextmenu", "datatype", "dir", "draggable", "dropzone", "hidden", "hreflang", "id", "inlist", "itemid", "itemref", "itemscope", "itemtype", "lang", "media", "ns1:type", "ns2:alphabet", "ns2:ph", "onabort", "onblur", "oncanplay", "oncanplaythrough", "onchange", "onclick", "oncontextmenu", "ondblclick", "ondrag", "ondragend", "ondragenter", "ondragleave", "ondragover", "ondragstart", "ondrop", "ondurationchange", "onemptied", "onended", "onerror", "onfocus", "oninput", "oninvalid", "onkeydown", "onkeypress", "onkeyup", "onload", "onloadeddata", "onloadedmetadata", "onloadstart", "onmousedown", "onmousemove", "onmouseout", "onmouseover", "onmouseup", "onmousewheel", "onpause", "onplay", "onplaying", "onprogress", "onratechange", "onreadystatechange", "onreset", "onscroll", "onseeked", "onseeking", "onselect", "onshow", "onstalled", "onsubmit", "onsuspend", "ontimeupdate", "onvolumechange", "onwaiting", "prefix", "property", "rel", "resource", "rev", "role", "spellcheck", "style", "tabindex", "target", "title", "type", "typeof", "vocab", "xml:base", "xml:lang", "xml:space", "colspan", "rowspan", "epub:type", "epub:prefix"]; allowedXhtml11Tags = ["div", "p", "h1", "h2", "h3", "h4", "h5", "h6", "ul", "ol", "li", "dl", "dt", "dd", "address", "hr", "pre", "blockquote", "center", "ins", "del", "a", "span", "bdo", "br", "em", "strong", "dfn", "code", "samp", "kbd", "bar", "cite", "abbr", "acronym", "q", "sub", "sup", "tt", "i", "b", "big", "small", "u", "s", "strike", "basefont", "font", "object", "param", "img", "table", "caption", "colgroup", "col", "thead", "tfoot", "tbody", "tr", "th", "td", "embed", "applet", "iframe", "img", "map", "noscript", "ns:svg", "object", "script", "table", "tt", "var"]; $ = cheerio.load(content.data, { lowerCaseTags: true, recognizeSelfClosing: true }); // Only body innerHTML is allowed if ($("body").length) { $ = cheerio.load($("body").html(), { lowerCaseTags: true, recognizeSelfClosing: true }); } $($("*").get().reverse()).each(function(elemIndex, elem) { var attrs, child, k, ref, ref1, that, v; attrs = elem.attribs; that = this; if ((ref = that.name) === "img" || ref === "br" || ref === "hr") { if (that.name === "img") { $(that).attr("alt", $(that).attr("alt") || "image-placeholder"); } } for (k in attrs) { v = attrs[k]; if (indexOf.call(allowedAttributes, k) >= 0) { if (k === "type") { if (that.name !== "script") { $(that).removeAttr(k); } } } else { $(that).removeAttr(k); } } if (self.options.version === 2) { if (ref1 = that.name, indexOf.call(allowedXhtml11Tags, ref1) >= 0) { } else { console.log("Warning (content[" + index + "]):", that.name, "tag isn't allowed on EPUB 2/XHTML 1.1 DTD."); child = $(that).html(); return $(that).replaceWith($("
" + child + "
")); } } }); $("img").each(function(index, elem) { var dir, extension, id, image, mediaType, url; url = $(elem).attr("src"); if (image = self.options.images.find(function(element) { return element.url === url; })) { id = image.id; extension = image.extension; } else { id = uuid(); mediaType = mime.getType(url.replace(/\?.*/, "")); extension = mime.getExtension(mediaType); dir = content.dir; self.options.images.push({id, url, dir, mediaType, extension}); } return $(elem).attr("src", `images/${id}.${extension}`); }); content.data = $.xml(); return content; }); if (this.options.cover) { this.options._coverMediaType = mime.getType(this.options.cover); this.options._coverExtension = mime.getExtension(this.options._coverMediaType); } this.render(); this.promise = this.defer.promise; } render() { var self; self = this; if (self.options.verbose) { console.log("Generating Template Files....."); } return this.generateTempFile().then(function() { if (self.options.verbose) { console.log("Downloading Images..."); } return self.downloadAllImage().fin(function() { if (self.options.verbose) { console.log("Making Cover..."); } return self.makeCover().then(function() { if (self.options.verbose) { console.log("Generating Epub Files..."); } return self.genEpub().then(function(result) { if (self.options.verbose) { console.log("About to finish..."); } self.defer.resolve(result); if (self.options.verbose) { return console.log("Done."); } }, function(err) { return self.defer.reject(err); }); }, function(err) { return self.defer.reject(err); }); }, function(err) { return self.defer.reject(err); }); }, function(err) { return self.defer.reject(err); }); } generateTempFile() { var base, generateDefer, htmlTocPath, ncxTocPath, opfPath, self; generateDefer = new Q.defer(); self = this; if (!fs.existsSync(this.options.tempDir)) { fs.mkdirSync(this.options.tempDir); } fs.mkdirSync(this.uuid); fs.mkdirSync(path.resolve(this.uuid, "./OEBPS")); (base = this.options).css || (base.css = fs.readFileSync(path.resolve(__dirname, "../templates/template.css"))); fs.writeFileSync(path.resolve(this.uuid, "./OEBPS/style.css"), this.options.css); if (self.options.fonts.length) { fs.mkdirSync(path.resolve(this.uuid, "./OEBPS/fonts")); this.options.fonts = _.map(this.options.fonts, function(font) { var filename; if (!fs.existsSync(font)) { generateDefer.reject(new Error('Custom font not found at ' + font + '.')); return generateDefer.promise; } filename = path.basename(font); fsextra.copySync(font, path.resolve(self.uuid, "./OEBPS/fonts/" + filename)); return filename; }); } _.each(this.options.content, function(content) { var data; data = `${self.options.docHeader}\n \n \n ${entities.encodeXML(content.title || '')}\n \n \n`; data += content.title && self.options.appendChapterTitles ? `

${entities.encodeXML(content.title)}

` : ""; data += content.title && content.author && content.author.length ? `

${entities.encodeXML(content.author.join(", "))}

` : ""; data += content.title && content.url ? `` : ""; data += `${content.data}`; return fs.writeFileSync(content.filePath, data); }); // write meta-inf/container.xml fs.mkdirSync(this.uuid + "/META-INF"); fs.writeFileSync(`${this.uuid}/META-INF/container.xml`, ""); if (self.options.version === 2) { // write meta-inf/com.apple.ibooks.display-options.xml [from pedrosanta:xhtml#6] fs.writeFileSync(`${this.uuid}/META-INF/com.apple.ibooks.display-options.xml`, "\n\n \n \n \n"); } opfPath = self.options.customOpfTemplatePath || path.resolve(__dirname, `../templates/epub${self.options.version}/content.opf.ejs`); if (!fs.existsSync(opfPath)) { generateDefer.reject(new Error('Custom file to OPF template not found.')); return generateDefer.promise; } ncxTocPath = self.options.customNcxTocTemplatePath || path.resolve(__dirname, "../templates/toc.ncx.ejs"); if (!fs.existsSync(ncxTocPath)) { generateDefer.reject(new Error('Custom file the NCX toc template not found.')); return generateDefer.promise; } htmlTocPath = self.options.customHtmlTocTemplatePath || path.resolve(__dirname, `../templates/epub${self.options.version}/toc.xhtml.ejs`); if (!fs.existsSync(htmlTocPath)) { generateDefer.reject(new Error('Custom file to HTML toc template not found.')); return generateDefer.promise; } Q.all([Q.nfcall(ejs.renderFile, opfPath, self.options), Q.nfcall(ejs.renderFile, ncxTocPath, self.options), Q.nfcall(ejs.renderFile, htmlTocPath, self.options)]).spread(function(data1, data2, data3) { fs.writeFileSync(path.resolve(self.uuid, "./OEBPS/content.opf"), data1); fs.writeFileSync(path.resolve(self.uuid, "./OEBPS/toc.ncx"), data2); fs.writeFileSync(path.resolve(self.uuid, "./OEBPS/toc.xhtml"), data3); return generateDefer.resolve(); }, function(err) { console.error(arguments); return generateDefer.reject(err); }); return generateDefer.promise; } makeCover() { var coverDefer, destPath, userAgent, writeStream; userAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.116 Safari/537.36"; coverDefer = new Q.defer(); if (this.options.cover) { destPath = path.resolve(this.uuid, "./OEBPS/cover." + this.options._coverExtension); writeStream = null; if (this.options.cover.slice(0, 4) === "http") { writeStream = request.get(this.options.cover).set({ 'User-Agent': userAgent }); writeStream.pipe(fs.createWriteStream(destPath)); } else { writeStream = fs.createReadStream(this.options.cover); writeStream.pipe(fs.createWriteStream(destPath)); } writeStream.on("end", function() { console.log("[Success] cover image downloaded successfully!"); return coverDefer.resolve(); }); writeStream.on("error", function(err) { console.error("Error", err); return coverDefer.reject(err); }); } else { coverDefer.resolve(); } return coverDefer.promise; } downloadImage(options) { //{id, url, mediaType} var auxpath, downloadImageDefer, filename, requestAction, self, userAgent; self = this; userAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.116 Safari/537.36"; if (!options.url && typeof options !== "string") { return false; } downloadImageDefer = new Q.defer(); filename = path.resolve(self.uuid, "./OEBPS/images/" + options.id + "." + options.extension); if (options.url.indexOf("file://") === 0) { auxpath = options.url.substr(7); fsextra.copySync(auxpath, filename); return downloadImageDefer.resolve(options); } else { if (options.url.indexOf("http") === 0) { requestAction = request.get(options.url).set({ 'User-Agent': userAgent }); requestAction.pipe(fs.createWriteStream(filename)); } else { requestAction = fs.createReadStream(path.resolve(options.dir, options.url)); requestAction.pipe(fs.createWriteStream(filename)); } requestAction.on('error', function(err) { console.error('[Download Error]', 'Error while downloading', options.url, err); fs.unlinkSync(filename); return downloadImageDefer.reject(err); }); requestAction.on('end', function() { console.log("[Download Success]", options.url); return downloadImageDefer.resolve(options); }); return downloadImageDefer.promise; } } downloadAllImage() { var deferArray, imgDefer, self; self = this; imgDefer = new Q.defer(); if (!self.options.images.length) { imgDefer.resolve(); } else { fs.mkdirSync(path.resolve(this.uuid, "./OEBPS/images")); deferArray = []; _.each(self.options.images, function(image) { return deferArray.push(self.downloadImage(image)); }); Q.all(deferArray).fin(function() { return imgDefer.resolve(); }); } return imgDefer.promise; } genEpub() { var archive, cwd, genDefer, output, self; // Thanks to Paul Bradley // http://www.bradleymedia.org/gzip-markdown-epub/ (404 as of 28.07.2016) // Web Archive URL: // http://web.archive.org/web/20150521053611/http://www.bradleymedia.org/gzip-markdown-epub // or Gist: // https://gist.github.com/cyrilis/8d48eef37fbc108869ac32eb3ef97bca genDefer = new Q.defer(); self = this; cwd = this.uuid; archive = archiver("zip", { zlib: { level: 9 } }); output = fs.createWriteStream(self.options.output); console.log("Zipping temp dir to", self.options.output); archive.append("application/epub+zip", { store: true, name: "mimetype" }); archive.directory(cwd + "/META-INF", "META-INF"); archive.directory(cwd + "/OEBPS", "OEBPS"); archive.pipe(output); archive.on("end", function() { console.log("Done zipping, clearing temp dir..."); return rimraf(cwd, function(err) { if (err) { return genDefer.reject(err); } else { return genDefer.resolve(); } }); }); archive.on("error", function(err) { return genDefer.reject(err); }); archive.finalize(); return genDefer.promise; } }; module.exports = EPub; }).call(this);