diff --git a/README.md b/README.md index 4d59e8d..fb64b2b 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Spreadsheet CFML -Standalone library for working with spreadsheets in CFML ([Lucee](http://lucee.org/) and Adobe ColdFusion), supporting all of ColdFusion's native spreadsheet functionality and much more besides. +Standalone library for working with spreadsheets and CSV in CFML ([Lucee](http://lucee.org/) and Adobe ColdFusion), supporting all of ColdFusion's native spreadsheet functionality and much more besides. ## Minimum Requirements diff --git a/Spreadsheet.cfc b/Spreadsheet.cfc index 27b9fc1..7f59d81 100644 --- a/Spreadsheet.cfc +++ b/Spreadsheet.cfc @@ -1,7 +1,7 @@ component accessors="true"{ //"static" - property name="version" default="3.11.1" setter="false"; + property name="version" default="3.11.1-develop" setter="false"; property name="osgiLibBundleVersion" default="5.2.4.1" setter="false"; //first 3 octets = POI version; increment 4th with other jar updates property name="osgiLibBundleSymbolicName" default="spreadsheet-cfml" setter="false"; property name="exceptionType" default="cfsimplicity.spreadsheet" setter="false"; @@ -1716,6 +1716,10 @@ component accessors="true"{ return this; } + public any function writeCsv(){ + return New objects.WriteCsv( this ); + } + public Spreadsheet function writeFileFromQuery( required query data ,required string filepath diff --git a/helpers/csv.cfc b/helpers/csv.cfc index 2a2ab75..2800f79 100644 --- a/helpers/csv.cfc +++ b/helpers/csv.cfc @@ -70,8 +70,7 @@ component extends="base"{ return dataFromParser( parser ); } finally{ - if( local.KeyExists( "parser" ) ) - parser.close(); + getFileHelper().closeLocalFileOrStream( local, "parser" ); } } diff --git a/helpers/query.cfc b/helpers/query.cfc index b198563..a960439 100644 --- a/helpers/query.cfc +++ b/helpers/query.cfc @@ -7,8 +7,8 @@ component extends="base"{ catch( any exception ){ if( !exception.message CONTAINS "undefined" ) rethrow; - // ACF - return arguments.q.getColumnNames(); + // ACF: the raw object can behave oddly with writeCsv().setQueryColumnsAsHeaderIfRequired(), hence re-casting as a CFML array + return ListToArray( ArrayToList( arguments.q.getColumnNames() ) ); } } diff --git a/objects/BaseCsv.cfc b/objects/BaseCsv.cfc new file mode 100644 index 0000000..6d1d233 --- /dev/null +++ b/objects/BaseCsv.cfc @@ -0,0 +1,120 @@ +component accessors="true"{ + + property name="filepath"; + property name="headerValues" type="array"; + /* Java objects */ + property name="format"; //org.apache.commons.csv.CSVFormat + /* Internal */ + property name="library" setter="false"; + + public BaseCsv function init( required spreadsheetLibrary, string initialPredefinedFormat="DEFAULT" ){ + variables.library = arguments.spreadsheetLibrary; + variables.format = createPredefinedFormat( arguments.initialPredefinedFormat ); + return this; + } + + /* Public builder API */ + public BaseCsv function withPredefinedFormat( required string type ){ + variables.format = createPredefinedFormat( arguments.type ); + return this; + } + + /* Format configuration */ + public BaseCsv function withAllowMissingColumnNames( boolean state=true ){ + variables.format = variables.format.builder().setAllowMissingColumnNames( JavaCast( "boolean", arguments.state ) ).build(); + return this; + } + + public BaseCsv function withAutoFlush( boolean state=true ){ + variables.format = variables.format.builder().setAutoFlush( JavaCast( "boolean", arguments.state ) ).build(); + return this; + } + + public BaseCsv function withCommentMarker( required string marker ){ + variables.format = variables.format.builder().setCommentMarker( JavaCast( "char", arguments.marker ) ).build(); + return this; + } + + public BaseCsv function withDelimiter( required string delimiter ){ + if( variables.library.getCsvHelper().delimiterIsTab( arguments.delimiter ) ){ + variables.format = createPredefinedFormat( "TDF" ); //tabs require several specific settings so use predefined format + return this; + } + variables.format = variables.format.builder().setDelimiter( JavaCast( "string", arguments.delimiter ) ).build(); + return this; + } + + public BaseCsv function withDuplicateHeaderMode( required string value ){ + var mode = variables.library.createJavaObject( "org.apache.commons.csv.DuplicateHeaderMode" )[ JavaCast( "string", arguments.value ) ]; + variables.format = variables.format.builder().setDuplicateHeaderMode( mode ).build(); + return this; + } + + public BaseCsv function withEscapeCharacter( required string character ){ + variables.format = variables.format.builder().setEscape( JavaCast( "char", arguments.character ) ).build(); + return this; + } + + public BaseCsv function withHeader( required array header ){ + variables.headerValues = arguments.header; + variables.format = variables.format.builder().setHeader( JavaCast( "string[]", arguments.header ) ).build(); + return this; + } + + public BaseCsv function withHeaderComments( required array comments ){ + variables.format = variables.format.builder().setHeaderComments( JavaCast( "string[]", arguments.comments ) ).build(); + return this; + } + + public BaseCsv function withIgnoreEmptyLines( boolean state=true ){ + variables.format = variables.format.builder().setIgnoreEmptyLines( JavaCast( "boolean", arguments.state ) ).build(); + return this; + } + + public BaseCsv function withIgnoreHeaderCase( boolean state=true ){ + variables.format = variables.format.builder().setIgnoreHeaderCase( JavaCast( "boolean", arguments.state ) ).build(); + return this; + } + + public BaseCsv function withIgnoreSurroundingSpaces( boolean state=true ){ + variables.format = variables.format.builder().setIgnoreSurroundingSpaces( JavaCast( "boolean", arguments.state ) ).build(); + return this; + } + + public BaseCsv function withNullString( required string value ){ + variables.format = variables.format.builder().setNullString( JavaCast( "string", arguments.value ) ).build(); + return this; + } + + public BaseCsv function withQuoteCharacter( string character ){ + variables.format = variables.format.builder().setQuote( JavaCast( "char", arguments.character ) ).build(); + return this; + } + + public BaseCsv function withQuoteMode( required string value ){ + var mode = variables.library.createJavaObject( "org.apache.commons.csv.QuoteMode" )[ JavaCast( "string", arguments.value ) ]; + variables.format = variables.format.builder().setQuoteMode( mode ).build(); + return this; + } + + public BaseCsv function withSkipHeaderRecord( boolean state=true ){ + variables.format = variables.format.builder().setSkipHeaderRecord( JavaCast( "boolean", arguments.state ) ).build(); + return this; + } + + public BaseCsv function withTrailingDelimiter( boolean state=true ){ + variables.format = variables.format.builder().setTrailingDelimiter( JavaCast( "boolean", arguments.state ) ).build(); + return this; + } + + public BaseCsv function withTrim( boolean state=true ){ + variables.format = variables.format.builder().setTrim( JavaCast( "boolean", arguments.state ) ).build(); + return this; + } + + //Private + private any function createPredefinedFormat( string type="DEFAULT" ){ + return variables.library.createJavaObject( "org.apache.commons.csv.CSVFormat" )[ JavaCast( "string", arguments.type ) ]; + } + +} \ No newline at end of file diff --git a/objects/ReadCsv.cfc b/objects/ReadCsv.cfc index 03f1669..42652e0 100644 --- a/objects/ReadCsv.cfc +++ b/objects/ReadCsv.cfc @@ -1,24 +1,17 @@ -component accessors="true"{ +component extends="BaseCsv" accessors="true"{ - property name="filepath"; - property name="firstRowIsHeader" type="boolean" default=false; - property name="headerValues" type="array"; + property name="firstRowIsHeader" type="boolean" default="false"; property name="numberOfRowsToSkip" default=0; property name="returnFormat" default="none"; property name="rowFilter"; property name="rowProcessor"; - /* Java objects */ - property name="format"; //org.apache.commons.csv.CSVFormat - /* Internal */ - property name="library" setter="false"; public ReadCsv function init( required spreadsheetLibrary, required string filepath ){ - variables.library = arguments.spreadsheetLibrary; + super.init( arguments.spreadsheetLibrary ); variables.library.getFileHelper() .throwErrorIFfileNotExists( arguments.filepath ) .throwErrorIFnotCsvOrTextFile( arguments.filepath ); variables.filepath = arguments.filepath; - variables.format = createPredefinedFormat(); return this; } @@ -29,99 +22,6 @@ component accessors="true"{ return this; } - public ReadCsv function withPredefinedFormat( required string type ){ - variables.format = createPredefinedFormat( arguments.type ); - return this; - } - - /* Format configuration */ - public ReadCsv function withAllowMissingColumnNames( boolean state=true ){ - variables.format = variables.format.builder().setAllowMissingColumnNames( JavaCast( "boolean", arguments.state ) ).build(); - return this; - } - - public ReadCsv function withAutoFlush( boolean state=true ){ - variables.format = variables.format.builder().setAutoFlush( JavaCast( "boolean", arguments.state ) ).build(); - return this; - } - - public ReadCsv function withCommentMarker( required string marker ){ - variables.format = variables.format.builder().setCommentMarker( JavaCast( "char", arguments.marker ) ).build(); - return this; - } - - public ReadCsv function withDelimiter( required string delimiter ){ - if( variables.library.getCsvHelper().delimiterIsTab( arguments.delimiter ) ){ - variables.format = createPredefinedFormat( "TDF" ); //tabs require several specific settings so use predefined format - return this; - } - variables.format = variables.format.builder().setDelimiter( JavaCast( "string", arguments.delimiter ) ).build(); - return this; - } - - public ReadCsv function withDuplicateHeaderMode( required string value ){ - var mode = variables.library.createJavaObject( "org.apache.commons.csv.DuplicateHeaderMode" )[ JavaCast( "string", arguments.value ) ]; - variables.format = variables.format.builder().setDuplicateHeaderMode( mode ).build(); - return this; - } - - public ReadCsv function withEscapeCharacter( required string character ){ - variables.format = variables.format.builder().setEscape( JavaCast( "char", arguments.character ) ).build(); - return this; - } - - public ReadCsv function withHeader( required array header ){ - variables.headerValues = arguments.header; - variables.format = variables.format.builder().setHeader( JavaCast( "string[]", arguments.header ) ).build(); - return this; - } - - public ReadCsv function withHeaderComments( required array comments ){ - variables.format = variables.format.builder().setHeaderComments( JavaCast( "string[]", arguments.comments ) ).build(); - return this; - } - - public ReadCsv function withIgnoreEmptyLines( boolean state=true ){ - variables.format = variables.format.builder().setIgnoreEmptyLines( JavaCast( "boolean", arguments.state ) ).build(); - return this; - } - - public ReadCsv function withIgnoreHeaderCase( boolean state=true ){ - variables.format = variables.format.builder().setIgnoreHeaderCase( JavaCast( "boolean", arguments.state ) ).build(); - return this; - } - - public ReadCsv function withIgnoreSurroundingSpaces( boolean state=true ){ - variables.format = variables.format.builder().setIgnoreSurroundingSpaces( JavaCast( "boolean", arguments.state ) ).build(); - return this; - } - - public ReadCsv function withNullString( required string value ){ - variables.format = variables.format.builder().setNullString( JavaCast( "string", arguments.value ) ).build(); - return this; - } - - public ReadCsv function withQuoteCharacter( string character ){ - variables.format = variables.format.builder().setQuote( JavaCast( "char", arguments.character ) ).build(); - return this; - } - - public ReadCsv function withSkipHeaderRecord( boolean state=true ){ - variables.format = variables.format.builder().setSkipHeaderRecord( JavaCast( "boolean", arguments.state ) ).build(); - return this; - } - - public ReadCsv function withTrailingDelimiter( boolean state=true ){ - variables.format = variables.format.builder().setTrailingDelimiter( JavaCast( "boolean", arguments.state ) ).build(); - return this; - } - - public ReadCsv function withTrim( boolean state=true ){ - variables.format = variables.format.builder().setTrim( JavaCast( "boolean", arguments.state ) ).build(); - return this; - } - - // additional features public ReadCsv function withFirstRowIsHeader( boolean state=true ){ variables.firstRowIsHeader = arguments.state; return this; @@ -178,8 +78,7 @@ component accessors="true"{ } } finally { - if( local.KeyExists( "parser" ) ) - parser.close(); + variables.library.getFileHelper().closeLocalFileOrStream( local, "parser" ); } if( variables.returnFormat == "array" ){ useManuallySpecifiedHeaderForColumnsIfRequired( result ); @@ -199,8 +98,4 @@ component accessors="true"{ return variables.numberOfRowsToSkip && ( arguments.skippedRecords < variables.numberOfRowsToSkip ); } - private any function createPredefinedFormat( string type="DEFAULT" ){ - return variables.library.createJavaObject( "org.apache.commons.csv.CSVFormat" )[ JavaCast( "string", arguments.type ) ]; - } - } \ No newline at end of file diff --git a/objects/WriteCsv.cfc b/objects/WriteCsv.cfc new file mode 100644 index 0000000..1ec1678 --- /dev/null +++ b/objects/WriteCsv.cfc @@ -0,0 +1,199 @@ +component extends="BaseCsv" accessors="true"{ + + property name="data" setter="false"; + property name="parallelThreadsToUse" type="numeric" default=1 setter="false"; + property name="useQueryColumnsAsHeader" type="boolean" default="false" setter="false"; + property name="useStructKeysAsHeader" type="boolean" default="false" setter="false"; + + public WriteCsv function init( required spreadsheetLibrary ){ + super.init( spreadsheetLibrary=arguments.spreadsheetLibrary, initialPredefinedFormat="EXCEL" ); + return this; + } + + /* Public builder API */ + + public WriteCsv function fromData( required any data ){ + if( !IsArray( arguments.data ) && !IsQuery( arguments.data ) ) + Throw( type=variables.library.getExceptionType() & ".invalidDataForCsv", message="Invalid data", detail="Please pass your data as a query, an array of arrays, or an array of structs" ); + variables.data = arguments.data; + return this; + } + + public WriteCsv function toFile( required string path ){ + variables.filepath = arguments.path; + return this; + } + + public WriteCsv function withParallelThreads( numeric numberOfThreads=2 ){ + /* WARNING: can have unexpected results */ + if( !variables.library.engineSupportsParallelLoopProcessing() ) + variables.library.getExceptionHelper().throwParallelOptionNotSupportedException(); + variables.parallelThreadsToUse = Int( arguments.numberOfThreads ); + return this; + } + + public WriteCsv function withQueryColumnsAsHeader( boolean state=true ){ + variables.useQueryColumnsAsHeader = arguments.state; + return this; + } + + public WriteCsv function withStructKeysAsHeader( boolean state=true ){ + variables.useStructKeysAsHeader = arguments.state; + return this; + } + + // final execution + public any function execute(){ + var appendable = newAppendableBuffer(); + printTo( appendable ); + if( IsNull( variables.filepath ) ) + return appendable.toString(); + return this; + } + + /* Private */ + private void function printTo( required appendable ){ + try{ + if( IsQuery( data ) ){ + setQueryColumnsAsHeaderIfRequired(); + var printer = newPrinter( arguments.appendable ); + printFromQuery( printer ); + return; + } + setStructKeysAsHeaderIfRequired(); + var printer = newPrinter( arguments.appendable ); + printFromArray( printer ); + } + finally{ + if( local.KeyExists( "printer" ) ) + printer.close( JavaCast( "boolean", true ) ); + } + } + + private void function setQueryColumnsAsHeaderIfRequired(){ + if( !variables.useQueryColumnsAsHeader ) + return; + var columns = variables.library.getQueryHelper()._QueryColumnArray( variables.data ); + super.withHeader( columns ); + } + + private void function setStructKeysAsHeaderIfRequired(){ + if( !variables.useStructKeysAsHeader || !variables.data.Len() || !IsStruct( variables.data[ 1 ] ) ) + return; + var keys = variables.data[ 1 ].KeyArray(); + super.withHeader( keys ); + } + + private any function newPrinter( required appendable ){ + return variables.library.createJavaObject( "org.apache.commons.csv.CSVPrinter" ).init( arguments.appendable, variables.format ); + } + + private any function newAppendableBuffer(){ + if( IsNull( variables.filepath ) ) + return variables.library.getStringHelper().newJavaStringBuilder(); + return newBufferedFileWriter(); + } + + private any function newBufferedFileWriter(){ + var path = CreateObject( "java", "java.nio.file.Paths" ).get( JavaCast( "string", variables.filepath ), [] ); + var charset = CreateObject( "java", "java.nio.charset.Charset" ).forName( "UTF-8" ); + return CreateObject( "java", "java.nio.file.Files" ).newBufferedWriter( path, charset, [] ); + } + + private void function printFromArray( required printer ){ + if( useParallelThreads() ){ + var printRowFunction = function( row ){ + printRowFromArray( row, printer );//don't scope + }; + printUsingParallelThreads( printRowFunction ); + return; + } + for( var row in variables.data ){ + printRowFromArray( row, arguments.printer ); + } + } + + private void function printFromQuery( required printer ){ + var columns = variables.library.getQueryHelper()._QueryColumnArray( variables.data ); + if( useParallelThreads() ){ + var printRowFunction = function( row ){ + printRowFromQuery( row, columns, printer );//don't scope + }; + printUsingParallelThreads( printRowFunction ); + return; + } + for( var row in variables.data ){ + printRowFromQuery( row, columns, arguments.printer ); + } + } + + private void function printUsingParallelThreads( required function printRowFunction ){ + variables.data.Each( + arguments.printRowFunction + ,true + ,variables.parallelThreadsToUse + ); + } + + private function printRowFromArray( required row, required printer ){ + if( IsStruct( arguments.row ) ) + arguments.row = _StructValueArray( arguments.row ); + arguments.row = checkArrayRow( arguments.row ); + printRow( arguments.printer, arguments.row ); + } + + private function printRowFromQuery( required row, required columns, required printer ){ + arguments.row = convertQueryRowToArray( arguments.row, arguments.columns ); + printRow( arguments.printer, arguments.row ); + } + + private void function printRow( required printer, required array row ){ + arguments.printer.printRecord( JavaCast( "string[]", row ) );//force numbers to strings to avoid 0.0 formatting + } + + private array function checkArrayRow( required array row ){ + var totalColumns = arguments.row.Len(); + cfloop( from=1, to=totalColumns, index="i" ){ + var value = arguments.row[ i ]; + if( !IsSimpleValue( value ) ) + Throw( type=variables.library.getExceptionType() & ".invalidDataForCsv", message="Invalid data", detail="Your data contains complex values which cannot be output to CSV" ); + arguments.row[ i ] = formatDateString( value ); + } + return arguments.row; + } + + private string function formatDateString( required string value ){ + if( !variables.library.getDateHelper().isDateObject( arguments.value ) ) + return arguments.value; + return DateTimeFormat( arguments.value, variables.library.getDateFormats().DATETIME ); + } + + private array function convertQueryRowToArray( required struct row, required array columns ){ + var result = []; + for( var column IN arguments.columns ){ + var cellValue = formatDateString( arguments.row[ column ] ); + result.Append( cellValue ); + }; + return result; + } + + private boolean function useParallelThreads(){ + return variables.library.engineSupportsParallelLoopProcessing() && ( variables.parallelThreadsToUse > 1 ); + } + + private array function _StructValueArray( required struct data ){ + try{ + return StructValueArray( arguments.data ); // Lucee 5.3.8.117+ + } + catch( any exception ){ + if( !exception.message.REFindNoCase( "undefined|no matching function" ) ) + rethrow; + var result = []; + for( var key in arguments.data ){ + result.Append( arguments.data[ key ] ); + } + return result; + } + } + +} \ No newline at end of file diff --git a/test/specs/cellValue.cfm b/test/specs/cellValue.cfm index a7c957e..02c57e0 100644 --- a/test/specs/cellValue.cfm +++ b/test/specs/cellValue.cfm @@ -6,7 +6,7 @@ describe( "cellValue", function(){ }); it( "Gets the value from the specified cell", function(){ - var data = QueryNew( "column1,column2", "VarChar,VarChar", [ [ "a","b" ], [ "c","d" ] ] ); + var data = [ [ "a", "b" ], [ "c", "d" ] ]; workbooks.Each( function( wb ){ s.addRows( wb, data ); expect( s.getCellValue( wb, 2, 2 ) ).toBe( "d" ); diff --git a/test/specs/chaining.cfm b/test/specs/chaining.cfm index 00e671e..6714bc8 100644 --- a/test/specs/chaining.cfm +++ b/test/specs/chaining.cfm @@ -42,7 +42,7 @@ describe( "chaining", function(){ }); it( "Allows the workbook to be generated from a CSV file", function(){ - var csv = 'column1,column2#crlf#"Frumpo McNugget",12345'; + var csv = 'column1,column2#newline#"Frumpo McNugget",12345'; wb = s.newChainable().fromCsv( csv=csv, firstRowIsHeader=true ).getWorkbook(); expect( s.getCellValue( wb, 2, 2 ) ).toBe( "12345" ); }); diff --git a/test/specs/csvToQuery.cfm b/test/specs/csvToQuery.cfm index b062fc2..bc29100 100644 --- a/test/specs/csvToQuery.cfm +++ b/test/specs/csvToQuery.cfm @@ -67,21 +67,21 @@ describe( "csvToQuery", function(){ }); it( "can handle empty cells", function(){ - var csv = 'Frumpo,McNugget#crlf#Susi#crlf#Susi,#crlf#,Sorglos#crlf# '; + var csv = 'Frumpo,McNugget#newline#Susi#newline#Susi,#newline#,Sorglos#newline# '; var expected = QueryNew( "column1,column2", "", [ [ "Frumpo", "McNugget" ], [ "Susi", "" ], [ "Susi", "" ], [ "", "Sorglos" ] ] ); var actual = s.csvToQuery( csv ); expect( actual ).toBe( expected ); }); it( "can treat the first line as the column names", function(){ - var csv = 'Name,Phone#crlf#Frumpo,12345'; + var csv = 'Name,Phone#newline#Frumpo,12345'; var expected = QueryNew( "Name,Phone", "", [ [ "Frumpo", "12345" ] ] ); var actual = s.csvToQuery( csv=csv, firstRowIsHeader=true ); expect( actual ).toBe( expected ); }); it( "can handle spaces in header/column names", function(){ - var csv = 'Name,Phone Number#crlf#Frumpo,12345'; + var csv = 'Name,Phone Number#newline#Frumpo,12345'; if( s.getIsACF() ){ //ACF won't allow spaces in column names when creating queries programmatically. Use setColumnNames() to override: var expected = QueryNew( "column1,column2", "", [ [ "Frumpo", "12345" ] ] ); @@ -94,11 +94,11 @@ describe( "csvToQuery", function(){ }); it( "will preserve the case of header/column names", function(){ - var csv = 'Name,Phone#crlf#Frumpo McNugget,12345'; + var csv = 'Name,Phone#newline#Frumpo McNugget,12345'; var actual = s.csvToQuery( csv=csv, firstRowIsHeader=true ); expect( actual.getColumnNames()[ 1 ] ).toBeWithCase( "Name" ); //invalid variable name - csv = '1st Name,Phone#crlf#Frumpo McNugget,12345'; + csv = '1st Name,Phone#newline#Frumpo McNugget,12345'; actual = s.csvToQuery( csv=csv, firstRowIsHeader=true ); expect( actual.getColumnNames()[ 1 ] ).toBeWithCase( "1st Name" ); }); @@ -106,27 +106,27 @@ describe( "csvToQuery", function(){ describe( "trimming", function(){ it( "will trim the csv string by default", function(){ - var csv = crlf & '"Frumpo McNugget",12345' & crlf; + var csv = newline & '"Frumpo McNugget",12345' & newline; var actual = s.csvToQuery( csv ); expect( actual ).toBe( basicExpectedQuery ); }); it( "will trim the csv file by default", function(){ - var csv = crlf & '"Frumpo McNugget",12345' & crlf; + var csv = newline & '"Frumpo McNugget",12345' & newline; FileWrite( tempCsvPath, csv ); var actual = s.csvToQuery( filepath: tempCsvPath ); expect( actual ).toBe( basicExpectedQuery ); }); it( "can preserve a string's leading/trailing space", function(){ - var csv = crlf & '"Frumpo McNugget",12345' & crlf; + var csv = newline & '"Frumpo McNugget",12345' & newline; var actual = s.csvToQuery( csv: csv, trim: false ); expected = QueryNew( "column1,column2", "", [ [ "", "" ], [ "Frumpo McNugget", "12345" ] ] ); expect( actual ).toBe( expected ); }); it( "can preserve a file's leading/trailing space", function(){ - var csv = crlf & '"Frumpo McNugget",12345' & crlf; + var csv = newline & '"Frumpo McNugget",12345' & newline; FileWrite( tempCsvPath, csv ); var actual = s.csvToQuery( filepath: tempCsvPath, trim: false ); expected = QueryNew( "column1,column2", "", [ [ "", "" ], [ "Frumpo McNugget", "12345" ] ] ); @@ -174,7 +174,7 @@ describe( "csvToQuery", function(){ }); it( "ColumnNames argument overrides firstRowIsHeader: none of the header row values will be used", function(){ - var csv = 'header1,header2#crlf#"Frumpo McNugget",12345'; + var csv = 'header1,header2#newline#"Frumpo McNugget",12345'; var columnNames = [ "name", "phone number" ]; var q = s.csvToQuery( csv=csv, queryColumnNames=columnNames ); expect( q.getColumnNames()[ 1 ] ).toBe( columnNames[ 1 ] ); @@ -182,7 +182,7 @@ describe( "csvToQuery", function(){ }); it( "Allows csv header names to be made safe for query column names", function(){ - var csv = 'id,id,"A B","x/?y","(a)"," A","##1","1a"#crlf#1,2,3,4,5,6,7,8'; + var csv = 'id,id,"A B","x/?y","(a)"," A","##1","1a"#newline#1,2,3,4,5,6,7,8'; var q = s.csvToQuery( csv=csv, firstRowIsHeader=true, makeColumnNamesSafe=true ); expect( q.getColumnNames() ).toBe( [ "id", "id2", "A_B", "x_y", "_a_", "A", "Number1", "_a" ] ); }); @@ -202,7 +202,7 @@ describe( "csvToQuery", function(){ }); it( "allows the query column types to be manually set where the column order isn't known, but the header row values are", function(){ - var csv = 'integer,double,"string column",time#crlf#1,1.1,string,12:00'; + var csv = 'integer,double,"string column",time#newline#1,1.1,string,12:00'; var columnTypes = { "string column": "VARCHAR", "integer": "INTEGER", "time": "TIME", "double": "DOUBLE" };//not in order var q = s.csvToQuery( csv=csv, queryColumnTypes="Integer,Double,VarChar,Time", firstRowIsHeader=true ); var columns = GetMetaData( q ); @@ -235,7 +235,7 @@ describe( "csvToQuery", function(){ }); it( "automatic detecting of query column types ignores blank cells", function(){ - var csv = ',,,#crlf#,2,test,2021-03-10 12:00:00#crlf#1,1.1,string,2021-03-10 12:00:00#crlf#1,,,'; + var csv = ',,,#newline#,2,test,2021-03-10 12:00:00#newline#1,1.1,string,2021-03-10 12:00:00#newline#1,,,'; var q = s.csvToQuery( csv=csv, queryColumnTypes="auto" ); var columns = GetMetaData( q ); expect( columns[ 1 ].typeName ).toBe( "DOUBLE" ); diff --git a/test/specs/dateFormats.cfm b/test/specs/dateFormats.cfm index 8245224..417a5a8 100644 --- a/test/specs/dateFormats.cfm +++ b/test/specs/dateFormats.cfm @@ -90,7 +90,7 @@ describe( "dateFormats customisability",function(){ var actual = s.read( src=path, format="html" ); var expected = "