| 'use strict'; |
| var utf8 = require('./utf8'); |
| var utils = require('./utils'); |
| var GenericWorker = require('./stream/GenericWorker'); |
| var StreamHelper = require('./stream/StreamHelper'); |
| var defaults = require('./defaults'); |
| var CompressedObject = require('./compressedObject'); |
| var ZipObject = require('./zipObject'); |
| var generate = require("./generate"); |
| var nodejsUtils = require("./nodejsUtils"); |
| var NodejsStreamInputAdapter = require("./nodejs/NodejsStreamInputAdapter"); |
| |
| |
| /** |
| * Add a file in the current folder. |
| * @private |
| * @param {string} name the name of the file |
| * @param {String|ArrayBuffer|Uint8Array|Buffer} data the data of the file |
| * @param {Object} originalOptions the options of the file |
| * @return {Object} the new file. |
| */ |
| var fileAdd = function(name, data, originalOptions) { |
| // be sure sub folders exist |
| var dataType = utils.getTypeOf(data), |
| parent; |
| |
| |
| /* |
| * Correct options. |
| */ |
| |
| var o = utils.extend(originalOptions || {}, defaults); |
| o.date = o.date || new Date(); |
| if (o.compression !== null) { |
| o.compression = o.compression.toUpperCase(); |
| } |
| |
| if (typeof o.unixPermissions === "string") { |
| o.unixPermissions = parseInt(o.unixPermissions, 8); |
| } |
| |
| // UNX_IFDIR 0040000 see zipinfo.c |
| if (o.unixPermissions && (o.unixPermissions & 0x4000)) { |
| o.dir = true; |
| } |
| // Bit 4 Directory |
| if (o.dosPermissions && (o.dosPermissions & 0x0010)) { |
| o.dir = true; |
| } |
| |
| if (o.dir) { |
| name = forceTrailingSlash(name); |
| } |
| if (o.createFolders && (parent = parentFolder(name))) { |
| folderAdd.call(this, parent, true); |
| } |
| |
| var isUnicodeString = dataType === "string" && o.binary === false && o.base64 === false; |
| if (!originalOptions || typeof originalOptions.binary === "undefined") { |
| o.binary = !isUnicodeString; |
| } |
| |
| |
| var isCompressedEmpty = (data instanceof CompressedObject) && data.uncompressedSize === 0; |
| |
| if (isCompressedEmpty || o.dir || !data || data.length === 0) { |
| o.base64 = false; |
| o.binary = true; |
| data = ""; |
| o.compression = "STORE"; |
| dataType = "string"; |
| } |
| |
| /* |
| * Convert content to fit. |
| */ |
| |
| var zipObjectContent = null; |
| if (data instanceof CompressedObject || data instanceof GenericWorker) { |
| zipObjectContent = data; |
| } else if (nodejsUtils.isNode && nodejsUtils.isStream(data)) { |
| zipObjectContent = new NodejsStreamInputAdapter(name, data); |
| } else { |
| zipObjectContent = utils.prepareContent(name, data, o.binary, o.optimizedBinaryString, o.base64); |
| } |
| |
| var object = new ZipObject(name, zipObjectContent, o); |
| this.files[name] = object; |
| /* |
| TODO: we can't throw an exception because we have async promises |
| (we can have a promise of a Date() for example) but returning a |
| promise is useless because file(name, data) returns the JSZip |
| object for chaining. Should we break that to allow the user |
| to catch the error ? |
| |
| return external.Promise.resolve(zipObjectContent) |
| .then(function () { |
| return object; |
| }); |
| */ |
| }; |
| |
| /** |
| * Find the parent folder of the path. |
| * @private |
| * @param {string} path the path to use |
| * @return {string} the parent folder, or "" |
| */ |
| var parentFolder = function (path) { |
| if (path.slice(-1) === '/') { |
| path = path.substring(0, path.length - 1); |
| } |
| var lastSlash = path.lastIndexOf('/'); |
| return (lastSlash > 0) ? path.substring(0, lastSlash) : ""; |
| }; |
| |
| /** |
| * Returns the path with a slash at the end. |
| * @private |
| * @param {String} path the path to check. |
| * @return {String} the path with a trailing slash. |
| */ |
| var forceTrailingSlash = function(path) { |
| // Check the name ends with a / |
| if (path.slice(-1) !== "/") { |
| path += "/"; // IE doesn't like substr(-1) |
| } |
| return path; |
| }; |
| |
| /** |
| * Add a (sub) folder in the current folder. |
| * @private |
| * @param {string} name the folder's name |
| * @param {boolean=} [createFolders] If true, automatically create sub |
| * folders. Defaults to false. |
| * @return {Object} the new folder. |
| */ |
| var folderAdd = function(name, createFolders) { |
| createFolders = (typeof createFolders !== 'undefined') ? createFolders : defaults.createFolders; |
| |
| name = forceTrailingSlash(name); |
| |
| // Does this folder already exist? |
| if (!this.files[name]) { |
| fileAdd.call(this, name, null, { |
| dir: true, |
| createFolders: createFolders |
| }); |
| } |
| return this.files[name]; |
| }; |
| |
| /** |
| * Cross-window, cross-Node-context regular expression detection |
| * @param {Object} object Anything |
| * @return {Boolean} true if the object is a regular expression, |
| * false otherwise |
| */ |
| function isRegExp(object) { |
| return Object.prototype.toString.call(object) === "[object RegExp]"; |
| } |
| |
| // return the actual prototype of JSZip |
| var out = { |
| /** |
| * @see loadAsync |
| */ |
| load: function() { |
| throw new Error("This method has been removed in JSZip 3.0, please check the upgrade guide."); |
| }, |
| |
| |
| /** |
| * Call a callback function for each entry at this folder level. |
| * @param {Function} cb the callback function: |
| * function (relativePath, file) {...} |
| * It takes 2 arguments : the relative path and the file. |
| */ |
| forEach: function(cb) { |
| var filename, relativePath, file; |
| for (filename in this.files) { |
| if (!this.files.hasOwnProperty(filename)) { |
| continue; |
| } |
| file = this.files[filename]; |
| relativePath = filename.slice(this.root.length, filename.length); |
| if (relativePath && filename.slice(0, this.root.length) === this.root) { // the file is in the current root |
| cb(relativePath, file); // TODO reverse the parameters ? need to be clean AND consistent with the filter search fn... |
| } |
| } |
| }, |
| |
| /** |
| * Filter nested files/folders with the specified function. |
| * @param {Function} search the predicate to use : |
| * function (relativePath, file) {...} |
| * It takes 2 arguments : the relative path and the file. |
| * @return {Array} An array of matching elements. |
| */ |
| filter: function(search) { |
| var result = []; |
| this.forEach(function (relativePath, entry) { |
| if (search(relativePath, entry)) { // the file matches the function |
| result.push(entry); |
| } |
| |
| }); |
| return result; |
| }, |
| |
| /** |
| * Add a file to the zip file, or search a file. |
| * @param {string|RegExp} name The name of the file to add (if data is defined), |
| * the name of the file to find (if no data) or a regex to match files. |
| * @param {String|ArrayBuffer|Uint8Array|Buffer} data The file data, either raw or base64 encoded |
| * @param {Object} o File options |
| * @return {JSZip|Object|Array} this JSZip object (when adding a file), |
| * a file (when searching by string) or an array of files (when searching by regex). |
| */ |
| file: function(name, data, o) { |
| if (arguments.length === 1) { |
| if (isRegExp(name)) { |
| var regexp = name; |
| return this.filter(function(relativePath, file) { |
| return !file.dir && regexp.test(relativePath); |
| }); |
| } |
| else { // text |
| var obj = this.files[this.root + name]; |
| if (obj && !obj.dir) { |
| return obj; |
| } else { |
| return null; |
| } |
| } |
| } |
| else { // more than one argument : we have data ! |
| name = this.root + name; |
| fileAdd.call(this, name, data, o); |
| } |
| return this; |
| }, |
| |
| /** |
| * Add a directory to the zip file, or search. |
| * @param {String|RegExp} arg The name of the directory to add, or a regex to search folders. |
| * @return {JSZip} an object with the new directory as the root, or an array containing matching folders. |
| */ |
| folder: function(arg) { |
| if (!arg) { |
| return this; |
| } |
| |
| if (isRegExp(arg)) { |
| return this.filter(function(relativePath, file) { |
| return file.dir && arg.test(relativePath); |
| }); |
| } |
| |
| // else, name is a new folder |
| var name = this.root + arg; |
| var newFolder = folderAdd.call(this, name); |
| |
| // Allow chaining by returning a new object with this folder as the root |
| var ret = this.clone(); |
| ret.root = newFolder.name; |
| return ret; |
| }, |
| |
| /** |
| * Delete a file, or a directory and all sub-files, from the zip |
| * @param {string} name the name of the file to delete |
| * @return {JSZip} this JSZip object |
| */ |
| remove: function(name) { |
| name = this.root + name; |
| var file = this.files[name]; |
| if (!file) { |
| // Look for any folders |
| if (name.slice(-1) !== "/") { |
| name += "/"; |
| } |
| file = this.files[name]; |
| } |
| |
| if (file && !file.dir) { |
| // file |
| delete this.files[name]; |
| } else { |
| // maybe a folder, delete recursively |
| var kids = this.filter(function(relativePath, file) { |
| return file.name.slice(0, name.length) === name; |
| }); |
| for (var i = 0; i < kids.length; i++) { |
| delete this.files[kids[i].name]; |
| } |
| } |
| |
| return this; |
| }, |
| |
| /** |
| * Generate the complete zip file |
| * @param {Object} options the options to generate the zip file : |
| * - compression, "STORE" by default. |
| * - type, "base64" by default. Values are : string, base64, uint8array, arraybuffer, blob. |
| * @return {String|Uint8Array|ArrayBuffer|Buffer|Blob} the zip file |
| */ |
| generate: function(options) { |
| throw new Error("This method has been removed in JSZip 3.0, please check the upgrade guide."); |
| }, |
| |
| /** |
| * Generate the complete zip file as an internal stream. |
| * @param {Object} options the options to generate the zip file : |
| * - compression, "STORE" by default. |
| * - type, "base64" by default. Values are : string, base64, uint8array, arraybuffer, blob. |
| * @return {StreamHelper} the streamed zip file. |
| */ |
| generateInternalStream: function(options) { |
| var worker, opts = {}; |
| try { |
| opts = utils.extend(options || {}, { |
| streamFiles: false, |
| compression: "STORE", |
| compressionOptions : null, |
| type: "", |
| platform: "DOS", |
| comment: null, |
| mimeType: 'application/zip', |
| encodeFileName: utf8.utf8encode |
| }); |
| |
| opts.type = opts.type.toLowerCase(); |
| opts.compression = opts.compression.toUpperCase(); |
| |
| // "binarystring" is prefered but the internals use "string". |
| if(opts.type === "binarystring") { |
| opts.type = "string"; |
| } |
| |
| if (!opts.type) { |
| throw new Error("No output type specified."); |
| } |
| |
| utils.checkSupport(opts.type); |
| |
| // accept nodejs `process.platform` |
| if( |
| opts.platform === 'darwin' || |
| opts.platform === 'freebsd' || |
| opts.platform === 'linux' || |
| opts.platform === 'sunos' |
| ) { |
| opts.platform = "UNIX"; |
| } |
| if (opts.platform === 'win32') { |
| opts.platform = "DOS"; |
| } |
| |
| var comment = opts.comment || this.comment || ""; |
| worker = generate.generateWorker(this, opts, comment); |
| } catch (e) { |
| worker = new GenericWorker("error"); |
| worker.error(e); |
| } |
| return new StreamHelper(worker, opts.type || "string", opts.mimeType); |
| }, |
| /** |
| * Generate the complete zip file asynchronously. |
| * @see generateInternalStream |
| */ |
| generateAsync: function(options, onUpdate) { |
| return this.generateInternalStream(options).accumulate(onUpdate); |
| }, |
| /** |
| * Generate the complete zip file asynchronously. |
| * @see generateInternalStream |
| */ |
| generateNodeStream: function(options, onUpdate) { |
| options = options || {}; |
| if (!options.type) { |
| options.type = "nodebuffer"; |
| } |
| return this.generateInternalStream(options).toNodejsStream(onUpdate); |
| } |
| }; |
| module.exports = out; |