Source: lib/csv.js

/**
 * @copyright Copyright (c) 2015 All Rights Reserved.
 * @author Baris Yuksel <baris@onehundredyearsofcode.com>
 *
 * @file Module that exports the csv() class. Should be used
*  with new() to create new parser classes.
 */
exports = module.exports = csv;

var csvdata = require('./csvdata.js'),
    private = require ('./private.js');

/**
 * Creates a new csv parser.
 * @constructor
 */
function csv() {}

/**
 * Converts CSV string to JSON.
 * @param {string} input - csv string
 * @param {dictionary} argdic - arguments for parsing the csv string
 * @return {string} JSON represantion of the csv string.
 * @example
 * CSVToJSON("magician, born\r\nhoudini, 1874\r\ncopperfield, 1956\r\n",
 *           {hasHeaders: true});
 * // outputs:
 *   [ { "magician":"houdini",
 *       "born": 1874 },
 *     { "magician":"copperfield",
 *       "born":1956 } ]
 */
csv.prototype.CSVToJSON = function(input, argdic) {
  return this.csvdataToJSON(this.parseString(input, argdic));
};

/**
 * Converts JSON to CSV string.
 * @param {string} input - JSON table string
 * @param {dictionary} argdic - arguments for creating the csv string.
 * @return {string} csv string corresponding to the JSON input.
 * @example
 * JSONToCSV( [ { "magician":"houdini", "born": 1874 },
 *              { "magician":"copperfield", "born":1956 } ]);
 * // outputs:
 * "magician, born\r\nhoudini, 1874\r\ncopperfield, 1956\r\n",
 */
csv.prototype.JSONToCSV = function(input, argdic) {
  return this.csvdataToString(this.JSONToCsvdata(input), argdic);
};

/**
 * Produces simple string output of csvdata.
 * @param {string} input - a csvdata object.
 * @param {dictionary} argdic - arguments for creating the csv string.
 * @return {string} csv string corresponding to the csvdata object.
 * @example
 * // Given csvdata of:
 * { column_names: { "magician", "born"},
 *   rows: { { "houdini", 1874 },
 *           { "copperfield", 1956} }
 * }
 * csvdataToString()  // will produce
 * "magician, born\r\nhoudini, 1874\r\ncopperfield, 1956\r\n"
 */
csv.prototype.csvdataToString = function(input, argdic) {
  var delim = private.getArg(argdic, 'delim', ',');
  var str = '';
  var columnnames = input.columnNames;
  if (typeof columnnames !== 'undefined') {
    for (var i = 0; i < columnnames.length; i++) {
      str += private.doubleQuoteIfNecessary(columnnames[i]) + delim;
    }
    if (str.length > 0) {
      str = str.slice(0, - 1) + '\r\n';
    }
  }

  var rows = input.rows;
  if (typeof rows === 'undefined') return str;

  for (var k = 0; k < rows.length; k++) {
    var temp = '';
    for (var m = 0; m < rows[k].length; m++) {
      temp += private.doubleQuoteIfNecessary(rows[k][m]) + delim;
    }
    if (temp.length > 0) {
      temp = temp.slice(0, - 1);
    }
    str += temp + '\r\n';
  }
  return str;
};

/**
 * Produces the JSON of the csvdata.
 * Uses JSON table schema: {@link http://dataprotocols.org/json-table-schema/}
 * @param {string} input - a csvdata object.
 * @return {string} JSON represantion of the csvdata object.
 * @example
 * // Given csvdata of:
 * { column_names: { "magician", "born"},
 *   rows: { { "houdini", 1874 },
 *           { "copperfield", 1956} }
 * }
 *
 * csvdataToJSON() // will produce:
 *   [ { "magician":"houdini",
 *       "born": 1874 },
 *     { "magician":"copperfield",
 *       "born":1956 } ]
 */
csv.prototype.csvdataToJSON = function(input) {
  var columnnames = input.columnNames;

  // No columnnames, let's fill it up with generated names
  if ((typeof columnnames === 'undefined' || columnnames.length === 0) &&
      input.columnCount !== 0) {
    var digitCount = input.columnCount.toString().length;
    columnnames = [];  // Don't modify input's columnnames, assign a new array

    for (var m = 0; m < input.columnCount; m++) {
      var neededZeroCount = digitCount - m.toString().length;
      var fullname = 'Col ' + Array(neededZeroCount + 1).join('0') + m.toString();
      columnnames.push(fullname);
    }
  }

  var rows = input.rows;
  var output = [];

  for (var i = 0; i < rows.length; i++) {
    var entry = {};
    for (var j = 0; j < rows[i].length; j++) {
      entry[columnnames[j]] = rows[i][j];
    }
    output.push(entry);
  }
  return JSON.stringify(output);
};

/**
 * Produces the csvdata of table formatted JSON
 * Uses JSON table schema: {@link http://dataprotocols.org/json-table-schema/}
 * @param {string} - JSON table.
 * @return {csvdata} the csvdata object representing the JSON input.
 * @example
 * // Given JSON of:
 *   {  "magician":"houdini",
 *      "born": 1874   },
 *   {   "magician":"copperfield",
 *       "born":1956   }
 * // will produce
 * { column_names: { "magician", "born"},
 *   rows: { { "houdini", 1874 },
 *           { "copperfield", 1956} }
 * }
 */
csv.prototype.JSONToCsvdata = function(jsonStr) {
  var obj = JSON.parse(jsonStr);
  var rows = [];
  for (var i = 0; i < obj.length; i++) {
    var line = [];
    for (var cell in obj[i]) {
      if (obj[i].hasOwnProperty(cell)) {
        line.push(obj[i][cell]);
      }
    }
    rows.push(line);
  }
  var columnnames = [];
  for (var attr in obj[0]) {
    if (obj[0].hasOwnProperty(attr)) {
      columnnames.push(attr);
    }
  }
  var output = { rows: rows,
                 columnNames: columnnames,
                 rowCount: rows.length,
                 columnCount: columnnames.length
               };
  return this.makeCsvdataFromObj(output);
};

/**
 * Produces the csvdata of csv string.
 * @param {string} input - csv string
 * @param {dictionary} argdic - arguments for parsing the csv string
 * @return {csvdata} the csvdata object representing the csv string.
 * @example
 * parseString("magician, born\r\nhoudini, 1874\r\ncopperfield, 1956\r\n",
 *           {hasHeaders: true});
 * // will produce
 * { column_names: { "magician", "born"},
 *   rows: { { "houdini", 1874 },
 *           { "copperfield", 1956} }
 * }
 */
csv.prototype.parseString= function(str, argdic) {
  var parsed = private.parseStringToArray(str, argdic);
  var retValue = new csvdata();
  var hasHeaders = private.getArg(argdic, 'hasHeaders', false);

  if (hasHeaders) {
    retValue.columnNames = parsed.shift();
    if (typeof retValue.columnNames !== 'undefined') {
      retValue.columnCount = retValue.columnNames.length;
    }
  }

  retValue.rows = parsed;
  retValue.rowCount = parsed.length;
  if (retValue.columnCount === 0 && retValue.rowCount > 0) {
    retValue.columnCount = retValue.rows[0].length;
  }
  return retValue;
};

/**
 * Can take an object and if the object has the same properties
 * as csvdata, it creates a csvdata from those properties by deep copying.
 * @param {object} - obj Any JS object with csvdata like properties.
 * @return {csvdata} a new csvdata object deep copied from obj.
 */
csv.prototype.makeCsvdataFromObj= function(obj) {
  if (typeof obj === 'undefined') return;
  var retVal = new csvdata();

  if (typeof obj.columnCount !== 'undefined') {
    retVal.columnCount = obj.columnCount;
  }
  if (typeof obj.columnNames !== 'undefined') {
    retVal.columnNames = obj.columnNames.slice();
  }
  if (typeof obj.rows !== 'undefined') {
    retVal.rows = [];
    for (var i = 0; i < obj.rows.length; i++) {
      retVal.rows.push(obj.rows[i].slice());
    }
  }
  if (typeof obj.columnCount !== 'undefined') {
    retVal.rowCount = obj.rowCount;
  }
  return retVal;
};

/**
 * Find errors.
 * @param {csvdata} - input a csvdata object.
 * @return {Array<string>} The errors in the csvdata object.
 */
csv.prototype.findErrors= function(input) {
  // Check length of rows
  var errors = [];
  var rows = input.rows;
  var columncount = rows[0].length;
  var types = [];
  for (var j = 0; j < rows[0].length; j++) {
    types.push(typeof rows[0][j]);
  }
  for (var r_index = 0; r_index < rows.length; r_index++) {
    for (var c_index = 0; c_index < rows[r_index].length; c_index++) {
      var actualtype =  typeof rows[r_index][c_index];
      if ( actualtype !== types[c_index]) {
        errors.push('Type mismatch at row:' + r_index + ' col:' + c_index +
                      ' expected:' + types[c_index] + ' actual:' +  actualtype);
      }
    }
  }
  if (input.columnCount !== columncount) {
    errors.push('Column count is ' + input.columnCount +
                  ' but Row 0 has ' + columncount + ' cols');
  }
  for (var i = 0; i < rows.length; i++) {
    if (rows[i].length !== columncount) {
      errors.push('Row ' + i + ' has ' + rows[i].length +
                    ' cols, Row 0 has ' + columncount);
    }
  }
  return errors;
};