UNPKG

epub-chinese-converter

Version:

A small util to convert epub files from Simplified Chinese to Traditional Chinese

331 lines (330 loc) 14.2 kB
"use strict"; var ZipEntry = require("./zipEntry"); var Headers = require("./headers"); var Utils = require("./util"); module.exports = function (/*Buffer|null*/ inBuffer, /** object */ options) { var entryList = [], entryTable = {}, _comment = Buffer.alloc(0), mainHeader = new Headers.MainHeader(), loadedEntries = false; var password = null; // assign options var opts = Object.assign(Object.create(null), options); var noSort = opts.noSort; if (inBuffer) { // is a memory buffer readMainHeader(opts.readEntries); } else { // none. is a new file loadedEntries = true; } function iterateEntries(callback) { var totalEntries = mainHeader.diskEntries; // total number of entries var index = mainHeader.offset; // offset of first CEN header for (var i = 0; i < totalEntries; i++) { var tmp = index; var entry = new ZipEntry(inBuffer); entry.header = inBuffer.slice(tmp, (tmp += Utils.Constants.CENHDR)); entry.entryName = inBuffer.slice(tmp, (tmp += entry.header.fileNameLength)); index += entry.header.centralHeaderSize; callback(entry); } } function readEntries() { loadedEntries = true; entryTable = {}; if (mainHeader.diskEntries > (inBuffer.length - mainHeader.offset) / Utils.Constants.CENHDR) { throw new Error(Utils.Errors.DISK_ENTRY_TOO_LARGE); } entryList = new Array(mainHeader.diskEntries); // total number of entries var index = mainHeader.offset; // offset of first CEN header for (var i = 0; i < entryList.length; i++) { var tmp = index, entry = new ZipEntry(inBuffer); entry.header = inBuffer.slice(tmp, (tmp += Utils.Constants.CENHDR)); entry.entryName = inBuffer.slice(tmp, (tmp += entry.header.fileNameLength)); if (entry.header.extraLength) { entry.extra = inBuffer.slice(tmp, (tmp += entry.header.extraLength)); } if (entry.header.commentLength) entry.comment = inBuffer.slice(tmp, tmp + entry.header.commentLength); index += entry.header.centralHeaderSize; entryList[i] = entry; entryTable[entry.entryName] = entry; } } function readMainHeader(/*Boolean*/ readNow) { var i = inBuffer.length - Utils.Constants.ENDHDR, // END header size max = Math.max(0, i - 0xffff), // 0xFFFF is the max zip file comment length n = max, endStart = inBuffer.length, endOffset = -1, // Start offset of the END header commentEnd = 0; for (i; i >= n; i--) { if (inBuffer[i] !== 0x50) continue; // quick check that the byte is 'P' if (inBuffer.readUInt32LE(i) === Utils.Constants.ENDSIG) { // "PK\005\006" endOffset = i; commentEnd = i; endStart = i + Utils.Constants.ENDHDR; // We already found a regular signature, let's look just a bit further to check if there's any zip64 signature n = i - Utils.Constants.END64HDR; continue; } if (inBuffer.readUInt32LE(i) === Utils.Constants.END64SIG) { // Found a zip64 signature, let's continue reading the whole zip64 record n = max; continue; } if (inBuffer.readUInt32LE(i) === Utils.Constants.ZIP64SIG) { // Found the zip64 record, let's determine it's size endOffset = i; endStart = i + Utils.readBigUInt64LE(inBuffer, i + Utils.Constants.ZIP64SIZE) + Utils.Constants.ZIP64LEAD; break; } } if (!~endOffset) throw new Error(Utils.Errors.INVALID_FORMAT); mainHeader.loadFromBinary(inBuffer.slice(endOffset, endStart)); if (mainHeader.commentLength) { _comment = inBuffer.slice(commentEnd + Utils.Constants.ENDHDR); } if (readNow) readEntries(); } function sortEntries() { if (entryList.length > 1 && !noSort) { entryList.sort(function (a, b) { return a.entryName.toLowerCase().localeCompare(b.entryName.toLowerCase()); }); } } return { /** * Returns an array of ZipEntry objects existent in the current opened archive * @return Array */ get entries() { if (!loadedEntries) { readEntries(); } return entryList; }, /** * Archive comment * @return {String} */ get comment() { return _comment.toString(); }, set comment(val) { _comment = Utils.toBuffer(val); mainHeader.commentLength = _comment.length; }, getEntryCount: function () { if (!loadedEntries) { return mainHeader.diskEntries; } return entryList.length; }, forEach: function (callback) { if (!loadedEntries) { iterateEntries(callback); return; } entryList.forEach(callback); }, /** * Returns a reference to the entry with the given name or null if entry is inexistent * * @param entryName * @return ZipEntry */ getEntry: function (/*String*/ entryName) { if (!loadedEntries) { readEntries(); } return entryTable[entryName] || null; }, /** * Adds the given entry to the entry list * * @param entry */ setEntry: function (/*ZipEntry*/ entry) { if (!loadedEntries) { readEntries(); } entryList.push(entry); entryTable[entry.entryName] = entry; mainHeader.totalEntries = entryList.length; }, /** * Removes the entry with the given name from the entry list. * * If the entry is a directory, then all nested files and directories will be removed * @param entryName */ deleteEntry: function (/*String*/ entryName) { if (!loadedEntries) { readEntries(); } var entry = entryTable[entryName]; if (entry && entry.isDirectory) { var _self = this; this.getEntryChildren(entry).forEach(function (child) { if (child.entryName !== entryName) { _self.deleteEntry(child.entryName); } }); } entryList.splice(entryList.indexOf(entry), 1); delete entryTable[entryName]; mainHeader.totalEntries = entryList.length; }, /** * Iterates and returns all nested files and directories of the given entry * * @param entry * @return Array */ getEntryChildren: function (/*ZipEntry*/ entry) { if (!loadedEntries) { readEntries(); } if (entry && entry.isDirectory) { var list_1 = []; var name_1 = entry.entryName; var len_1 = name_1.length; entryList.forEach(function (zipEntry) { if (zipEntry.entryName.substr(0, len_1) === name_1) { list_1.push(zipEntry); } }); return list_1; } return []; }, /** * Returns the zip file * * @return Buffer */ compressToBuffer: function () { if (!loadedEntries) { readEntries(); } sortEntries(); var dataBlock = []; var headerBlocks = []; var totalSize = 0; var dindex = 0; mainHeader.size = 0; mainHeader.offset = 0; for (var _i = 0, entryList_1 = entryList; _i < entryList_1.length; _i++) { var entry = entryList_1[_i]; // compress data and set local and entry header accordingly. Reason why is called first var compressedData = entry.getCompressedData(); entry.header.offset = dindex; // 1. construct local header var localHeader = entry.packLocalHeader(); // 2. offsets var dataLength = localHeader.length + compressedData.length; dindex += dataLength; // 3. store values in sequence dataBlock.push(localHeader); dataBlock.push(compressedData); // 4. construct central header var centralHeader = entry.packCentralHeader(); headerBlocks.push(centralHeader); // 5. update main header mainHeader.size += centralHeader.length; totalSize += dataLength + centralHeader.length; } totalSize += mainHeader.mainHeaderSize; // also includes zip file comment length // point to end of data and beginning of central directory first record mainHeader.offset = dindex; dindex = 0; var outBuffer = Buffer.alloc(totalSize); // write data blocks for (var _a = 0, dataBlock_1 = dataBlock; _a < dataBlock_1.length; _a++) { var content = dataBlock_1[_a]; content.copy(outBuffer, dindex); dindex += content.length; } // write central directory entries for (var _b = 0, headerBlocks_1 = headerBlocks; _b < headerBlocks_1.length; _b++) { var content = headerBlocks_1[_b]; content.copy(outBuffer, dindex); dindex += content.length; } // write main header var mh = mainHeader.toBinary(); if (_comment) { _comment.copy(mh, Utils.Constants.ENDHDR); // add zip file comment } mh.copy(outBuffer, dindex); return outBuffer; }, toAsyncBuffer: function (/*Function*/ onSuccess, /*Function*/ onFail, /*Function*/ onItemStart, /*Function*/ onItemEnd) { try { if (!loadedEntries) { readEntries(); } sortEntries(); var dataBlock_2 = []; var centralHeaders_1 = []; var totalSize_1 = 0; var dindex_1 = 0; mainHeader.size = 0; mainHeader.offset = 0; var compress2Buffer_1 = function (entryLists) { if (entryLists.length > 0) { var entry_1 = entryLists.shift(); var name_2 = entry_1.entryName + entry_1.extra.toString(); if (onItemStart) onItemStart(name_2); entry_1.getCompressedDataAsync(function (compressedData) { if (onItemEnd) onItemEnd(name_2); entry_1.header.offset = dindex_1; // 1. construct local header var localHeader = entry_1.packLocalHeader(); // 2. offsets var dataLength = localHeader.length + compressedData.length; dindex_1 += dataLength; // 3. store values in sequence dataBlock_2.push(localHeader); dataBlock_2.push(compressedData); // central header var centalHeader = entry_1.packCentralHeader(); centralHeaders_1.push(centalHeader); mainHeader.size += centalHeader.length; totalSize_1 += dataLength + centalHeader.length; compress2Buffer_1(entryLists); }); } else { totalSize_1 += mainHeader.mainHeaderSize; // also includes zip file comment length // point to end of data and beginning of central directory first record mainHeader.offset = dindex_1; dindex_1 = 0; var outBuffer_1 = Buffer.alloc(totalSize_1); dataBlock_2.forEach(function (content) { content.copy(outBuffer_1, dindex_1); // write data blocks dindex_1 += content.length; }); centralHeaders_1.forEach(function (content) { content.copy(outBuffer_1, dindex_1); // write central directory entries dindex_1 += content.length; }); var mh = mainHeader.toBinary(); if (_comment) { _comment.copy(mh, Utils.Constants.ENDHDR); // add zip file comment } mh.copy(outBuffer_1, dindex_1); // write main header onSuccess(outBuffer_1); } }; compress2Buffer_1(Array.from(entryList)); } catch (e) { onFail(e); } } }; };