'use strict' ; import type DataEventEmitter from '../DataEventEmitter' ; import DataParser from './DataParser.js' ; import DataTable from '../DataTable.js' ; import DataConverter from '../DataConverter.js' ; import U from '../../Core/Utilities.js' ; const { merge } = U ; private class CSVParser extends DataParser < DataParser . Event > { /**
     * Default options
     */ protected static readonly defaultOptions : CSVParser . ClassJSONOptions = { ... DataParser . defaultOptions , lineDelimiter : '\n' } ; param { CSVParser . OptionsType } options param { DataConverter } converter public constructor ( options ? , converter ? ) { super ( ) ; this . options = merge ( CSVParser . defaultOptions , options ) ; this . converter = converter || new DataConverter ( ) ; } private columns : Array < DataTable . Column > = [ ] ; private headers : Array < string > = [ ] ; private dataTypes : Array < Array < string > > = [ ] ; private guessedItemDelimiter ? : string ; private guessedDecimalPoint ? : string ; private options : CSVParser . ClassJSONOptions ; public converter : DataConverter ; param { CSVParser . OptionsType } options param { DataEventEmitter . EventDetail } eventDetail emits emits public parse ( options , eventDetail ? ) { const parser = this , dataTypes = parser . dataTypes , converter = parser . converter , parserOptions = merge ( this . options , options ) , { beforeParse , lineDelimiter , firstRowAsNames , itemDelimiter } = parserOptions ; let lines , rowIt = 0 , { csv , startRow , endRow } = parserOptions , column ; parser . columns = [ ] ; parser . emit < DataParser . Event > ( { type : 'parse' , columns : parser . columns , detail : eventDetail , headers : parser . headers } ) ; if ( csv && beforeParse ) { csv = beforeParse ( csv ) ; } if ( csv ) { lines = csv . replace ( /\r\n/g , '\n' ) . replace ( /\r/g , '\n' ) . split ( lineDelimiter || '\n' ) ; if ( ! startRow || startRow < 0 ) { startRow = 0 ; } if ( ! endRow || endRow >= lines . length ) { endRow = lines . length - 1 ; } if ( ! itemDelimiter ) { parser . guessedItemDelimiter = parser . guessDelimiter ( lines ) ; } if ( firstRowAsNames ) { const headers = lines [ 0 ] . split ( itemDelimiter || parser . guessedItemDelimiter || ',' ) ; for ( let i = 0 ; i < headers . length ; i ++ ) { headers [ i ] = headers [ i ] . replace ( /^["']|["']$/g , '' ) ; } parser . headers = headers ; startRow ++ ; } let offset = 0 ; for ( rowIt = startRow ; rowIt <= endRow ; rowIt ++ ) { if ( lines [ rowIt ] [ 0 ] === '#' ) { offset ++ ; } else { parser . parseCSVRow ( lines [ rowIt ] , rowIt - startRow - offset ) ; } } if ( dataTypes . length && dataTypes [ 0 ] . length && dataTypes [ 0 ] [ 1 ] === 'date' && ! parser . converter . getDateFormat ( ) ) { parser . converter . deduceDateFormat ( parser . columns [ 0 ] as Array < string > , null , true ) ; } for ( let i = 0 , iEnd = parser . columns . length ; i < iEnd ; ++ i ) { column = parser . columns [ i ] ; for ( let j = 0 , jEnd = column . length ; j < jEnd ; ++ j ) { if ( column [ j ] && typeof column [ j ] === 'string' ) { let cellValue = converter . asGuessedType ( column [ j ] as string ) ; if ( cellValue instanceof Date ) { cellValue = cellValue . getTime ( ) ; } parser . columns [ i ] [ j ] = cellValue ; } } } } parser . emit < DataParser . Event > ( { type : 'afterParse' , columns : parser . columns , detail : eventDetail , headers : parser . headers } ) ; } /**
     * Internal method that parses a single CSV row
     */ private parseCSVRow ( columnStr , rowNumber ) { const parser = this , converter = this . converter , columns = parser . columns || [ ] , dataTypes = parser . dataTypes , { startColumn , endColumn } = parser . options , itemDelimiter = ( parser . options . itemDelimiter || parser . guessedItemDelimiter ) ; let { decimalPoint } = parser . options ; if ( ! decimalPoint || decimalPoint === itemDelimiter ) { decimalPoint = parser . guessedDecimalPoint || '.' ; } let i = 0 , c = '' , cl = '' , cn = '' , token = '' , actualColumn = 0 , column = 0 ; private function read ( j ) { c = columnStr [ j ] ; cl = columnStr [ j - 1 ] ; cn = columnStr [ j + 1 ] ; } private function pushType ( type ) { if ( dataTypes . length < column + 1 ) { dataTypes . push ( [ type ] ) ; } if ( dataTypes [ column ] [ dataTypes [ column ] . length - 1 ] !== type ) { dataTypes [ column ] . push ( type ) ; } } private function push ( ) { if ( startColumn > actualColumn || actualColumn > endColumn ) { ++ actualColumn ; token = '' ; return ; } if ( typeof token === 'string' ) { if ( ! isNaN ( parseFloat ( token ) ) && isFinite ( token as any ) ) { token = parseFloat ( token ) as any ; pushType ( 'number' ) ; } else if ( ! isNaN ( Date . parse ( token ) ) ) { token = token . replace ( /\//g , '-' ) ; pushType ( 'date' ) ; } else { pushType ( 'string' ) ; } } else { pushType ( 'number' ) ; } if ( columns . length < column + 1 ) { columns . push ( [ ] ) ; } if ( typeof token !== 'number' && converter . guessType ( token ) !== 'number' && decimalPoint ) { const initialValue = token ; token = token . replace ( decimalPoint , '.' ) ; if ( converter . guessType ( token ) !== 'number' ) { token = initialValue ; } } columns [ column ] [ rowNumber ] = token ; token = '' ; ++ column ; ++ actualColumn ; } if ( ! columnStr . trim ( ) . length ) { return ; } if ( columnStr . trim ( ) [ 0 ] === '#' ) { return ; } for ( ; i < columnStr . length ; i ++ ) { read ( i ) ; if ( c === '#' ) { if ( ! /^#[0-F]{3,3}|[0-F]{6,6}/i . test ( columnStr . substr ( i ) ) ) { push ( ) ; return ; } } if ( c === '"' ) { read ( ++ i ) ; while ( i < columnStr . length ) { if ( c === '"' && cl !== '"' && cn !== '"' ) { break ; } if ( c !== '"' || ( c === '"' && cl !== '"' ) ) { token += c ; } read ( ++ i ) ; } } else if ( c === itemDelimiter ) { push ( ) ; } else { token += c ; } } push ( ) ; } param { Array < string > } lines private guessDelimiter ( lines ) { let points = 0 , commas = 0 , guessed ; const potDelimiters = { ',' : 0 , ';' : 0 , '\t' : 0 } , linesCount = lines . length ; for ( let i = 0 ; i < linesCount ; i ++ ) { let inStr = false , c , cn , cl , token = '' ; if ( i > 13 ) { break ; } const columnStr = lines [ i ] ; for ( let j = 0 ; j < columnStr . length ; j ++ ) { c = columnStr [ j ] ; cn = columnStr [ j + 1 ] ; cl = columnStr [ j - 1 ] ; if ( c === '#' ) { break ; } if ( c === '"' ) { if ( inStr ) { if ( cl !== '"' && cn !== '"' ) { while ( cn === ' ' && j < columnStr . length ) { cn = columnStr [ ++ j ] ; } if ( typeof potDelimiters [ cn ] !== 'undefined' ) { potDelimiters [ cn ] ++ ; } inStr = false ; } } else { inStr = true ; } } else if ( typeof potDelimiters [ c ] !== 'undefined' ) { token = token . trim ( ) ; if ( ! isNaN ( Date . parse ( token ) ) ) { potDelimiters [ c ] ++ ; } else if ( isNaN ( Number ( token ) ) || ! isFinite ( Number ( token ) ) ) { potDelimiters [ c ] ++ ; } token = '' ; } else { token += c ; } if ( c === ',' ) { commas ++ ; } if ( c === '.' ) { points ++ ; } } } if ( potDelimiters [ ';' ] > potDelimiters [ ',' ] ) { guessed = ';' ; } else if ( potDelimiters [ ',' ] > potDelimiters [ ';' ] ) { guessed = ',' ; } else { guessed = ',' ; } if ( points > commas ) { this . guessedDecimalPoint = '.' ; } else { this . guessedDecimalPoint = ',' ; } return guessed ; } return { DataTable } public getTable ( ) { return DataParser . getTableFromColumns ( this . columns , this . headers ) ; } } namespace CSVParser { /**
     * Interface for the BeforeParse callback function
     *
     */ export interface DataBeforeParseCallbackFunction { ( csv ) : string ; } /**
     * All available options for the parser
     */ export type OptionsType = Partial < ClassJSONOptions & ParserOptions > ; /**
     * Options for the CSV parser that are compatible with ClassJSON
     */ export interface ClassJSONOptions extends DataParser . Options { csv ? : string ; decimalPoint ? : string ; itemDelimiter ? : string ; lineDelimiter : string ; } /**
     * Options that are not compatible with ClassJSON
     */ export interface ParserOptions { beforeParse ? : DataBeforeParseCallbackFunction ; decimalRegex ? : RegExp ; } } export default CSVParser ;