Desmos csv import

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

Du musst eine Erweiterung wie Tampermonkey, Greasemonkey oder Violentmonkey installieren, um dieses Skript zu installieren.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

Sie müssten eine Skript Manager Erweiterung installieren damit sie dieses Skript installieren können

(Ich habe schon ein Skript Manager, Lass mich es installieren!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         Desmos csv import
// @namespace    http://tampermonkey.net/
// @version      2024-11-12
// @description  Just a little helper userscipt to convert csv into a format that desmos likes
// @author       Evan Plaice
// @author       Pranshu Tanwar
// @match        https://www.desmos.com/calculator*
// @icon         data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
// @grant        unsafeWindow
// @license      MIT
// ==/UserScript==

// HOW TO USE (in browser console with desmos open)
// define    csv = `your csv as a string`
// call      to_add = desmoscsv.csv.toDesmosTable(csv)
//           state = Calc.getState()
//           state.expressions.list.push(to_add)
//           Calc.setState(state)


/* 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 [email protected] 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 $

  $ = {}
  /**
   * 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
      }

      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)
      }
    },

      /**
       below is custom added code
     * $.csv.toDesmosTable(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).
     */
    toDesmosTable: 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
        }
      }

      let result = []
      let arr = $.csv.toArrays(csv, options)
      for (let i = 0; i < arr.length; i++) {
          let inner = arr[i]
          if (i == 0) {
              result = new Array(inner.length)
              for (let v = 0; v < inner.length; v++) {
                  result[v] = {color: "#eeeeee", values: [inner[v]]}
              }
          }
          else {
              for (let v = 0; v < inner.length; v++) {
                  result[v].values.push(inner[v])
              }
          }
      }
      let ret = new Object();
      ret.type = "table"
      ret.columns = result
      return ret
    }
    ,
    /**
    above is custom added code
    */

    /**
    * $.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

  this.$ = $
}).call(this)

if (unsafeWindow.desmoscsv == undefined) {
    unsafeWindow.desmoscsv = this.$
}