/**

* jQuery-csv (jQuery Plugin)
*
* This document is licensed as free software under the terms of the
* MIT License: http://www.opensource.org/licenses/mit-license.php
*
* Acknowledgements:
* The original design and influence to implement this library as a jquery
* plugin is influenced by jquery-json (http://code.google.com/p/jquery-json/).
* If you're looking to use native JSON.Stringify but want additional backwards
* compatibility for browsers that don't support it, I highly recommend you
* check it out.
*
* A special thanks goes out to rwk@acm.org for providing a lot of valuable
* feedback to the project including the core for the new FSM
* (Finite State Machine) parsers. If you're looking for a stable TSV parser
* be sure to take a look at jquery-tsv (http://code.google.com/p/jquery-tsv/).

* For legal purposes I'll include the "NO WARRANTY EXPRESSED OR IMPLIED.
* USE AT YOUR OWN RISK.". Which, in 'layman's terms' means, by using this
* library you are accepting responsibility if it breaks your code.
*
* Legal jargon aside, I will do my best to provide a useful and stable core
* that can effectively be built on.
*
* Copyrighted 2012 by Evan Plaice.
*/

RegExp.escape = function (s) {

return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');

};

(function () {

'use strict';

var $;

// to keep backwards compatibility
if (typeof jQuery !== 'undefined' && jQuery) {
  $ = jQuery;
} else {
  $ = {};
}

/**
 * jQuery.csv.defaults
 * Encapsulates the method paramater defaults for the CSV plugin module.
 */

$.csv = {
  defaults: {
    separator: ',',
    delimiter: '"',
    headers: true
  },

  hooks: {
    castToScalar: function (value, state) {
      var hasDot = /\./;
      if (isNaN(value)) {
        return value;
      } else {
        if (hasDot.test(value)) {
          return parseFloat(value);
        } else {
          var integer = parseInt(value);
          if (isNaN(integer)) {
            return null;
          } else {
            return integer;
          }
        }
      }
    }
  },

  parsers: {
    parse: function (csv, options) {
      // cache settings
      var separator = options.separator;
      var delimiter = options.delimiter;

      // set initial state if it's missing
      if (!options.state.rowNum) {
        options.state.rowNum = 1;
      }
      if (!options.state.colNum) {
        options.state.colNum = 1;
      }

      // clear initial state
      var data = [];
      var entry = [];
      var state = 0;
      var value = '';
      var exit = false;

      function endOfEntry () {
        // reset the state
        state = 0;
        value = '';

        // if 'start' hasn't been met, don't output
        if (options.start && options.state.rowNum < options.start) {
          // update global state
          entry = [];
          options.state.rowNum++;
          options.state.colNum = 1;
          return;
        }

        if (options.onParseEntry === undefined) {
          // onParseEntry hook not set
          data.push(entry);
        } else {
          var hookVal = options.onParseEntry(entry, options.state); // onParseEntry Hook
          // false skips the row, configurable through a hook
          if (hookVal !== false) {
            data.push(hookVal);
          }
        }
        // console.log('entry:' + entry);

        // cleanup
        entry = [];

        // if 'end' is met, stop parsing
        if (options.end && options.state.rowNum >= options.end) {
          exit = true;
        }

        // update global state
        options.state.rowNum++;
        options.state.colNum = 1;
      }

      function endOfValue () {
        if (options.onParseValue === undefined) {
          // onParseValue hook not set
          entry.push(value);
        } else {
          var hook = options.onParseValue(value, options.state); // onParseValue Hook
          // false skips the row, configurable through a hook
          if (hook !== false) {
            entry.push(hook);
          }
        }
        // console.log('value:' + value);
        // reset the state
        value = '';
        state = 0;
        // update global state
        options.state.colNum++;
      }

      // escape regex-specific control chars
      var escSeparator = RegExp.escape(separator);
      var escDelimiter = RegExp.escape(delimiter);

      // compile the regEx str using the custom delimiter/separator
      var match = /(D|S|\r\n|\n|\r|[^DS\r\n]+)/;
      var matchSrc = match.source;
      matchSrc = matchSrc.replace(/S/g, escSeparator);
      matchSrc = matchSrc.replace(/D/g, escDelimiter);
      match = new RegExp(matchSrc, 'gm');

      // put on your fancy pants...
      // process control chars individually, use look-ahead on non-control chars
      csv.replace(match, function (m0) {
        if (exit) {
          return;
        }
        switch (state) {
          // the start of a value
          case 0:
            // null last value
            if (m0 === separator) {
              value += '';
              endOfValue();
              break;
            }
            // opening delimiter
            if (m0 === delimiter) {
              state = 1;
              break;
            }
            // null last value
            if (/^(\r\n|\n|\r)$/.test(m0)) {
              endOfValue();
              endOfEntry();
              break;
            }
            // un-delimited value
            value += m0;
            state = 3;
            break;

          // delimited input
          case 1:
            // second delimiter? check further
            if (m0 === delimiter) {
              state = 2;
              break;
            }
            // delimited data
            value += m0;
            state = 1;
            break;

          // delimiter found in delimited input
          case 2:
            // escaped delimiter?
            if (m0 === delimiter) {
              value += m0;
              state = 1;
              break;
            }
            // null value
            if (m0 === separator) {
              endOfValue();
              break;
            }
            // end of entry
            if (/^(\r\n|\n|\r)$/.test(m0)) {
              endOfValue();
              endOfEntry();
              break;
            }
            // broken paser?
            throw new Error('CSVDataError: Illegal State [Row:' + options.state.rowNum + '][Col:' + options.state.colNum + ']');

          // un-delimited input
          case 3:
            // null last value
            if (m0 === separator) {
              endOfValue();
              break;
            }
            // end of entry
            if (/^(\r\n|\n|\r)$/.test(m0)) {
              endOfValue();
              endOfEntry();
              break;
            }
            if (m0 === delimiter) {
            // non-compliant data
              throw new Error('CSVDataError: Illegal Quote [Row:' + options.state.rowNum + '][Col:' + options.state.colNum + ']');
            }
            // broken parser?
            throw new Error('CSVDataError: Illegal Data [Row:' + options.state.rowNum + '][Col:' + options.state.colNum + ']');
          default:
            // shenanigans
            throw new Error('CSVDataError: Unknown State [Row:' + options.state.rowNum + '][Col:' + options.state.colNum + ']');
        }
        // console.log('val:' + m0 + ' state:' + state);
      });

      // submit the last entry
      // ignore null last line
      if (entry.length !== 0) {
        endOfValue();
        endOfEntry();
      }

      return data;
    },

    // a csv-specific line splitter
    splitLines: function (csv, options) {
      if (!csv) {
        return undefined;
      }

      options = options || {};

      // cache settings
      var separator = options.separator || $.csv.defaults.separator;
      var delimiter = options.delimiter || $.csv.defaults.delimiter;

      // set initial state if it's missing
      options.state = options.state || {};
      if (!options.state.rowNum) {
        options.state.rowNum = 1;
      }

      // clear initial state
      var entries = [];
      var state = 0;
      var entry = '';
      var exit = false;

      function endOfLine () {
        // reset the state
        state = 0;

        // if 'start' hasn't been met, don't output
        if (options.start && options.state.rowNum < options.start) {
          // update global state
          entry = '';
          options.state.rowNum++;
          return;
        }

        if (options.onParseEntry === undefined) {
          // onParseEntry hook not set
          entries.push(entry);
        } else {
          var hookVal = options.onParseEntry(entry, options.state); // onParseEntry Hook
          // false skips the row, configurable through a hook
          if (hookVal !== false) {
            entries.push(hookVal);
          }
        }

        // cleanup
        entry = '';

        // if 'end' is met, stop parsing
        if (options.end && options.state.rowNum >= options.end) {
          exit = true;
        }

        // update global state
        options.state.rowNum++;
      }

      // escape regex-specific control chars
      var escSeparator = RegExp.escape(separator);
      var escDelimiter = RegExp.escape(delimiter);

      // compile the regEx str using the custom delimiter/separator
      var match = /(D|S|\n|\r|[^DS\r\n]+)/;
      var matchSrc = match.source;
      matchSrc = matchSrc.replace(/S/g, escSeparator);
      matchSrc = matchSrc.replace(/D/g, escDelimiter);
      match = new RegExp(matchSrc, 'gm');

      // put on your fancy pants...
      // process control chars individually, use look-ahead on non-control chars
      csv.replace(match, function (m0) {
        if (exit) {
          return;
        }
        switch (state) {
          // the start of a value/entry
          case 0:
            // null value
            if (m0 === separator) {
              entry += m0;
              state = 0;
              break;
            }
            // opening delimiter
            if (m0 === delimiter) {
              entry += m0;
              state = 1;
              break;
            }
            // end of line
            if (m0 === '\n') {
              endOfLine();
              break;
            }
            // phantom carriage return
            if (/^\r$/.test(m0)) {
              break;
            }
            // un-delimit value
            entry += m0;
            state = 3;
            break;

          // delimited input
          case 1:
            // second delimiter? check further
            if (m0 === delimiter) {
              entry += m0;
              state = 2;
              break;
            }
            // delimited data
            entry += m0;
            state = 1;
            break;

          // delimiter found in delimited input
          case 2:
            // escaped delimiter?
            var prevChar = entry.substr(entry.length - 1);
            if (m0 === delimiter && prevChar === delimiter) {
              entry += m0;
              state = 1;
              break;
            }
            // end of value
            if (m0 === separator) {
              entry += m0;
              state = 0;
              break;
            }
            // end of line
            if (m0 === '\n') {
              endOfLine();
              break;
            }
            // phantom carriage return
            if (m0 === '\r') {
              break;
            }
            // broken paser?
            throw new Error('CSVDataError: Illegal state [Row:' + options.state.rowNum + ']');

          // un-delimited input
          case 3:
            // null value
            if (m0 === separator) {
              entry += m0;
              state = 0;
              break;
            }
            // end of line
            if (m0 === '\n') {
              endOfLine();
              break;
            }
            // phantom carriage return
            if (m0 === '\r') {
              break;
            }
            // non-compliant data
            if (m0 === delimiter) {
              throw new Error('CSVDataError: Illegal quote [Row:' + options.state.rowNum + ']');
            }
            // broken parser?
            throw new Error('CSVDataError: Illegal state [Row:' + options.state.rowNum + ']');
          default:
            // shenanigans
            throw new Error('CSVDataError: Unknown state [Row:' + options.state.rowNum + ']');
        }
        // console.log('val:' + m0 + ' state:' + state);
      });

      // submit the last entry
      // ignore null last line
      if (entry !== '') {
        endOfLine();
      }

      return entries;
    },

    // a csv entry parser
    parseEntry: function (csv, options) {
      // cache settings
      var separator = options.separator;
      var delimiter = options.delimiter;

      // set initial state if it's missing
      if (!options.state.rowNum) {
        options.state.rowNum = 1;
      }
      if (!options.state.colNum) {
        options.state.colNum = 1;
      }

      // clear initial state
      var entry = [];
      var state = 0;
      var value = '';

      function endOfValue () {
        if (options.onParseValue === undefined) {
          // onParseValue hook not set
          entry.push(value);
        } else {
          var hook = options.onParseValue(value, options.state); // onParseValue Hook
          // false skips the value, configurable through a hook
          if (hook !== false) {
            entry.push(hook);
          }
        }
        // reset the state
        value = '';
        state = 0;
        // update global state
        options.state.colNum++;
      }

      // checked for a cached regEx first
      if (!options.match) {
        // escape regex-specific control chars
        var escSeparator = RegExp.escape(separator);
        var escDelimiter = RegExp.escape(delimiter);

        // compile the regEx str using the custom delimiter/separator
        var match = /(D|S|\n|\r|[^DS\r\n]+)/;
        var matchSrc = match.source;
        matchSrc = matchSrc.replace(/S/g, escSeparator);
        matchSrc = matchSrc.replace(/D/g, escDelimiter);
        options.match = new RegExp(matchSrc, 'gm');
      }

      // put on your fancy pants...
      // process control chars individually, use look-ahead on non-control chars
      csv.replace(options.match, function (m0) {
        switch (state) {
          // the start of a value
          case 0:
            // null last value
            if (m0 === separator) {
              value += '';
              endOfValue();
              break;
            }
            // opening delimiter
            if (m0 === delimiter) {
              state = 1;
              break;
            }
            // skip un-delimited new-lines
            if (m0 === '\n' || m0 === '\r') {
              break;
            }
            // un-delimited value
            value += m0;
            state = 3;
            break;

          // delimited input
          case 1:
            // second delimiter? check further
            if (m0 === delimiter) {
              state = 2;
              break;
            }
            // delimited data
            value += m0;
            state = 1;
            break;

          // delimiter found in delimited input
          case 2:
            // escaped delimiter?
            if (m0 === delimiter) {
              value += m0;
              state = 1;
              break;
            }
            // null value
            if (m0 === separator) {
              endOfValue();
              break;
            }
            // skip un-delimited new-lines
            if (m0 === '\n' || m0 === '\r') {
              break;
            }
            // broken paser?
            throw new Error('CSVDataError: Illegal State [Row:' + options.state.rowNum + '][Col:' + options.state.colNum + ']');

          // un-delimited input
          case 3:
            // null last value
            if (m0 === separator) {
              endOfValue();
              break;
            }
            // skip un-delimited new-lines
            if (m0 === '\n' || m0 === '\r') {
              break;
            }
            // non-compliant data
            if (m0 === delimiter) {
              throw new Error('CSVDataError: Illegal Quote [Row:' + options.state.rowNum + '][Col:' + options.state.colNum + ']');
            }
            // broken parser?
            throw new Error('CSVDataError: Illegal Data [Row:' + options.state.rowNum + '][Col:' + options.state.colNum + ']');
          default:
            // shenanigans
            throw new Error('CSVDataError: Unknown State [Row:' + options.state.rowNum + '][Col:' + options.state.colNum + ']');
        }
        // console.log('val:' + m0 + ' state:' + state);
      });

      // submit the last value
      endOfValue();

      return entry;
    }
  },

  helpers: {

    /**
     * $.csv.helpers.collectPropertyNames(objectsArray)
     * Collects all unique property names from all passed objects.
     *
     * @param {Array} objects Objects to collect properties from.
     *
     * Returns an array of property names (array will be empty,
     * if objects have no own properties).
     */
    collectPropertyNames: function (objects) {
      var o = [];
      var propName = [];
      var props = [];
      for (o in objects) {
        for (propName in objects[o]) {
          if ((objects[o].hasOwnProperty(propName)) &&
              (props.indexOf(propName) < 0) &&
              (typeof objects[o][propName] !== 'function')) {
            props.push(propName);
          }
        }
      }
      return props;
    }
  },

  /**
   * $.csv.toArray(csv)
   * Converts a CSV entry string to a javascript array.
   *
   * @param {Array} csv The string containing the CSV data.
   * @param {Object} [options] An object containing user-defined options.
   * @param {Character} [separator] An override for the separator character. Defaults to a comma(,).
   * @param {Character} [delimiter] An override for the delimiter character. Defaults to a double-quote(").
   *
   * This method deals with simple CSV strings only. It's useful if you only
   * need to parse a single entry. If you need to parse more than one line,
   * use $.csv2Array instead.
   */
  toArray: function (csv, options, callback) {
    options = (options !== undefined ? options : {});
    var config = {};
    config.callback = ((callback !== undefined && typeof (callback) === 'function') ? callback : false);
    config.separator = 'separator' in options ? options.separator : $.csv.defaults.separator;
    config.delimiter = 'delimiter' in options ? options.delimiter : $.csv.defaults.delimiter;
    var state = (options.state !== undefined ? options.state : {});

    // setup
    options = {
      delimiter: config.delimiter,
      separator: config.separator,
      onParseEntry: options.onParseEntry,
      onParseValue: options.onParseValue,
      state: state
    };

    var entry = $.csv.parsers.parseEntry(csv, options);

    // push the value to a callback if one is defined
    if (!config.callback) {
      return entry;
    } else {
      config.callback('', entry);
    }
  },

  /**
   * $.csv.toArrays(csv)
   * Converts a CSV string to a javascript array.
   *
   * @param {String} csv The string containing the raw CSV data.
   * @param {Object} [options] An object containing user-defined options.
   * @param {Character} [separator] An override for the separator character. Defaults to a comma(,).
   * @param {Character} [delimiter] An override for the delimiter character. Defaults to a double-quote(").
   *
   * This method deals with multi-line CSV. The breakdown is simple. The first
   * dimension of the array represents the line (or entry/row) while the second
   * dimension contains the values (or values/columns).
   */
  toArrays: function (csv, options, callback) {
    options = (options !== undefined ? options : {});
    var config = {};
    config.callback = ((callback !== undefined && typeof (callback) === 'function') ? callback : false);
    config.separator = 'separator' in options ? options.separator : $.csv.defaults.separator;
    config.delimiter = 'delimiter' in options ? options.delimiter : $.csv.defaults.delimiter;

    // setup
    var data = [];
    options = {
      delimiter: config.delimiter,
      separator: config.separator,
      onPreParse: options.onPreParse,
      onParseEntry: options.onParseEntry,
      onParseValue: options.onParseValue,
      onPostParse: options.onPostParse,
      start: options.start,
      end: options.end,
      state: {
        rowNum: 1,
        colNum: 1
      }
    };

    // onPreParse hook
    if (options.onPreParse !== undefined) {
      options.onPreParse(csv, options.state);
    }

    // parse the data
    data = $.csv.parsers.parse(csv, options);

    // onPostParse hook
    if (options.onPostParse !== undefined) {
      options.onPostParse(data, options.state);
    }

    // push the value to a callback if one is defined
    if (!config.callback) {
      return data;
    } else {
      config.callback('', data);
    }
  },

  /**
   * $.csv.toObjects(csv)
   * Converts a CSV string to a javascript object.
   * @param {String} csv The string containing the raw CSV data.
   * @param {Object} [options] An object containing user-defined options.
   * @param {Character} [separator] An override for the separator character. Defaults to a comma(,).
   * @param {Character} [delimiter] An override for the delimiter character. Defaults to a double-quote(").
   * @param {Boolean} [headers] Indicates whether the data contains a header line. Defaults to true.
   *
   * This method deals with multi-line CSV strings. Where the headers line is
   * used as the key for each value per entry.
   */
  toObjects: function (csv, options, callback) {
    options = (options !== undefined ? options : {});
    var config = {};
    config.callback = ((callback !== undefined && typeof (callback) === 'function') ? callback : false);
    config.separator = 'separator' in options ? options.separator : $.csv.defaults.separator;
    config.delimiter = 'delimiter' in options ? options.delimiter : $.csv.defaults.delimiter;
    config.headers = 'headers' in options ? options.headers : $.csv.defaults.headers;
    options.start = 'start' in options ? options.start : 1;

    // account for headers
    if (config.headers) {
      options.start++;
    }
    if (options.end && config.headers) {
      options.end++;
    }

    // setup
    var lines = [];
    var data = [];

    options = {
      delimiter: config.delimiter,
      separator: config.separator,
      onPreParse: options.onPreParse,
      onParseEntry: options.onParseEntry,
      onParseValue: options.onParseValue,
      onPostParse: options.onPostParse,
      start: options.start,
      end: options.end,
      state: {
        rowNum: 1,
        colNum: 1
      },
      match: false,
      transform: options.transform
    };

    // fetch the headers
    var headerOptions = {
      delimiter: config.delimiter,
      separator: config.separator,
      start: 1,
      end: 1,
      state: {
        rowNum: 1,
        colNum: 1
      }
    };

    // onPreParse hook
    if (options.onPreParse !== undefined) {
      options.onPreParse(csv, options.state);
    }

    // parse the csv
    var headerLine = $.csv.parsers.splitLines(csv, headerOptions);
    var headers = $.csv.toArray(headerLine[0], options);

    // fetch the data
    lines = $.csv.parsers.splitLines(csv, options);

    // reset the state for re-use
    options.state.colNum = 1;
    if (headers) {
      options.state.rowNum = 2;
    } else {
      options.state.rowNum = 1;
    }

    // convert data to objects
    for (var i = 0, len = lines.length; i < len; i++) {
      var entry = $.csv.toArray(lines[i], options);
      var object = {};
      for (var j = 0; j < headers.length; j++) {
        object[headers[j]] = entry[j];
      }
      if (options.transform !== undefined) {
        data.push(options.transform.call(undefined, object));
      } else {
        data.push(object);
      }

      // update row state
      options.state.rowNum++;
    }

    // onPostParse hook
    if (options.onPostParse !== undefined) {
      options.onPostParse(data, options.state);
    }

    // push the value to a callback if one is defined
    if (!config.callback) {
      return data;
    } else {
      config.callback('', data);
    }
  },

  /**
  * $.csv.fromArrays(arrays)
  * Converts a javascript array to a CSV String.
  *
  * @param {Array} arrays An array containing an array of CSV entries.
  * @param {Object} [options] An object containing user-defined options.
  * @param {Character} [separator] An override for the separator character. Defaults to a comma(,).
  * @param {Character} [delimiter] An override for the delimiter character. Defaults to a double-quote(").
  *
  * This method generates a CSV file from an array of arrays (representing entries).
  */
  fromArrays: function (arrays, options, callback) {
    options = (options !== undefined ? options : {});
    var config = {};
    config.callback = ((callback !== undefined && typeof (callback) === 'function') ? callback : false);
    config.separator = 'separator' in options ? options.separator : $.csv.defaults.separator;
    config.delimiter = 'delimiter' in options ? options.delimiter : $.csv.defaults.delimiter;

    var output = '';
    var line;
    var lineValues;
    var i;
    var j;

    for (i = 0; i < arrays.length; i++) {
      line = arrays[i];
      lineValues = [];
      for (j = 0; j < line.length; j++) {
        var strValue = (line[j] === undefined || line[j] === null) ? '' : line[j].toString();
        if (strValue.indexOf(config.delimiter) > -1) {
          strValue = strValue.replace(new RegExp(config.delimiter, 'g'), config.delimiter + config.delimiter);
        }

        var escMatcher = '\n|\r|S|D';
        escMatcher = escMatcher.replace('S', config.separator);
        escMatcher = escMatcher.replace('D', config.delimiter);

        if (strValue.search(escMatcher) > -1) {
          strValue = config.delimiter + strValue + config.delimiter;
        }
        lineValues.push(strValue);
      }
      output += lineValues.join(config.separator) + '\n';
    }

    // push the value to a callback if one is defined
    if (!config.callback) {
      return output;
    } else {
      config.callback('', output);
    }
  },

  /**
   * $.csv.fromObjects(objects)
   * Converts a javascript dictionary to a CSV string.
   *
   * @param {Object} objects An array of objects containing the data.
   * @param {Object} [options] An object containing user-defined options.
   * @param {Character} [separator] An override for the separator character. Defaults to a comma(,).
   * @param {Character} [delimiter] An override for the delimiter character. Defaults to a double-quote(").
   * @param {Character} [sortOrder] Sort order of columns (named after
   *   object properties). Use 'alpha' for alphabetic. Default is 'declare',
   *   which means, that properties will _probably_ appear in order they were
   *   declared for the object. But without any guarantee.
   * @param {Character or Array} [manualOrder] Manually order columns. May be
   * a strin in a same csv format as an output or an array of header names
   * (array items won't be parsed). All the properties, not present in
   * `manualOrder` will be appended to the end in accordance with `sortOrder`
   * option. So the `manualOrder` always takes preference, if present.
   *
   * This method generates a CSV file from an array of objects (name:value pairs).
   * It starts by detecting the headers and adding them as the first line of
   * the CSV file, followed by a structured dump of the data.
   */
  fromObjects: function (objects, options, callback) {
    options = (options !== undefined ? options : {});
    var config = {};
    config.callback = ((callback !== undefined && typeof (callback) === 'function') ? callback : false);
    config.separator = 'separator' in options ? options.separator : $.csv.defaults.separator;
    config.delimiter = 'delimiter' in options ? options.delimiter : $.csv.defaults.delimiter;
    config.headers = 'headers' in options ? options.headers : $.csv.defaults.headers;
    config.sortOrder = 'sortOrder' in options ? options.sortOrder : 'declare';
    config.manualOrder = 'manualOrder' in options ? options.manualOrder : [];
    config.transform = options.transform;

    if (typeof config.manualOrder === 'string') {
      config.manualOrder = $.csv.toArray(config.manualOrder, config);
    }

    if (config.transform !== undefined) {
      var origObjects = objects;
      objects = [];

      var i;
      for (i = 0; i < origObjects.length; i++) {
        objects.push(config.transform.call(undefined, origObjects[i]));
      }
    }

    var props = $.csv.helpers.collectPropertyNames(objects);

    if (config.sortOrder === 'alpha') {
      props.sort();
    } // else {} - nothing to do for 'declare' order

    if (config.manualOrder.length > 0) {
      var propsManual = [].concat(config.manualOrder);
      let p;
      for (p = 0; p < props.length; p++) {
        if (propsManual.indexOf(props[p]) < 0) {
          propsManual.push(props[p]);
        }
      }
      props = propsManual;
    }

    var o, p, line, output, propName;
    if (config.headers) {
      output.push(props);
    }

    for (o = 0; o < objects.length; o++) {
      line = [];
      for (p = 0; p < props.length; p++) {
        propName = props[p];
        if (propName in objects[o] && typeof objects[o][propName] !== 'function') {
          line.push(objects[o][propName]);
        } else {
          line.push('');
        }
      }
      output.push(line);
    }

    // push the value to a callback if one is defined
    return $.csv.fromArrays(output, options, config.callback);
  }
};

// Maintenance code to maintain backward-compatibility
// Will be removed in release 1.0
$.csvEntry2Array = $.csv.toArray;
$.csv2Array = $.csv.toArrays;
$.csv2Dictionary = $.csv.toObjects;

// CommonJS module is defined
if (typeof module !== 'undefined' && module.exports) {
  module.exports = $.csv;
}

}).call(this);