You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1027 lines
35 KiB

  1. /* eslint no-prototype-builtins: 0 */
  2. /**
  3. * jQuery-csv (jQuery Plugin)
  4. *
  5. * This document is licensed as free software under the terms of the
  6. * MIT License: http://www.opensource.org/licenses/mit-license.php
  7. *
  8. * Acknowledgements:
  9. * The original design and influence to implement this library as a jquery
  10. * plugin is influenced by jquery-json (http://code.google.com/p/jquery-json/).
  11. * If you're looking to use native JSON.Stringify but want additional backwards
  12. * compatibility for browsers that don't support it, I highly recommend you
  13. * check it out.
  14. *
  15. * A special thanks goes out to rwk@acm.org for providing a lot of valuable
  16. * feedback to the project including the core for the new FSM
  17. * (Finite State Machine) parsers. If you're looking for a stable TSV parser
  18. * be sure to take a look at jquery-tsv (http://code.google.com/p/jquery-tsv/).
  19. * For legal purposes I'll include the "NO WARRANTY EXPRESSED OR IMPLIED.
  20. * USE AT YOUR OWN RISK.". Which, in 'layman's terms' means, by using this
  21. * library you are accepting responsibility if it breaks your code.
  22. *
  23. * Legal jargon aside, I will do my best to provide a useful and stable core
  24. * that can effectively be built on.
  25. *
  26. * Copyrighted 2012 by Evan Plaice.
  27. */
  28. RegExp.escape = function (s) {
  29. return s.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&')
  30. };
  31. (function () {
  32. 'use strict'
  33. let $
  34. // to keep backwards compatibility
  35. if (typeof jQuery !== 'undefined' && jQuery) {
  36. $ = jQuery
  37. } else {
  38. $ = {}
  39. }
  40. /**
  41. * jQuery.csv.defaults
  42. * Encapsulates the method paramater defaults for the CSV plugin module.
  43. */
  44. $.csv = {
  45. defaults: {
  46. separator: ',',
  47. delimiter: '"',
  48. headers: true
  49. },
  50. hooks: {
  51. castToScalar: function (value, state) {
  52. const hasDot = /\./
  53. if (isNaN(value)) {
  54. return value
  55. } else {
  56. if (hasDot.test(value)) {
  57. return parseFloat(value)
  58. } else {
  59. const integer = parseInt(value)
  60. if (isNaN(integer)) {
  61. return null
  62. } else {
  63. return integer
  64. }
  65. }
  66. }
  67. }
  68. },
  69. parsers: {
  70. parse: function (csv, options) {
  71. // cache settings
  72. const separator = options.separator
  73. const delimiter = options.delimiter
  74. // set initial state if it's missing
  75. if (!options.state.rowNum) {
  76. options.state.rowNum = 1
  77. }
  78. if (!options.state.colNum) {
  79. options.state.colNum = 1
  80. }
  81. // clear initial state
  82. const data = []
  83. let entry = []
  84. let state = 0
  85. let value = ''
  86. let exit = false
  87. function endOfEntry () {
  88. // reset the state
  89. state = 0
  90. value = ''
  91. // if 'start' hasn't been met, don't output
  92. if (options.start && options.state.rowNum < options.start) {
  93. // update global state
  94. entry = []
  95. options.state.rowNum++
  96. options.state.colNum = 1
  97. return
  98. }
  99. if (options.onParseEntry === undefined) {
  100. // onParseEntry hook not set
  101. data.push(entry)
  102. } else {
  103. const hookVal = options.onParseEntry(entry, options.state) // onParseEntry Hook
  104. // false skips the row, configurable through a hook
  105. if (hookVal !== false) {
  106. data.push(hookVal)
  107. }
  108. }
  109. // console.log('entry:' + entry);
  110. // cleanup
  111. entry = []
  112. // if 'end' is met, stop parsing
  113. if (options.end && options.state.rowNum >= options.end) {
  114. exit = true
  115. }
  116. // update global state
  117. options.state.rowNum++
  118. options.state.colNum = 1
  119. }
  120. function endOfValue () {
  121. if (options.onParseValue === undefined) {
  122. // onParseValue hook not set
  123. entry.push(value)
  124. } else if (options.headers && options.state.rowNum === 1) {
  125. // don't onParseValue object headers
  126. entry.push(value)
  127. } else {
  128. const hook = options.onParseValue(value, options.state) // onParseValue Hook
  129. // false skips the row, configurable through a hook
  130. if (hook !== false) {
  131. entry.push(hook)
  132. }
  133. }
  134. // console.log('value:' + value);
  135. // reset the state
  136. value = ''
  137. state = 0
  138. // update global state
  139. options.state.colNum++
  140. }
  141. // escape regex-specific control chars
  142. const escSeparator = RegExp.escape(separator)
  143. const escDelimiter = RegExp.escape(delimiter)
  144. // compile the regEx str using the custom delimiter/separator
  145. let match = /(D|S|\r\n|\n|\r|[^DS\r\n]+)/
  146. let matchSrc = match.source
  147. matchSrc = matchSrc.replace(/S/g, escSeparator)
  148. matchSrc = matchSrc.replace(/D/g, escDelimiter)
  149. match = new RegExp(matchSrc, 'gm')
  150. // put on your fancy pants...
  151. // process control chars individually, use look-ahead on non-control chars
  152. csv.replace(match, function (m0) {
  153. if (exit) {
  154. return
  155. }
  156. switch (state) {
  157. // the start of a value
  158. case 0:
  159. // null last value
  160. if (m0 === separator) {
  161. value += ''
  162. endOfValue()
  163. break
  164. }
  165. // opening delimiter
  166. if (m0 === delimiter) {
  167. state = 1
  168. break
  169. }
  170. // null last value
  171. if (/^(\r\n|\n|\r)$/.test(m0)) {
  172. endOfValue()
  173. endOfEntry()
  174. break
  175. }
  176. // un-delimited value
  177. value += m0
  178. state = 3
  179. break
  180. // delimited input
  181. case 1:
  182. // second delimiter? check further
  183. if (m0 === delimiter) {
  184. state = 2
  185. break
  186. }
  187. // delimited data
  188. value += m0
  189. state = 1
  190. break
  191. // delimiter found in delimited input
  192. case 2:
  193. // escaped delimiter?
  194. if (m0 === delimiter) {
  195. value += m0
  196. state = 1
  197. break
  198. }
  199. // null value
  200. if (m0 === separator) {
  201. endOfValue()
  202. break
  203. }
  204. // end of entry
  205. if (/^(\r\n|\n|\r)$/.test(m0)) {
  206. endOfValue()
  207. endOfEntry()
  208. break
  209. }
  210. // broken paser?
  211. throw Error('CSVDataError: Illegal State [Row:' + options.state.rowNum + '][Col:' + options.state.colNum + ']')
  212. // un-delimited input
  213. case 3:
  214. // null last value
  215. if (m0 === separator) {
  216. endOfValue()
  217. break
  218. }
  219. // end of entry
  220. if (/^(\r\n|\n|\r)$/.test(m0)) {
  221. endOfValue()
  222. endOfEntry()
  223. break
  224. }
  225. if (m0 === delimiter) {
  226. // non-compliant data
  227. throw Error('CSVDataError: Illegal Quote [Row:' + options.state.rowNum + '][Col:' + options.state.colNum + ']')
  228. }
  229. // broken parser?
  230. throw Error('CSVDataError: Illegal Data [Row:' + options.state.rowNum + '][Col:' + options.state.colNum + ']')
  231. default:
  232. // shenanigans
  233. throw Error('CSVDataError: Unknown State [Row:' + options.state.rowNum + '][Col:' + options.state.colNum + ']')
  234. }
  235. // console.log('val:' + m0 + ' state:' + state);
  236. })
  237. // submit the last entry
  238. // ignore null last line
  239. if (entry.length !== 0) {
  240. endOfValue()
  241. endOfEntry()
  242. }
  243. return data
  244. },
  245. // a csv-specific line splitter
  246. splitLines: function (csv, options) {
  247. if (!csv) {
  248. return undefined
  249. }
  250. options = options || {}
  251. // cache settings
  252. const separator = options.separator || $.csv.defaults.separator
  253. const delimiter = options.delimiter || $.csv.defaults.delimiter
  254. // set initial state if it's missing
  255. options.state = options.state || {}
  256. if (!options.state.rowNum) {
  257. options.state.rowNum = 1
  258. }
  259. // clear initial state
  260. const entries = []
  261. let state = 0
  262. let entry = ''
  263. let exit = false
  264. function endOfLine () {
  265. // reset the state
  266. state = 0
  267. // if 'start' hasn't been met, don't output
  268. if (options.start && options.state.rowNum < options.start) {
  269. // update global state
  270. entry = ''
  271. options.state.rowNum++
  272. return
  273. }
  274. if (options.onParseEntry === undefined) {
  275. // onParseEntry hook not set
  276. entries.push(entry)
  277. } else {
  278. const hookVal = options.onParseEntry(entry, options.state) // onParseEntry Hook
  279. // false skips the row, configurable through a hook
  280. if (hookVal !== false) {
  281. entries.push(hookVal)
  282. }
  283. }
  284. // cleanup
  285. entry = ''
  286. // if 'end' is met, stop parsing
  287. if (options.end && options.state.rowNum >= options.end) {
  288. exit = true
  289. }
  290. // update global state
  291. options.state.rowNum++
  292. }
  293. // escape regex-specific control chars
  294. const escSeparator = RegExp.escape(separator)
  295. const escDelimiter = RegExp.escape(delimiter)
  296. // compile the regEx str using the custom delimiter/separator
  297. let match = /(D|S|\n|\r|[^DS\r\n]+)/
  298. let matchSrc = match.source
  299. matchSrc = matchSrc.replace(/S/g, escSeparator)
  300. matchSrc = matchSrc.replace(/D/g, escDelimiter)
  301. match = new RegExp(matchSrc, 'gm')
  302. // put on your fancy pants...
  303. // process control chars individually, use look-ahead on non-control chars
  304. csv.replace(match, function (m0) {
  305. if (exit) {
  306. return
  307. }
  308. switch (state) {
  309. // the start of a value/entry
  310. case 0:
  311. // null value
  312. if (m0 === separator) {
  313. entry += m0
  314. state = 0
  315. break
  316. }
  317. // opening delimiter
  318. if (m0 === delimiter) {
  319. entry += m0
  320. state = 1
  321. break
  322. }
  323. // end of line
  324. if (m0 === '\n') {
  325. endOfLine()
  326. break
  327. }
  328. // phantom carriage return
  329. if (/^\r$/.test(m0)) {
  330. break
  331. }
  332. // un-delimit value
  333. entry += m0
  334. state = 3
  335. break
  336. // delimited input
  337. case 1:
  338. // second delimiter? check further
  339. if (m0 === delimiter) {
  340. entry += m0
  341. state = 2
  342. break
  343. }
  344. // delimited data
  345. entry += m0
  346. state = 1
  347. break
  348. // delimiter found in delimited input
  349. case 2: {
  350. // escaped delimiter?
  351. const prevChar = entry.substr(entry.length - 1)
  352. if (m0 === delimiter && prevChar === delimiter) {
  353. entry += m0
  354. state = 1
  355. break
  356. }
  357. // end of value
  358. if (m0 === separator) {
  359. entry += m0
  360. state = 0
  361. break
  362. }
  363. // end of line
  364. if (m0 === '\n') {
  365. endOfLine()
  366. break
  367. }
  368. // phantom carriage return
  369. if (m0 === '\r') {
  370. break
  371. }
  372. // broken paser?
  373. throw Error('CSVDataError: Illegal state [Row:' + options.state.rowNum + ']')
  374. }
  375. // un-delimited input
  376. case 3:
  377. // null value
  378. if (m0 === separator) {
  379. entry += m0
  380. state = 0
  381. break
  382. }
  383. // end of line
  384. if (m0 === '\n') {
  385. endOfLine()
  386. break
  387. }
  388. // phantom carriage return
  389. if (m0 === '\r') {
  390. break
  391. }
  392. // non-compliant data
  393. if (m0 === delimiter) {
  394. throw Error('CSVDataError: Illegal quote [Row:' + options.state.rowNum + ']')
  395. }
  396. // broken parser?
  397. throw Error('CSVDataError: Illegal state [Row:' + options.state.rowNum + ']')
  398. default:
  399. // shenanigans
  400. throw Error('CSVDataError: Unknown state [Row:' + options.state.rowNum + ']')
  401. }
  402. // console.log('val:' + m0 + ' state:' + state);
  403. })
  404. // submit the last entry
  405. // ignore null last line
  406. if (entry !== '') {
  407. endOfLine()
  408. }
  409. return entries
  410. },
  411. // a csv entry parser
  412. parseEntry: function (csv, options) {
  413. // cache settings
  414. const separator = options.separator
  415. const delimiter = options.delimiter
  416. // set initial state if it's missing
  417. if (!options.state.rowNum) {
  418. options.state.rowNum = 1
  419. }
  420. if (!options.state.colNum) {
  421. options.state.colNum = 1
  422. }
  423. // clear initial state
  424. const entry = []
  425. let state = 0
  426. let value = ''
  427. function endOfValue () {
  428. if (options.onParseValue === undefined) {
  429. // onParseValue hook not set
  430. entry.push(value)
  431. } else {
  432. const hook = options.onParseValue(value, options.state) // onParseValue Hook
  433. // false skips the value, configurable through a hook
  434. if (hook !== false) {
  435. entry.push(hook)
  436. }
  437. }
  438. // reset the state
  439. value = ''
  440. state = 0
  441. // update global state
  442. options.state.colNum++
  443. }
  444. // checked for a cached regEx first
  445. if (!options.match) {
  446. // escape regex-specific control chars
  447. const escSeparator = RegExp.escape(separator)
  448. const escDelimiter = RegExp.escape(delimiter)
  449. // compile the regEx str using the custom delimiter/separator
  450. const match = /(D|S|\n|\r|[^DS\r\n]+)/
  451. let matchSrc = match.source
  452. matchSrc = matchSrc.replace(/S/g, escSeparator)
  453. matchSrc = matchSrc.replace(/D/g, escDelimiter)
  454. options.match = new RegExp(matchSrc, 'gm')
  455. }
  456. // put on your fancy pants...
  457. // process control chars individually, use look-ahead on non-control chars
  458. csv.replace(options.match, function (m0) {
  459. switch (state) {
  460. // the start of a value
  461. case 0:
  462. // null last value
  463. if (m0 === separator) {
  464. value += ''
  465. endOfValue()
  466. break
  467. }
  468. // opening delimiter
  469. if (m0 === delimiter) {
  470. state = 1
  471. break
  472. }
  473. // skip un-delimited new-lines
  474. if (m0 === '\n' || m0 === '\r') {
  475. break
  476. }
  477. // un-delimited value
  478. value += m0
  479. state = 3
  480. break
  481. // delimited input
  482. case 1:
  483. // second delimiter? check further
  484. if (m0 === delimiter) {
  485. state = 2
  486. break
  487. }
  488. // delimited data
  489. value += m0
  490. state = 1
  491. break
  492. // delimiter found in delimited input
  493. case 2:
  494. // escaped delimiter?
  495. if (m0 === delimiter) {
  496. value += m0
  497. state = 1
  498. break
  499. }
  500. // null value
  501. if (m0 === separator) {
  502. endOfValue()
  503. break
  504. }
  505. // skip un-delimited new-lines
  506. if (m0 === '\n' || m0 === '\r') {
  507. break
  508. }
  509. // broken paser?
  510. throw Error('CSVDataError: Illegal State [Row:' + options.state.rowNum + '][Col:' + options.state.colNum + ']')
  511. // un-delimited input
  512. case 3:
  513. // null last value
  514. if (m0 === separator) {
  515. endOfValue()
  516. break
  517. }
  518. // skip un-delimited new-lines
  519. if (m0 === '\n' || m0 === '\r') {
  520. break
  521. }
  522. // non-compliant data
  523. if (m0 === delimiter) {
  524. throw Error('CSVDataError: Illegal Quote [Row:' + options.state.rowNum + '][Col:' + options.state.colNum + ']')
  525. }
  526. // broken parser?
  527. throw Error('CSVDataError: Illegal Data [Row:' + options.state.rowNum + '][Col:' + options.state.colNum + ']')
  528. default:
  529. // shenanigans
  530. throw Error('CSVDataError: Unknown State [Row:' + options.state.rowNum + '][Col:' + options.state.colNum + ']')
  531. }
  532. // console.log('val:' + m0 + ' state:' + state);
  533. })
  534. // submit the last value
  535. endOfValue()
  536. return entry
  537. }
  538. },
  539. helpers: {
  540. /**
  541. * $.csv.helpers.collectPropertyNames(objectsArray)
  542. * Collects all unique property names from all passed objects.
  543. *
  544. * @param {Array} objects Objects to collect properties from.
  545. *
  546. * Returns an array of property names (array will be empty,
  547. * if objects have no own properties).
  548. */
  549. collectPropertyNames: function (objects) {
  550. let o = []
  551. let propName = []
  552. const props = []
  553. for (o in objects) {
  554. for (propName in objects[o]) {
  555. if ((objects[o].hasOwnProperty(propName)) &&
  556. (props.indexOf(propName) < 0) &&
  557. (typeof objects[o][propName] !== 'function')) {
  558. props.push(propName)
  559. }
  560. }
  561. }
  562. return props
  563. }
  564. },
  565. /**
  566. * $.csv.toArray(csv)
  567. * Converts a CSV entry string to a javascript array.
  568. *
  569. * @param {Array} csv The string containing the CSV data.
  570. * @param {Object} [options] An object containing user-defined options.
  571. * @param {Character} [separator] An override for the separator character. Defaults to a comma(,).
  572. * @param {Character} [delimiter] An override for the delimiter character. Defaults to a double-quote(").
  573. *
  574. * This method deals with simple CSV strings only. It's useful if you only
  575. * need to parse a single entry. If you need to parse more than one line,
  576. * use $.csv2Array instead.
  577. */
  578. toArray: function (csv, options, callback) {
  579. // if callback was passed to options swap callback with options
  580. if (options !== undefined && typeof (options) === 'function') {
  581. if (callback !== undefined) {
  582. return console.error('You cannot 3 arguments with the 2nd argument being a function')
  583. }
  584. callback = options
  585. options = {}
  586. }
  587. options = (options !== undefined ? options : {})
  588. const config = {}
  589. config.callback = ((callback !== undefined && typeof (callback) === 'function') ? callback : false)
  590. config.separator = 'separator' in options ? options.separator : $.csv.defaults.separator
  591. config.delimiter = 'delimiter' in options ? options.delimiter : $.csv.defaults.delimiter
  592. const state = (options.state !== undefined ? options.state : {})
  593. // setup
  594. options = {
  595. delimiter: config.delimiter,
  596. separator: config.separator,
  597. onParseEntry: options.onParseEntry,
  598. onParseValue: options.onParseValue,
  599. state: state
  600. }
  601. const entry = $.csv.parsers.parseEntry(csv, options)
  602. // push the value to a callback if one is defined
  603. if (!config.callback) {
  604. return entry
  605. } else {
  606. config.callback('', entry)
  607. }
  608. },
  609. /**
  610. * $.csv.toArrays(csv)
  611. * Converts a CSV string to a javascript array.
  612. *
  613. * @param {String} csv The string containing the raw CSV data.
  614. * @param {Object} [options] An object containing user-defined options.
  615. * @param {Character} [separator] An override for the separator character. Defaults to a comma(,).
  616. * @param {Character} [delimiter] An override for the delimiter character. Defaults to a double-quote(").
  617. *
  618. * This method deals with multi-line CSV. The breakdown is simple. The first
  619. * dimension of the array represents the line (or entry/row) while the second
  620. * dimension contains the values (or values/columns).
  621. */
  622. toArrays: function (csv, options, callback) {
  623. // if callback was passed to options swap callback with options
  624. if (options !== undefined && typeof (options) === 'function') {
  625. if (callback !== undefined) {
  626. return console.error('You cannot 3 arguments with the 2nd argument being a function')
  627. }
  628. callback = options
  629. options = {}
  630. }
  631. options = (options !== undefined ? options : {})
  632. const config = {}
  633. config.callback = ((callback !== undefined && typeof (callback) === 'function') ? callback : false)
  634. config.separator = 'separator' in options ? options.separator : $.csv.defaults.separator
  635. config.delimiter = 'delimiter' in options ? options.delimiter : $.csv.defaults.delimiter
  636. // setup
  637. let data = []
  638. options = {
  639. delimiter: config.delimiter,
  640. separator: config.separator,
  641. onPreParse: options.onPreParse,
  642. onParseEntry: options.onParseEntry,
  643. onParseValue: options.onParseValue,
  644. onPostParse: options.onPostParse,
  645. start: options.start,
  646. end: options.end,
  647. state: {
  648. rowNum: 1,
  649. colNum: 1
  650. }
  651. }
  652. // onPreParse hook
  653. if (options.onPreParse !== undefined) {
  654. csv = options.onPreParse(csv, options.state)
  655. }
  656. // parse the data
  657. data = $.csv.parsers.parse(csv, options)
  658. // onPostParse hook
  659. if (options.onPostParse !== undefined) {
  660. data = options.onPostParse(data, options.state)
  661. }
  662. // push the value to a callback if one is defined
  663. if (!config.callback) {
  664. return data
  665. } else {
  666. config.callback('', data)
  667. }
  668. },
  669. /**
  670. * $.csv.toObjects(csv)
  671. * Converts a CSV string to a javascript object.
  672. * @param {String} csv The string containing the raw CSV data.
  673. * @param {Object} [options] An object containing user-defined options.
  674. * @param {Character} [separator] An override for the separator character. Defaults to a comma(,).
  675. * @param {Character} [delimiter] An override for the delimiter character. Defaults to a double-quote(").
  676. * @param {Boolean} [headers] Indicates whether the data contains a header line. Defaults to true.
  677. *
  678. * This method deals with multi-line CSV strings. Where the headers line is
  679. * used as the key for each value per entry.
  680. */
  681. toObjects: function (csv, options, callback) {
  682. // if callback was passed to options swap callback with options
  683. if (options !== undefined && typeof (options) === 'function') {
  684. if (callback !== undefined) {
  685. return console.error('You cannot 3 arguments with the 2nd argument being a function')
  686. }
  687. callback = options
  688. options = {}
  689. }
  690. options = (options !== undefined ? options : {})
  691. const config = {}
  692. config.callback = ((callback !== undefined && typeof (callback) === 'function') ? callback : false)
  693. config.separator = 'separator' in options ? options.separator : $.csv.defaults.separator
  694. config.delimiter = 'delimiter' in options ? options.delimiter : $.csv.defaults.delimiter
  695. config.headers = 'headers' in options ? options.headers : $.csv.defaults.headers
  696. options.start = 'start' in options ? options.start : 1
  697. // account for headers
  698. if (config.headers) {
  699. options.start++
  700. }
  701. if (options.end && config.headers) {
  702. options.end++
  703. }
  704. // setup
  705. let lines = []
  706. let data = []
  707. options = {
  708. delimiter: config.delimiter,
  709. separator: config.separator,
  710. onPreParse: options.onPreParse,
  711. onParseEntry: options.onParseEntry,
  712. onParseValue: options.onParseValue,
  713. onPostParse: options.onPostParse,
  714. start: options.start,
  715. end: options.end,
  716. state: {
  717. rowNum: 1,
  718. colNum: 1
  719. },
  720. match: false,
  721. transform: options.transform
  722. }
  723. // fetch the headers
  724. const headerOptions = {
  725. delimiter: config.delimiter,
  726. separator: config.separator,
  727. start: 1,
  728. end: 1,
  729. state: {
  730. rowNum: 1,
  731. colNum: 1
  732. },
  733. headers: true
  734. }
  735. // onPreParse hook
  736. if (options.onPreParse !== undefined) {
  737. csv = options.onPreParse(csv, options.state)
  738. }
  739. // parse the csv
  740. const headerLine = $.csv.parsers.splitLines(csv, headerOptions)
  741. const headers = $.csv.toArray(headerLine[0], headerOptions)
  742. // fetch the data
  743. lines = $.csv.parsers.splitLines(csv, options)
  744. // reset the state for re-use
  745. options.state.colNum = 1
  746. if (headers) {
  747. options.state.rowNum = 2
  748. } else {
  749. options.state.rowNum = 1
  750. }
  751. // convert data to objects
  752. for (let i = 0, len = lines.length; i < len; i++) {
  753. const entry = $.csv.toArray(lines[i], options)
  754. const object = {}
  755. for (let j = 0; j < headers.length; j++) {
  756. object[headers[j]] = entry[j]
  757. }
  758. if (options.transform !== undefined) {
  759. data.push(options.transform.call(undefined, object))
  760. } else {
  761. data.push(object)
  762. }
  763. // update row state
  764. options.state.rowNum++
  765. }
  766. // onPostParse hook
  767. if (options.onPostParse !== undefined) {
  768. data = options.onPostParse(data, options.state)
  769. }
  770. // push the value to a callback if one is defined
  771. if (!config.callback) {
  772. return data
  773. } else {
  774. config.callback('', data)
  775. }
  776. },
  777. /**
  778. * $.csv.fromArrays(arrays)
  779. * Converts a javascript array to a CSV String.
  780. *
  781. * @param {Array} arrays An array containing an array of CSV entries.
  782. * @param {Object} [options] An object containing user-defined options.
  783. * @param {Character} [separator] An override for the separator character. Defaults to a comma(,).
  784. * @param {Character} [delimiter] An override for the delimiter character. Defaults to a double-quote(").
  785. *
  786. * This method generates a CSV file from an array of arrays (representing entries).
  787. */
  788. fromArrays: function (arrays, options, callback) {
  789. // if callback was passed to options swap callback with options
  790. if (options !== undefined && typeof (options) === 'function') {
  791. if (callback !== undefined) {
  792. return console.error('You cannot 3 arguments with the 2nd argument being a function')
  793. }
  794. callback = options
  795. options = {}
  796. }
  797. options = (options !== undefined ? options : {})
  798. const config = {}
  799. config.callback = ((callback !== undefined && typeof (callback) === 'function') ? callback : false)
  800. config.separator = 'separator' in options ? options.separator : $.csv.defaults.separator
  801. config.delimiter = 'delimiter' in options ? options.delimiter : $.csv.defaults.delimiter
  802. let output = ''
  803. for (let i = 0; i < arrays.length; i++) {
  804. const line = arrays[i]
  805. const lineValues = []
  806. for (let j = 0; j < line.length; j++) {
  807. let strValue = (line[j] === undefined || line[j] === null) ? '' : line[j].toString()
  808. if (strValue.indexOf(config.delimiter) > -1) {
  809. strValue = strValue.replace(new RegExp(config.delimiter, 'g'), config.delimiter + config.delimiter)
  810. }
  811. let escMatcher = '\n|\r|S|D'
  812. escMatcher = escMatcher.replace('S', config.separator)
  813. escMatcher = escMatcher.replace('D', config.delimiter)
  814. if (strValue.search(escMatcher) > -1) {
  815. strValue = config.delimiter + strValue + config.delimiter
  816. }
  817. lineValues.push(strValue)
  818. }
  819. output += lineValues.join(config.separator) + '\n'
  820. }
  821. // push the value to a callback if one is defined
  822. if (!config.callback) {
  823. return output
  824. } else {
  825. config.callback('', output)
  826. }
  827. },
  828. /**
  829. * $.csv.fromObjects(objects)
  830. * Converts a javascript dictionary to a CSV string.
  831. *
  832. * @param {Object} objects An array of objects containing the data.
  833. * @param {Object} [options] An object containing user-defined options.
  834. * @param {Character} [separator] An override for the separator character. Defaults to a comma(,).
  835. * @param {Character} [delimiter] An override for the delimiter character. Defaults to a double-quote(").
  836. * @param {Character} [sortOrder] Sort order of columns (named after
  837. * object properties). Use 'alpha' for alphabetic. Default is 'declare',
  838. * which means, that properties will _probably_ appear in order they were
  839. * declared for the object. But without any guarantee.
  840. * @param {Character or Array} [manualOrder] Manually order columns. May be
  841. * a strin in a same csv format as an output or an array of header names
  842. * (array items won't be parsed). All the properties, not present in
  843. * `manualOrder` will be appended to the end in accordance with `sortOrder`
  844. * option. So the `manualOrder` always takes preference, if present.
  845. *
  846. * This method generates a CSV file from an array of objects (name:value pairs).
  847. * It starts by detecting the headers and adding them as the first line of
  848. * the CSV file, followed by a structured dump of the data.
  849. */
  850. fromObjects: function (objects, options, callback) {
  851. // if callback was passed to options swap callback with options
  852. if (options !== undefined && typeof (options) === 'function') {
  853. if (callback !== undefined) {
  854. return console.error('You cannot 3 arguments with the 2nd argument being a function')
  855. }
  856. callback = options
  857. options = {}
  858. }
  859. options = (options !== undefined ? options : {})
  860. const config = {}
  861. config.callback = ((callback !== undefined && typeof (callback) === 'function') ? callback : false)
  862. config.separator = 'separator' in options ? options.separator : $.csv.defaults.separator
  863. config.delimiter = 'delimiter' in options ? options.delimiter : $.csv.defaults.delimiter
  864. config.headers = 'headers' in options ? options.headers : $.csv.defaults.headers
  865. config.sortOrder = 'sortOrder' in options ? options.sortOrder : 'declare'
  866. config.manualOrder = 'manualOrder' in options ? options.manualOrder : []
  867. config.transform = options.transform
  868. if (typeof config.manualOrder === 'string') {
  869. config.manualOrder = $.csv.toArray(config.manualOrder, config)
  870. }
  871. if (config.transform !== undefined) {
  872. const origObjects = objects
  873. objects = []
  874. for (let i = 0; i < origObjects.length; i++) {
  875. objects.push(config.transform.call(undefined, origObjects[i]))
  876. }
  877. }
  878. let props = $.csv.helpers.collectPropertyNames(objects)
  879. if (config.sortOrder === 'alpha') {
  880. props.sort()
  881. }
  882. if (config.manualOrder.length > 0) {
  883. const propsManual = [].concat(config.manualOrder)
  884. for (let p = 0; p < props.length; p++) {
  885. if (propsManual.indexOf(props[p]) < 0) {
  886. propsManual.push(props[p])
  887. }
  888. }
  889. props = propsManual
  890. }
  891. let line
  892. const output = []
  893. let propName
  894. if (config.headers) {
  895. output.push(props)
  896. }
  897. for (let o = 0; o < objects.length; o++) {
  898. line = []
  899. for (let p = 0; p < props.length; p++) {
  900. propName = props[p]
  901. if (propName in objects[o] && typeof objects[o][propName] !== 'function') {
  902. line.push(objects[o][propName])
  903. } else {
  904. line.push('')
  905. }
  906. }
  907. output.push(line)
  908. }
  909. // push the value to a callback if one is defined
  910. return $.csv.fromArrays(output, options, config.callback)
  911. }
  912. }
  913. // Maintenance code to maintain backward-compatibility
  914. // Will be removed in release 1.0
  915. $.csvEntry2Array = $.csv.toArray
  916. $.csv2Array = $.csv.toArrays
  917. $.csv2Dictionary = $.csv.toObjects
  918. // CommonJS module is defined
  919. if (typeof module !== 'undefined' && module.exports) {
  920. module.exports = $.csv
  921. }
  922. }).call(this)