Desmos csv import

Just a little helper userscipt to convert csv into a format that desmos likes

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