1028 lines
35 KiB
JavaScript
1028 lines
35 KiB
JavaScript
/* eslint no-prototype-builtins: 0 */
|
|
/**
|
|
* 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'
|
|
|
|
let $
|
|
|
|
// 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) {
|
|
const hasDot = /\./
|
|
if (isNaN(value)) {
|
|
return value
|
|
} else {
|
|
if (hasDot.test(value)) {
|
|
return parseFloat(value)
|
|
} else {
|
|
const integer = parseInt(value)
|
|
if (isNaN(integer)) {
|
|
return null
|
|
} else {
|
|
return integer
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
parsers: {
|
|
parse: function (csv, options) {
|
|
// cache settings
|
|
const separator = options.separator
|
|
const 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
|
|
const data = []
|
|
let entry = []
|
|
let state = 0
|
|
let value = ''
|
|
let 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 {
|
|
const 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 if (options.headers && options.state.rowNum === 1) {
|
|
// don't onParseValue object headers
|
|
entry.push(value)
|
|
} else {
|
|
const 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
|
|
const escSeparator = RegExp.escape(separator)
|
|
const escDelimiter = RegExp.escape(delimiter)
|
|
|
|
// compile the regEx str using the custom delimiter/separator
|
|
let match = /(D|S|\r\n|\n|\r|[^DS\r\n]+)/
|
|
let 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 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 Error('CSVDataError: Illegal Quote [Row:' + options.state.rowNum + '][Col:' + options.state.colNum + ']')
|
|
}
|
|
// broken parser?
|
|
throw Error('CSVDataError: Illegal Data [Row:' + options.state.rowNum + '][Col:' + options.state.colNum + ']')
|
|
default:
|
|
// shenanigans
|
|
throw 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
|
|
const separator = options.separator || $.csv.defaults.separator
|
|
const 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
|
|
const entries = []
|
|
let state = 0
|
|
let entry = ''
|
|
let 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 {
|
|
const 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
|
|
const escSeparator = RegExp.escape(separator)
|
|
const escDelimiter = RegExp.escape(delimiter)
|
|
|
|
// compile the regEx str using the custom delimiter/separator
|
|
let match = /(D|S|\n|\r|[^DS\r\n]+)/
|
|
let 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?
|
|
const 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 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 Error('CSVDataError: Illegal quote [Row:' + options.state.rowNum + ']')
|
|
}
|
|
// broken parser?
|
|
throw Error('CSVDataError: Illegal state [Row:' + options.state.rowNum + ']')
|
|
default:
|
|
// shenanigans
|
|
throw 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
|
|
const separator = options.separator
|
|
const 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
|
|
const entry = []
|
|
let state = 0
|
|
let value = ''
|
|
|
|
function endOfValue () {
|
|
if (options.onParseValue === undefined) {
|
|
// onParseValue hook not set
|
|
entry.push(value)
|
|
} else {
|
|
const 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
|
|
const escSeparator = RegExp.escape(separator)
|
|
const escDelimiter = RegExp.escape(delimiter)
|
|
|
|
// compile the regEx str using the custom delimiter/separator
|
|
const match = /(D|S|\n|\r|[^DS\r\n]+)/
|
|
let 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 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 Error('CSVDataError: Illegal Quote [Row:' + options.state.rowNum + '][Col:' + options.state.colNum + ']')
|
|
}
|
|
// broken parser?
|
|
throw Error('CSVDataError: Illegal Data [Row:' + options.state.rowNum + '][Col:' + options.state.colNum + ']')
|
|
default:
|
|
// shenanigans
|
|
throw 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) {
|
|
let o = []
|
|
let propName = []
|
|
const 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) {
|
|
// if callback was passed to options swap callback with options
|
|
if (options !== undefined && typeof (options) === 'function') {
|
|
if (callback !== undefined) {
|
|
return console.error('You cannot 3 arguments with the 2nd argument being a function')
|
|
}
|
|
callback = options
|
|
options = {}
|
|
}
|
|
|
|
options = (options !== undefined ? options : {})
|
|
const 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
|
|
const state = (options.state !== undefined ? options.state : {})
|
|
|
|
// setup
|
|
options = {
|
|
delimiter: config.delimiter,
|
|
separator: config.separator,
|
|
onParseEntry: options.onParseEntry,
|
|
onParseValue: options.onParseValue,
|
|
state: state
|
|
}
|
|
|
|
const 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) {
|
|
// if callback was passed to options swap callback with options
|
|
if (options !== undefined && typeof (options) === 'function') {
|
|
if (callback !== undefined) {
|
|
return console.error('You cannot 3 arguments with the 2nd argument being a function')
|
|
}
|
|
callback = options
|
|
options = {}
|
|
}
|
|
|
|
options = (options !== undefined ? options : {})
|
|
const 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
|
|
let 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) {
|
|
csv = options.onPreParse(csv, options.state)
|
|
}
|
|
|
|
// parse the data
|
|
data = $.csv.parsers.parse(csv, options)
|
|
|
|
// onPostParse hook
|
|
if (options.onPostParse !== undefined) {
|
|
data = 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) {
|
|
// if callback was passed to options swap callback with options
|
|
if (options !== undefined && typeof (options) === 'function') {
|
|
if (callback !== undefined) {
|
|
return console.error('You cannot 3 arguments with the 2nd argument being a function')
|
|
}
|
|
callback = options
|
|
options = {}
|
|
}
|
|
|
|
options = (options !== undefined ? options : {})
|
|
const 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
|
|
let lines = []
|
|
let 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
|
|
const headerOptions = {
|
|
delimiter: config.delimiter,
|
|
separator: config.separator,
|
|
start: 1,
|
|
end: 1,
|
|
state: {
|
|
rowNum: 1,
|
|
colNum: 1
|
|
},
|
|
headers: true
|
|
}
|
|
|
|
// onPreParse hook
|
|
if (options.onPreParse !== undefined) {
|
|
csv = options.onPreParse(csv, options.state)
|
|
}
|
|
|
|
// parse the csv
|
|
const headerLine = $.csv.parsers.splitLines(csv, headerOptions)
|
|
const headers = $.csv.toArray(headerLine[0], headerOptions)
|
|
|
|
// 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 (let i = 0, len = lines.length; i < len; i++) {
|
|
const entry = $.csv.toArray(lines[i], options)
|
|
const object = {}
|
|
for (let 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) {
|
|
data = 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) {
|
|
// if callback was passed to options swap callback with options
|
|
if (options !== undefined && typeof (options) === 'function') {
|
|
if (callback !== undefined) {
|
|
return console.error('You cannot 3 arguments with the 2nd argument being a function')
|
|
}
|
|
callback = options
|
|
options = {}
|
|
}
|
|
|
|
options = (options !== undefined ? options : {})
|
|
const 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
|
|
|
|
let output = ''
|
|
|
|
for (let i = 0; i < arrays.length; i++) {
|
|
const line = arrays[i]
|
|
const lineValues = []
|
|
for (let j = 0; j < line.length; j++) {
|
|
let 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)
|
|
}
|
|
|
|
let 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) {
|
|
// if callback was passed to options swap callback with options
|
|
if (options !== undefined && typeof (options) === 'function') {
|
|
if (callback !== undefined) {
|
|
return console.error('You cannot 3 arguments with the 2nd argument being a function')
|
|
}
|
|
callback = options
|
|
options = {}
|
|
}
|
|
|
|
options = (options !== undefined ? options : {})
|
|
const 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) {
|
|
const origObjects = objects
|
|
objects = []
|
|
|
|
for (let i = 0; i < origObjects.length; i++) {
|
|
objects.push(config.transform.call(undefined, origObjects[i]))
|
|
}
|
|
}
|
|
|
|
let props = $.csv.helpers.collectPropertyNames(objects)
|
|
|
|
if (config.sortOrder === 'alpha') {
|
|
props.sort()
|
|
}
|
|
|
|
if (config.manualOrder.length > 0) {
|
|
const propsManual = [].concat(config.manualOrder)
|
|
|
|
for (let p = 0; p < props.length; p++) {
|
|
if (propsManual.indexOf(props[p]) < 0) {
|
|
propsManual.push(props[p])
|
|
}
|
|
}
|
|
props = propsManual
|
|
}
|
|
|
|
let line
|
|
const output = []
|
|
let propName
|
|
if (config.headers) {
|
|
output.push(props)
|
|
}
|
|
|
|
for (let o = 0; o < objects.length; o++) {
|
|
line = []
|
|
for (let 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)
|
|
|