; docformat = 'rst' ;+ ; :Author: Paulo Penteado (`http://www.ppenteado.net <http://www.ppenteado.net>`), Feb/2013 ; This file contains write_csv_pp, and a copy of IDL's write_csv routines (renamed write_csv_pp_original ; and write_csv_convert_pp_original), since write_csv_pp requires an edit on the original write_csv. ;- ; $Id: //depot/Release/ENVI51_IDL83/idl/idldir/lib/write_csv.pro#1 $ ; ; Copyright (c) 2008-2013, Exelis Visual Information Solutions, Inc. All ; rights reserved. Unauthorized reproduction is prohibited. ;---------------------------------------------------------------------------- function write_csv_convert_pp_original, data,noquote=noquote compile_opt idl2, hidden switch (SIZE(data, /TYPE)) of 7: begin ; string type ; Always surround all strings with quotes, to avoid problems with ; commas and whitespace. if noquote then begin data1=data nq=where((strpos(data,'"') ge 0) or (strpos(data,',') ge 0),nnq) if (nnq gt 0) then begin data1[nq]='"'+data[nq]+'"' endif endif else begin data1 = '"'+data+'"' endelse ; Now look for double-quote chars, which need to be escaped. hasQuote = WHERE(STRPOS(data, '"') ge 0, nQuote) if (nQuote gt 0) then begin d = data[hasQuote] for i=0,nQuote-1 do d[i] = STRJOIN(STRTOK(d[i],'"',/EXTRACT,/PRESERVE_NULL),'""') data1[hasQuote] = '"' + d + '"' endif return, data1 end ; Be sure to convert bytes to numbers 1: return, STRTRIM(FIX(data), 1) ; Use a format code for double-precision numbers. 5: return, STRTRIM(STRING(data, FORMAT='(g)'), 1) 6: ; complex and dcomplex (fall thru) 9: return, '"' + STRCOMPRESS(data, /REMOVE_ALL) + '"' else: begin ; regular numeric types return, STRTRIM(data, 2) end endswitch end ;---------------------------------------------------------------------------- ;+ ; :Description: ; The WRITE_CSV procedure writes data to a "comma-separated value" ; (comma-delimited) text file. ; ; This routine writes CSV files consisting of an optional line of column ; headers, followed by columnar data, with commas separating each field. ; Each row is a new record. ; ; This routine is written in the IDL language. Its source code can be ; found in the file write_csv.pro in the lib subdirectory of the IDL ; distribution. ; ; :Syntax: ; WRITE_CSV, Filename, Data1 [, Data2,..., Data8] ; [, HEADER=value] ; ; :Params: ; Filename ; A string containing the name of the CSV file to write. ; ; Data1...Data8 ; The data values to be written out to the CSV file. The data arguments ; can have the following forms: ; * Data1 can be an IDL structure, where each field contains a ; one-dimensional array (a vector) of data that corresponds ; to a separate column. The vectors must all have the same ; number of elements, but can have different data types. If Data1 ; is an IDL structure, then all other data arguments are ignored. ; * Data1 can be a two-dimensional array, where each column in the array ; corresponds to a separate column in the output file. If Data1 is ; a two-dimensional array, then all other data arguments are ignored. ; * Data1...Data8 are one-dimensional arrays (vectors), where each vector ; corresponds to a separate column in the output file. Each vector ; can have a different data type. ; ; :Keywords: ; HEADER ; Set this keyword equal to a string array containing the column header ; names. The number of elements in HEADER must match the number of ; columns provided in Data1...Data8. If HEADER is not present, ; then no header row is written. ; ; TABLE_HEADER ; Set this keyword to a scalar string or string array containing extra table lines ; to be written at the beginning of the file. ; ; :History: ; Written, CT, VIS, Nov 2008 ; MP, VIS, Oct 2009: Added keyword SKIP_HEADER ; Dec 2010: Better handling for byte and double precision data. ; ;- pro write_csv_pp_original, Filename, Data1, Data2, Data3, Data4, Data5, Data6, Data7, Data8, $ HEADER=header, TABLE_HEADER=tableHeader,append=append,noquote=noquote compile_opt idl2 ON_ERROR, 2 ;Return on error ON_IOERROR, ioerr if (N_PARAMS() lt 2) then $ MESSAGE, 'Incorrect number of arguments.' isStruct = SIZE(Data1, /TYPE) eq 8 isArray = SIZE(Data1, /N_DIM) eq 2 if (SIZE(Filename,/TYPE) ne 7) then $ MESSAGE, 'Filename must be a string.' if (N_ELEMENTS(Data1) eq 0) then $ MESSAGE, 'Data1 must contain data.' ; Verify that all columns have the same number of elements. msg = 'Data fields must all have the same number of elements.' if (isStruct) then begin nfields = N_TAGS(Data1) nrows = N_ELEMENTS(Data1.(0)) for i=1,nfields-1 do begin if (N_ELEMENTS(Data1.(i)) ne nrows) then $ MESSAGE, msg endfor endif else if (isArray) then begin d = SIZE(Data1, /DIM) nfields = d[0] nrows = d[1] endif else begin ; Individual data arguments nfields = N_PARAMS() - 1 nrows = N_ELEMENTS(Data1) switch (nfields) of 8: if (N_ELEMENTS(Data8) ne nrows) then MESSAGE, msg 7: if (N_ELEMENTS(Data7) ne nrows) then MESSAGE, msg 6: if (N_ELEMENTS(Data6) ne nrows) then MESSAGE, msg 5: if (N_ELEMENTS(Data5) ne nrows) then MESSAGE, msg 4: if (N_ELEMENTS(Data4) ne nrows) then MESSAGE, msg 3: if (N_ELEMENTS(Data3) ne nrows) then MESSAGE, msg 2: if (N_ELEMENTS(Data2) ne nrows) then MESSAGE, msg else: endswitch endelse ; Verify that the header (if provided) has the correct number of elements. nheader = N_Elements(header) if (nheader gt 0) then begin ; Quietly ignore null strings. if (ARRAY_EQUAL(header,'')) then begin nheader = 0 endif else begin if (nheader ne nfields || SIZE(header,/type) ne 7) then begin MESSAGE, 'HEADER must be a string array of length equal to the number of columns.' endif endelse endif ; Start writing the file. OPENW, lun, filename, /GET_LUN,append=append ; What about handling COMMAS or QUOTES?! format = (nfields ge 2) ? '(' + STRTRIM(nfields-1,2)+'(A,","),A)' : '(A)' ; Printing out extra headers to csv file if n_elements(tableHeader) gt 0 then begin for i=0, n_elements(tableHeader)-1 do begin ;check if there is comma in the string posComma = stregex(tableHeader[i], ',') posQuote = stregex(tableHeader[i], '"') if (posComma eq -1) && (posQuote eq -1) then printf, lun, tableHeader[i], FORMAT=format else printf, lun, '"' + tableHeader[i] + '"', FORMAT=format endfor endif if (nheader gt 0) then begin PRINTF, lun, header, FORMAT=format endif if (isStruct) then begin ; Structure fields strCopy = STRARR(nfields, nrows) for i=0,nfields-1 do begin strCopy[i,*] = WRITE_CSV_CONVERT_pp_original(Data1.(i),noquote=noquote) endfor PRINTF, lun, strCopy, FORMAT=format endif else if (isArray) then begin ; Two-dimensional array PRINTF, lun, WRITE_CSV_CONVERT_pp_original(Data1,noquote=noquote), FORMAT=format endif else begin ; Individual data arguments strCopy = STRARR(nfields, nrows) switch (nfields) of 8: strCopy[7,*] = WRITE_CSV_CONVERT_pp_original(Data8,noquote=noquote) 7: strCopy[6,*] = WRITE_CSV_CONVERT_pp_original(Data7,noquote=noquote) 6: strCopy[5,*] = WRITE_CSV_CONVERT_pp_original(Data6,noquote=noquote) 5: strCopy[4,*] = WRITE_CSV_CONVERT_pp_original(Data5,noquote=noquote) 4: strCopy[3,*] = WRITE_CSV_CONVERT_pp_original(Data4,noquote=noquote) 3: strCopy[2,*] = WRITE_CSV_CONVERT_pp_original(Data3,noquote=noquote) 2: strCopy[1,*] = WRITE_CSV_CONVERT_pp_original(Data2,noquote=noquote) 1: strCopy[0,*] = WRITE_CSV_CONVERT_pp_original(Data1,noquote=noquote) endswitch PRINTF, lun, strCopy, FORMAT=format endelse FREE_LUN, lun return ioerr: ON_IOERROR, null if (N_ELEMENTS(lun) gt 0) then $ FREE_LUN, lun MESSAGE, !ERROR_STATE.msg end ;+ ; :Description: ; A simple wrapper for write_csv, to write csv files using a structure's field names as column ; titles (setting `titlesfromfields`), ccepting nested structures, and with the option of writing the ; file by pieces. ; ; :Params: ; file: in, required, type=string ; Passed to write_csv, specifies the name of the file to write. ; data1: in, required ; Passed to write_csv, after the variable has its structures flattened by a call to ; `pp_struct_unravel`. ; data2: in, optional ; Passed unaltered to write_csv. ; data3: in, optional ; Passed unaltered to write_csv. ; data4: in, optional ; Passed unaltered to write_csv. ; data5: in, optional ; Passed unaltered to write_csv. ; data6: in, optional ; Passed unaltered to write_csv. ; data7: in, optional ; Passed unaltered to write_csv. ; data8: in, optional ; Passed unaltered to write_csv. ; ; :Keywords: ; titlesfromfields: in, optional ; If set, the column titles in the csv file are made by the field names in data1. ; verbose: in, optional, default=0 ; If set, write_csv_pp will inform which piece of the file it is currently writing ; divide: in, optional, default=1 ; Used to split the file writing into ``divide`` pieces. This is useful to save memory, since ; IDL's ``write_csv`` creates a temporary string array with the whole file contents, before writing it ; to the file, and that array can be several times larger than the input array. ; noquote: in, optional, default=0 ; If set, string fields that do not contain commas or double-quotes will not ; be quoted. If not set (default), all string fields are quoted. ; _ref_extra: in, out, optional ; Any other parameters are passed, unaltered, to / from write_csv. ; ; :Examples: ; Make a simple structure array and write it to a csv file:: ; ; s={a:1,b:{c:2.5,d:-9,e:0},f:1.8,g:'h'} ; s2=replicate(s,2) ; s2[1].a=-1 ; s2[1].f=-1.8 ; s2[1].g='h,i' ; write_csv_pp,'write_csv_pp_test.csv',s2,/titlesfromfields ; ; Which result in a file with: ; ; A,B_C,B_D,B_E,F,G ; ; 1,2.50000,-9,0,1.80000,"h" ; ; -1,2.50000,-9,0,-1.80000,"h,i" ; ; Compare with using the `noquote` keyword: ; ; write_csv_pp,'write_csv_pp_test.csv',s2,/titlesfromfields,/noquote ; ; Which produces ; ; A,B_C,B_D,B_E,F,G ; ; 1,2.50000,-9,0,1.80000,h ; ; -1,2.50000,-9,0,-1.80000,"h,i" ; ; On the first row, the string (the last column) is unquoted. On the second, ; it is still quoted because it contains a comma; without this quote, it ; would look like the row has an extra column. ; ; :Requires: `pp_struct_unravel` ; ; :Author: Paulo Penteado (`http://www.ppenteado.net <http://www.ppenteado.net>`), Feb/2013 ;- pro write_csv_pp,file,data1,data2,data3,data4,data5,data6,data7,data8,titlesfromfields=tf,$ divide=divide,_ref_extra=ex,verbose=verbose,noquote=noquote compile_opt idl2,logical_predicate noquote=keyword_set(noquote) divide=n_elements(divide) ? divide : 1LL nrows=n_elements(data1) blocksize=ceil(nrows*1d0/divide) nd=ceil(nrows*1d0/blocksize) u=pp_struct_unravel(data1,/testonly) if u then data0=pp_struct_unravel(data1) for i=0LL,nd-1 do begin fr=i*blocksize lr=((i+1LL)*blocksize-1)<(nrows-1LL) if keyword_set(verbose) then print,'write_csv_pp: writing file section ',strtrim(i+1,2),' of ',strtrim(nd,2) if keyword_set(tf) then begin header=tag_names(u ? data0 : data1) write_csv_pp_original,file,(u ? data0[fr:lr] : data1[fr:lr]),$ n_elements(data2) ? data2[fr:lr] : !null ,$ n_elements(data3) ? data3[fr:lr] : !null ,$ n_elements(data4) ? data4[fr:lr] : !null ,$ n_elements(data5) ? data5[fr:lr] : !null ,$ n_elements(data6) ? data6[fr:lr] : !null ,$ n_elements(data7) ? data7[fr:lr] : !null ,$ n_elements(data8) ? data8[fr:lr] : !null ,$ _strict_extra=ex,header=(i eq 0 ? header : !null),append=i,noquote=noquote endif else begin write_csv_pp_original,file,(u ? data0[fr:lr] : data1[fr:lr]),$ n_elements(data2) ? data2[fr:lr] : !null ,$ n_elements(data3) ? data3[fr:lr] : !null ,$ n_elements(data4) ? data4[fr:lr] : !null ,$ n_elements(data5) ? data5[fr:lr] : !null ,$ n_elements(data6) ? data6[fr:lr] : !null ,$ n_elements(data7) ? data7[fr:lr] : !null ,$ n_elements(data8) ? data8[fr:lr] : !null ,$ _strict_extra=ex,append=i,noquote=noquote endelse endfor end