Team:William and Mary/jquery csvplugin

/**

* 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 (undefined) {

 '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) {
       // 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;
       }
       // 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, propName, 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 = ,
         line,
         lineValues,
         i, 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) + '\r\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);
       var 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 ); console.log('jquery csv finished running')