import type { Nullable } from "@thi.ng/api" ; import { isArray } from "@thi.ng/checks/is-array" ; import { isFunction } from "@thi.ng/checks/is-function" ; import { isIterable } from "@thi.ng/checks/is-iterable" ; import { ESCAPES } from "@thi.ng/strings/escape" ; import { split } from "@thi.ng/strings/split" ; import type { Reducer , Transducer } from "@thi.ng/transducers" ; import { compR } from "@thi.ng/transducers/compr" ; import { iterator1 } from "@thi.ng/transducers/iterator" ; import type { ColumnSpec , CommonCSVOpts , CSVOpts , CSVRecord , CSVRow , SimpleCSVOpts , } from "./api.js" ; internal type IndexEntry = { i : number ; spec : ColumnSpec ; } ; internal const DEFAULT_OPTS = { delim : "," , quote : '"' , comment : "#" , trim : false , } ; remarks Parsing behavior can be customized via given  CSVOpts . The default
 * behavior is:
 *
 * - comma delimiter
 * - field names are obtained from first line/row
 * - all columns are processed, but no coercions
 * - untrimmed cell values
 * - line comment prefix `#`
 *
 * Using the `cols` option, specific columns can be renamed and their values
 * coerced/transformed. Additionally, if `all` option is `false`, then the
 * result objects will only contain values of the columns specified in `cols`.
 *
 * Also see:
 * - thi.ng/transducers
 * -  CSVOpts * -  parseCSVFromString .
 *
 *  example param opts export function parseCSV ( opts ? ) ; export function parseCSV ( opts , src ) ; export function parseCSV ( opts ? , src ? ) { return isIterable ( src ) ? iterator1 ( parseCSV ( opts ) , src ) : ( rfn ) => { const { all , cols , delim , quote , comment , trim , header } = { all : true , ... DEFAULT_OPTS , ... opts , } ; const reduce = rfn [ 2 ] ; let index ; let revIndex ; let first = true ; let isQuoted = false ; let record = [ ] ; const init = ( header ) => { cols && ( index = initIndex ( header , cols ) ) ; all && ( revIndex = initRevIndex ( header ) ) ; first = false ; } ; const collectAll = ( row ) => record . reduce ( ( acc , x , i ) => ( ( acc [ revIndex [ i ] ] = trim ? x . trim ( ) : x ) , acc ) , row ) ; const collectIndexed = ( row ) => Object . entries ( index ) . reduce ( ( acc , [ id , { i , spec } ] ) => { let val = record [ i ] ; if ( val !== undefined ) { trim && ( val = val . trim ( ) ) ; all && spec . alias && delete acc [ id ] ; acc [ spec . alias || id ] = spec . tx ? spec . tx ( val , acc ) : val ; } return acc ; } , row ) ; header && init ( header ) ; return compR ( rfn , ( acc , line ) => { if ( ( ! line . length || line . startsWith ( comment ! ) ) && ! isQuoted ) return acc ; if ( ! first ) { isQuoted = parseLine ( line , record , isQuoted , delim ! , quote ! ) ; if ( isQuoted ) return acc ; const row = { } ; all && collectAll ( row ) ; index && collectIndexed ( row ) ; record = [ ] ; return reduce ( acc , row ) ; } else { isQuoted = parseLine ( line , record , isQuoted , delim ! , quote ! ) ; if ( ! isQuoted ) { init ( record ) ; record = [ ] ; } return acc ; } } ) ; } ; } /**
 * Simplified version of  parseCSV for use cases when no object mapping
 * is desired/required. Here, each CSV row will be emitted as simple array,
 * optionally with only filtered or transformed columns.
 *
 *  remarks See  SimpleCSVOpts for available options. Defaults are similar to
 * those used by  parseCSV .
 *
 *  example param opts export function parseCSVSimple ( opts ? ) ; export function parseCSVSimple ( opts , src ) ; export function parseCSVSimple ( opts ? , src ? ) { return isIterable ( src ) ? iterator1 ( parseCSVSimple ( opts ) , src ) : ( rfn ) => { const { cols , delim , quote , comment , trim , header } = { header : true , ... DEFAULT_OPTS , ... opts , } ; const reduce = rfn [ 2 ] ; let first = header ; let isQuoted = false ; let record = [ ] ; const collect = ( ) => cols ! . reduce ( ( acc , col , i ) => { if ( col ) { let val = record [ i ] ; if ( val !== undefined ) { trim && ( val = val . trim ( ) ) ; acc . push ( isFunction ( col ) ? col ( val , acc ) : val ) ; } } return acc ; } , < CSVRow > [ ] ) ; return compR ( rfn , ( acc , line ) => { if ( ( ! line . length || line . startsWith ( comment ! ) ) && ! isQuoted ) return acc ; if ( ! first ) { isQuoted = parseLine ( line , record , isQuoted , delim ! , quote ! ) ; if ( isQuoted ) return acc ; const row = cols ? collect ( ) : record ; record = [ ] ; return reduce ( acc , row ) ; } else { isQuoted = parseLine ( line , record , isQuoted , delim ! , quote ! ) ; first = false ; record = [ ] ; return acc ; } } ) ; } ; } /**
 * Syntax sugar for iterator version of  parseCSV , efficiently splitting
 * given source string into a line based input using
 *  {@link @thi.ng/strings#split} .
 *
 *  param opts param src export const parseCSVFromString = ( opts , src ) => parseCSV ( opts , split ( src ) ) ; /**
 * Syntax sugar for iterator version of  parseCSVSimple , efficiently
 * splitting given source string into a line based input using
 *  {@link @thi.ng/strings#split} .
 *
 *  param opts param src export const parseCSVSimpleFromString = ( opts , src ) => parseCSVSimple ( opts , split ( src ) ) ; remarks param line param acc param isQuoted param delim param quote const parseLine = ( line , acc , isQuoted , delim , quote ) => { let curr = "" ; let p = "" ; let openQuote = isQuoted ; for ( let i = 0 , n = line . length ; i < n ; i ++ ) { const c = line [ i ] ; if ( p === "\\" ) { curr += ESCAPES [ c ] || c ; } else if ( c === quote ) { if ( ! isQuoted ) { p = "" ; isQuoted = true ; continue ; } else if ( p === quote ) { curr += quote ; p = "" ; continue ; } else if ( line [ i + 1 ] !== quote ) isQuoted = false ; } else if ( ! isQuoted && c === delim ) { collectCell ( acc , curr , openQuote ) ; openQuote = false ; curr = "" ; } else if ( c !== "\\" ) { curr += c ; } p = c ; } curr !== "" && collectCell ( acc , curr , openQuote ) ; return isQuoted ; } ; const collectCell = ( acc , curr , openQuote ) => openQuote ? ( acc [ acc . length - 1 ] += "\n" + curr ) : acc . push ( curr ) ; const initIndex = ( line , cols ) => isArray ( cols ) ? cols . reduce ( ( acc , spec , i ) => { if ( spec ) { const alias = spec . alias || line [ i ] || String ( i ) ; acc [ alias ] = { i , spec : { alias , ... spec } } ; } return acc ; } , < Record < string , IndexEntry > > { } ) : line . reduce ( ( acc , id , i ) => cols ! [ id ] ? ( ( acc [ id ] = { i , spec : cols ! [ id ] } ) , acc ) : acc , < Record < string , IndexEntry > > { } ) ; const initRevIndex = ( line ) => line . reduce ( ( acc , x , i ) => ( ( acc [ i ] = x ) , acc ) , < Record < number , string > > { } ) ;