lamchau
9/4/2014 - 9:21 PM

file-saver.js

// https://raw.githubusercontent.com/eligrey/FileSaver.js/master/FileSaver.js
/* FileSaver.js
 * A saveAs() FileSaver implementation.
 * 2014-08-29
 *
 * By Eli Grey, http://eligrey.com
 * License: X11/MIT
 *   See https://github.com/eligrey/FileSaver.js/blob/master/LICENSE.md
 */

/*global self */
/*jslint bitwise: true, indent: 4, laxbreak: true, laxcomma: true, smarttabs: true, plusplus: true */

/*! @source http://purl.eligrey.com/github/FileSaver.js/blob/master/FileSaver.js */

var saveAs = saveAs
  // IE 10+ (native saveAs)
  || (typeof navigator !== "undefined" &&
      navigator.msSaveOrOpenBlob && navigator.msSaveOrOpenBlob.bind(navigator))
  // Everyone else
  || (function(view) {
	"use strict";
	// IE <10 is explicitly unsupported
	if (typeof navigator !== "undefined" &&
	    /MSIE [1-9]\./.test(navigator.userAgent)) {
		return;
	}
	var
		  doc = view.document
		  // only get URL when necessary in case Blob.js hasn't overridden it yet
		, get_URL = function() {
			return view.URL || view.webkitURL || view;
		}
		, save_link = doc.createElementNS("http://www.w3.org/1999/xhtml", "a")
		, can_use_save_link = "download" in save_link
		, click = function(node) {
			var event = doc.createEvent("MouseEvents");
			event.initMouseEvent(
				"click", true, false, view, 0, 0, 0, 0, 0
				, false, false, false, false, 0, null
			);
			node.dispatchEvent(event);
		}
		, webkit_req_fs = view.webkitRequestFileSystem
		, req_fs = view.requestFileSystem || webkit_req_fs || view.mozRequestFileSystem
		, throw_outside = function(ex) {
			(view.setImmediate || view.setTimeout)(function() {
				throw ex;
			}, 0);
		}
		, force_saveable_type = "application/octet-stream"
		, fs_min_size = 0
		// See https://code.google.com/p/chromium/issues/detail?id=375297#c7 for
		// the reasoning behind the timeout and revocation flow
		, arbitrary_revoke_timeout = 10
		, revoke = function(file) {
			var revoker = function() {
				if (typeof file === "string") { // file is an object URL
					get_URL().revokeObjectURL(file);
				} else { // file is a File
					file.remove();
				}
			};
			if (view.chrome) {
				revoker();
			} else {
				setTimeout(revoker, arbitrary_revoke_timeout);
			}
		}
		, dispatch = function(filesaver, event_types, event) {
			event_types = [].concat(event_types);
			var i = event_types.length;
			while (i--) {
				var listener = filesaver["on" + event_types[i]];
				if (typeof listener === "function") {
					try {
						listener.call(filesaver, event || filesaver);
					} catch (ex) {
						throw_outside(ex);
					}
				}
			}
		}
		, FileSaver = function(blob, name) {
			// First try a.download, then web filesystem, then object URLs
			var
				  filesaver = this
				, type = blob.type
				, blob_changed = false
				, object_url
				, target_view
				, dispatch_all = function() {
					dispatch(filesaver, "writestart progress write writeend".split(" "));
				}
				// on any filesys errors revert to saving with object URLs
				, fs_error = function() {
					// don't create more object URLs than needed
					if (blob_changed || !object_url) {
						object_url = get_URL().createObjectURL(blob);
					}
					if (target_view) {
						target_view.location.href = object_url;
					} else {
						var new_tab = view.open(object_url, "_blank");
						if (new_tab == undefined && typeof safari !== "undefined") {
							//Apple do not allow window.open, see http://bit.ly/1kZffRI
							view.location.href = object_url
						}
					}
					filesaver.readyState = filesaver.DONE;
					dispatch_all();
					revoke(object_url);
				}
				, abortable = function(func) {
					return function() {
						if (filesaver.readyState !== filesaver.DONE) {
							return func.apply(this, arguments);
						}
					};
				}
				, create_if_not_found = {create: true, exclusive: false}
				, slice
			;
			filesaver.readyState = filesaver.INIT;
			if (!name) {
				name = "download";
			}
			if (can_use_save_link) {
				object_url = get_URL().createObjectURL(blob);
				save_link.href = object_url;
				save_link.download = name;
				click(save_link);
				filesaver.readyState = filesaver.DONE;
				dispatch_all();
				revoke(object_url);
				return;
			}
			// Object and web filesystem URLs have a problem saving in Google Chrome when
			// viewed in a tab, so I force save with application/octet-stream
			// http://code.google.com/p/chromium/issues/detail?id=91158
			// Update: Google errantly closed 91158, I submitted it again:
			// https://code.google.com/p/chromium/issues/detail?id=389642
			if (view.chrome && type && type !== force_saveable_type) {
				slice = blob.slice || blob.webkitSlice;
				blob = slice.call(blob, 0, blob.size, force_saveable_type);
				blob_changed = true;
			}
			// Since I can't be sure that the guessed media type will trigger a download
			// in WebKit, I append .download to the filename.
			// https://bugs.webkit.org/show_bug.cgi?id=65440
			if (webkit_req_fs && name !== "download") {
				name += ".download";
			}
			if (type === force_saveable_type || webkit_req_fs) {
				target_view = view;
			}
			if (!req_fs) {
				fs_error();
				return;
			}
			fs_min_size += blob.size;
			req_fs(view.TEMPORARY, fs_min_size, abortable(function(fs) {
				fs.root.getDirectory("saved", create_if_not_found, abortable(function(dir) {
					var save = function() {
						dir.getFile(name, create_if_not_found, abortable(function(file) {
							file.createWriter(abortable(function(writer) {
								writer.onwriteend = function(event) {
									target_view.location.href = file.toURL();
									filesaver.readyState = filesaver.DONE;
									dispatch(filesaver, "writeend", event);
									revoke(file);
								};
								writer.onerror = function() {
									var error = writer.error;
									if (error.code !== error.ABORT_ERR) {
										fs_error();
									}
								};
								"writestart progress write abort".split(" ").forEach(function(event) {
									writer["on" + event] = filesaver["on" + event];
								});
								writer.write(blob);
								filesaver.abort = function() {
									writer.abort();
									filesaver.readyState = filesaver.DONE;
								};
								filesaver.readyState = filesaver.WRITING;
							}), fs_error);
						}), fs_error);
					};
					dir.getFile(name, {create: false}, abortable(function(file) {
						// delete file if it already exists
						file.remove();
						save();
					}), abortable(function(ex) {
						if (ex.code === ex.NOT_FOUND_ERR) {
							save();
						} else {
							fs_error();
						}
					}));
				}), fs_error);
			}), fs_error);
		}
		, FS_proto = FileSaver.prototype
		, saveAs = function(blob, name) {
			return new FileSaver(blob, name);
		}
	;
	FS_proto.abort = function() {
		var filesaver = this;
		filesaver.readyState = filesaver.DONE;
		dispatch(filesaver, "abort");
	};
	FS_proto.readyState = FS_proto.INIT = 0;
	FS_proto.WRITING = 1;
	FS_proto.DONE = 2;

	FS_proto.error =
	FS_proto.onwritestart =
	FS_proto.onprogress =
	FS_proto.onwrite =
	FS_proto.onabort =
	FS_proto.onerror =
	FS_proto.onwriteend =
		null;

	return saveAs;
}(
	   typeof self !== "undefined" && self
	|| typeof window !== "undefined" && window
	|| this.content
));
// `self` is undefined in Firefox for Android content script context
// while `this` is nsIContentFrameMessageManager
// with an attribute `content` that corresponds to the window

if (typeof module !== "undefined" && module !== null) {
  module.exports = saveAs;
} else if ((typeof define !== "undefined" && define !== null) && (define.amd != null)) {
  define([], function() {
    return saveAs;
  });
}
// https://raw.githubusercontent.com/bkeepers/rosie/v0.3.1/src/rosie.js
/**
 * Creates a new factory with attributes, options, etc. to be used to build
 * objects. Generally you should use `Factory.define()` instead of this
 * constructor.
 *
 * @param {?Function} constructor
 * @class
 */
var Factory = function(constructor) {
  this.construct = constructor;
  this.attrs = {};
  this.opts = {};
  this.sequences = {};
  this.callbacks = [];
};

Factory.prototype = {
  /**
   * Define an attribute on this factory. Attributes can optionally define a
   * default value, either as a value (e.g. a string or number) or as a builder
   * function. For example:
   *
   *   // no default value for age
   *   Factory.define('Person').attr('age')
   *
   *   // static default value for age
   *   Factory.define('Person').attr('age', 18)
   *
   *   // dynamic default value for age
   *   Factory.define('Person').attr('age', function() {
   *      return Math.random() * 100;
   *   })
   *
   * Attributes with dynamic default values can depend on options or other
   * attributes:
   *
   *   Factory.define('Person').attr('age', ['name'], function(name) {
   *     return name === 'Brian' ? 30 : 18;
   *   });
   *
   * By default if the consumer of your factory provides a value for an
   * attribute your builder function will not be called. You can override this
   * behavior by declaring that your attribute depends on itself:
   *
   *   Factory.define('Person').attr('spouse', ['spouse'], function(spouse) {
   *     return Factory.build('Person', spouse);
   *   });
   *
   * As in the example above, this can be a useful way to fill in
   * partially-specified child objects.
   *
   * @param {string} attr
   * @param {?Array.<string>} dependencies
   * @param {*} value
   * @return {Factory}
   */
  attr: function(attr, dependencies, value) {
    var builder;
    if (arguments.length === 2) {
      value = dependencies;
      dependencies = null;
    }

    builder = typeof value === 'function' ? value : function() { return value; };
    this.attrs[attr] = { dependencies: dependencies || [], builder: builder };
    return this;
  },

  /**
   * Define an option for this factory. Options are values that may inform
   * dynamic attribute behavior but are not included in objects built by the
   * factory. Like attributes, options may have dependencies. Unlike
   * attributes, options may only depend on other options.
   *
   *   Factory.define('Person')
   *     .option('includeRelationships', false)
   *     .attr(
   *       'spouse',
   *       ['spouse', 'includeRelationships'],
   *       function(spouse, includeRelationships) {
   *         return includeRelationships ?
   *           Factory.build('Person', spouse) :
   *           null;
   *       });
   *
   *   Factory.build('Person', null, { includeRelationships: true });
   *
   * Options may have either static or dynamic default values, just like
   * attributes. Options without default values must have a value specified
   * when building.
   *
   * @param {string} attr
   * @param {?Array.<string>} dependencies
   * @param {?*} value
   * @return {Factory}
   */
  option: function(opt, dependencies, value) {
    var builder;
    if (arguments.length === 2) {
      value = dependencies;
      dependencies = null;
    }
    if (arguments.length > 1) {
      builder = typeof value === 'function' ? value : function() { return value; };
    }
    this.opts[opt] = { dependencies: dependencies || [], builder: builder };
    return this;
  },

  /**
   * Defines an attribute that, by default, simply has an auto-incrementing
   * numeric value starting at 1. You can provide your own builder function
   * that accepts the number of the sequence and returns whatever value you'd
   * like it to be.
   *
   * Sequence values are inherited such that a factory derived from another
   * with a sequence will share the state of that sequence and they will never
   * conflict.
   *
   *   Factory.define('Person').sequence('id');
   *
   * @param {string} attr
   * @param {?function(number): *} builder
   * @return {Factory}
   */
  sequence: function(attr, builder) {
    var factory = this;
    builder = builder || function(i) { return i; };
    return this.attr(attr, function() {
      factory.sequences[attr] = factory.sequences[attr] || 0;
      return builder(++factory.sequences[attr]);
    });
  },

  /**
   * Sets a post-processor callback that will receive built objects and the
   * options for the build just before they are returned from the #build
   * function.
   *
   * @param {function(object, ?object)} callback
   * @return {Factory}
   */
  after: function(callback) {
    this.callbacks.push(callback);
    return this;
  },

  /**
   * Sets the constructor for this factory to be another factory. This can be
   * used to create more specific sub-types of factories.
   *
   * @param {Factory}
   * @return {Factory}
   */
  inherits: function(parentFactory) {
    this.construct = function(attributes, options) {
      return Factory.build(parentFactory, attributes, options);
    };
    return this;
  },

  /**
   * Builds a plain object containing values for each of the declared
   * attributes. The result of this is the same as the result when using #build
   * when there is no constructor registered.
   *
   * @param {?object} attributes
   * @param {?object} options
   * @return {object}
   */
  attributes: function(attributes, options) {
    attributes = Factory.util.extend({}, attributes);
    options = this.options(options);
    for (var attr in this.attrs) {
      this._attrValue(attr, attributes, options, [attr]);
    }
    return attributes;
  },

  /**
   * Generates a value for the given named attribute and adds the result to the
   * given attributes list.
   *
   * @private
   * @param {string} attr
   * @param {object} attributes
   * @param {object} options
   * @param {Array.<string>} stack
   * @return {*}
   */
  _attrValue: function(attr, attributes, options, stack) {
    if (!this._alwaysCallBuilder(attr) && Factory.util.hasOwnProp(attributes, attr)) {
      return attributes[attr];
    }

    var value = this._buildWithDependencies(this.attrs[attr], function(dep) {
      if (Factory.util.hasOwnProp(options, dep)) {
        return options[dep];
      } else if (dep === attr) {
        return attributes[dep];
      } else if (stack.indexOf(dep) >= 0) {
        throw new Error('detected a dependency cycle: '+stack.concat([dep]).join(' -> '));
      } else {
        return this._attrValue(dep, attributes, options, stack.concat([dep]));
      }
    });
    attributes[attr] = value;
    return value;
  },

  /**
   * Determines whether the given named attribute has listed itself as a
   * dependency.
   *
   * @private
   * @param {string} attr
   * @return {boolean}
   */
  _alwaysCallBuilder: function(attr) {
    var attrMeta = this.attrs[attr];
    return attrMeta.dependencies.indexOf(attr) >= 0;
  },

  /**
   * Generates values for all the registered options using the values given.
   *
   * @private
   * @param {object} options
   * @return {object}
   */
  options: function(options) {
    options = options || {};
    for (var opt in this.opts) {
      options[opt] = this._optionValue(opt, options);
    }
    return options;
  },

  /**
   * Generates a value for the given named option and adds the result to the
   * given options list.
   *
   * @private
   * @param {string}
   * @param {object} options
   * @return {*}
   */
  _optionValue: function(opt, options) {
    if (Factory.util.hasOwnProp(options, opt)) {
      return options[opt];
    }

    var optMeta = this.opts[opt];
    if (!optMeta.builder) {
      throw new Error('option `'+opt+'` has no default value and none was provided');
    }

    return this._buildWithDependencies(optMeta, function(dep) {
      return this._optionValue(dep, options);
    });
  },

  /**
   * Calls the builder function with its dependencies as determined by the
   * given dependency resolver.
   *
   * @private
   * @param {{builder: function(...[*]): *, dependencies: Array.<string>}} meta
   * @param {function(string): *} getDep
   * @return {*}
   */
  _buildWithDependencies: function(meta, getDep) {
    var deps = meta.dependencies;
    var self = this;
    var args = deps.map(function(){ return getDep.apply(self, arguments); });
    return meta.builder.apply(this, args);
  },

  /**
   * Builds objects by getting values for all attributes and optionally passing
   * the result to a constructor function.
   *
   * @param {object} attributes
   * @param {object} options
   * @return {*}
   */
  build: function(attributes, options) {
    var result = this.attributes(attributes, options);
    var retval = null;

    if (this.construct) {
      if (typeof this.construct.create === 'function') {
        retval = this.construct.create(result);
      } else {
        retval = new this.construct(result);
      }
    } else {
      retval = result;
    }

    for (var i = 0; i < this.callbacks.length; i++) {
      this.callbacks[i](retval, this.options(options));
    }
    return retval;
  },

  /**
   * Extends a given factory by copying over its attributes, options,
   * callbacks, and constructor. This can be useful when you want to make
   * different types which all share certain attributes.
   *
   * @param {string} name The factory to extend.
   * @return {Factory}
   */
  extend: function(name) {
    var factory = Factory.factories[name];
    // Copy the parent's constructor
    if (this.construct === undefined) { this.construct = factory.construct; }
    Factory.util.extend(this.attrs, factory.attrs);
    Factory.util.extend(this.opts, factory.opts);
    // Copy the parent's callbacks
    this.callbacks = factory.callbacks.slice();
    return this;
  }
};

/**
 * @private
 */
Factory.util = (function() {
  var hasOwnProp = Object.prototype.hasOwnProperty;

  return {
    /**
     * Determines whether `object` has its own property named `prop`.
     *
     * @private
     * @param {object} object
     * @param {string} prop
     * @return {boolean}
     */
    hasOwnProp: function(object, prop) {
      return hasOwnProp.call(object, prop);
    },

    /**
     * Extends `dest` with all of own properties of `source`.
     *
     * @private
     * @param {object} dest
     * @param {?object} source
     * @return {object}
     */
    extend: function(dest, source) {
      if (source) {
        for (var key in source) {
          if (hasOwnProp.call(source, key)) {
            dest[key] = source[key];
          }
        }
      }
      return dest;
    }
  };
})();

Factory.factories = {};

/**
 * Defines a factory by name and constructor function. Call #attr and #option
 * on the result to define the properties of this factory.
 *
 * @param {string} name
 * @param {?function(object): *} constructor
 * @return {Factory}
 */
Factory.define = function(name, constructor) {
  var factory = new Factory(constructor);
  this.factories[name] = factory;
  return factory;
};

/**
 * Locates a factory by name and calls #build on it.
 *
 * @param {string} name
 * @param {object} attributes
 * @param {object} options
 * @return {*}
 */
Factory.build = function(name, attributes, options) {
  if (!this.factories[name])
    throw new Error('The "'+name+'" factory is not defined.');
  return this.factories[name].build(attributes, options);
};

/**
 * Builds a collection of objects using the named factory.
 *
 * @param {string} name
 * @param {number} size
 * @param {object} attributes
 * @param {object} options
 * @return {Array.<*>}
 */
Factory.buildList = function(name, size, attributes, options) {
  var objs = [];
  for (var i = 0; i < size; i++) {
    objs.push(Factory.build(name, attributes, options));
  }
  return objs;
};

/**
 * Locates a factory by name and calls #attributes on it.
 *
 * @param {string} name
 * @param {object} attributes
 * @param {object} options
 * @return {object}
 */
Factory.attributes = function(name, attributes, options) {
  return this.factories[name].attributes(attributes, options);
};

if (typeof exports != "undefined") {
  exports.Factory = Factory;
}