Skip to content

Commit 42b9ac0

Browse files
committed
added initial csv write capability.
1 parent c15a1c0 commit 42b9ac0

File tree

2 files changed

+241
-1
lines changed

2 files changed

+241
-1
lines changed

src/csv_module.f90

Lines changed: 222 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,14 @@ module csv_module
4747
type(csv_string),dimension(:),allocatable :: header !! the header
4848
type(csv_string),dimension(:,:),allocatable :: csv_data !! the data in the file
4949

50+
!for writing a csv file:
51+
integer :: icol = 0 !! last column written in current row
52+
integer :: iunit = 0 !! file unit for writing
53+
logical :: enclose_strings_in_quotes = .true. !! if true, all string cells
54+
!! will be enclosed in quotes.
55+
logical :: enclose_all_in_quotes = .false. !! if true, *all* cells will
56+
!! be enclosed in quotes.
57+
5058
contains
5159

5260
private
@@ -80,6 +88,12 @@ module csv_module
8088
procedure :: get_character_column
8189
procedure :: get_csv_string_column
8290

91+
procedure,public :: open => open_csv_file
92+
generic,public :: add => add_cell
93+
procedure :: add_cell
94+
procedure,public :: next_row
95+
procedure,public :: close => close_csv_file
96+
8397
procedure :: tokenize => tokenize_csv_line
8498
procedure :: read_line_from_file
8599
procedure :: get_column
@@ -231,6 +245,213 @@ subroutine read_csv_file(me,filename,header_row,skip_rows,status_ok)
231245
end subroutine read_csv_file
232246
!*****************************************************************************************
233247

248+
!*****************************************************************************************
249+
!>
250+
! Open a CSV file for writing
251+
252+
subroutine open_csv_file(me,filename,&
253+
n_cols,&
254+
enclose_strings_in_quotes,&
255+
enclose_all_in_quotes,&
256+
int_fmt,&
257+
real_fmt,&
258+
status_ok)
259+
260+
implicit none
261+
262+
class(csv_file),intent(inout) :: me
263+
character(len=*),intent(in) :: filename !! the CSV file to open
264+
integer,intent(in) :: n_cols !! number of columns in the file
265+
logical,intent(in),optional :: enclose_strings_in_quotes !! default is true
266+
logical,intent(in),optional :: enclose_all_in_quotes !! default is false
267+
character(len=*),intent(in),optional :: int_fmt !! format string for writing integers
268+
!! (default is I10)
269+
character(len=*),intent(in),optional :: real_fmt !! format string for writing reals
270+
!! (default is E27.17)
271+
logical,intent(out) :: status_ok !! status flag
272+
273+
integer :: istat !! open `iostat` flag
274+
275+
call me%destroy()
276+
277+
me%n_cols = n_cols
278+
me%n_rows = 0
279+
me%icol = 0
280+
281+
if (present(enclose_strings_in_quotes)) then
282+
me%enclose_strings_in_quotes = enclose_strings_in_quotes
283+
else
284+
me%enclose_strings_in_quotes = .true.
285+
end if
286+
if (present(enclose_all_in_quotes)) then
287+
me%enclose_all_in_quotes = enclose_all_in_quotes
288+
else
289+
me%enclose_all_in_quotes = .false.
290+
end if
291+
if (me%enclose_all_in_quotes) me%enclose_strings_in_quotes = .true. ! override
292+
293+
open(newunit=me%iunit,file=filename,status='REPLACE',iostat=istat)
294+
if (istat==0) then
295+
status_ok = .true.
296+
else
297+
write(error_unit,'(A)') 'Error opening file: '//trim(filename)
298+
status_ok = .false.
299+
end if
300+
301+
end subroutine open_csv_file
302+
!*****************************************************************************************
303+
304+
!*****************************************************************************************
305+
!>
306+
! Close a CSV file after writing
307+
308+
subroutine close_csv_file(me,status_ok)
309+
310+
implicit none
311+
312+
class(csv_file),intent(inout) :: me
313+
logical,intent(out) :: status_ok !! status flag
314+
315+
integer :: istat !! close `iostat` flag
316+
317+
close(me%iunit,iostat=istat)
318+
status_ok = istat==0
319+
320+
end subroutine close_csv_file
321+
!*****************************************************************************************
322+
323+
!*****************************************************************************************
324+
!>
325+
! Add a cell to a CSV file.
326+
327+
subroutine add_cell(me,val,int_fmt,real_fmt,trim_str)
328+
329+
implicit none
330+
331+
class(csv_file),intent(inout) :: me
332+
class(*),intent(in) :: val !! the value to add
333+
character(len=*),intent(in),optional :: int_fmt !! format string for integers
334+
character(len=*),intent(in),optional :: real_fmt !! format string for reals
335+
logical,intent(in),optional :: trim_str !! to trim the string
336+
337+
integer :: istat !! write `iostat` flag
338+
character(len=:),allocatable :: ifmt !! actual format string to use for integers
339+
character(len=:),allocatable :: rfmt !! actual format string to use for REALS
340+
logical :: trimstr !! if the strings are to be trimmed
341+
342+
! make sure the row isn't already finished
343+
if (me%icol<me%n_cols) then
344+
345+
me%icol = me%icol + 1
346+
347+
if (me%enclose_all_in_quotes) then
348+
write(me%iunit,fmt='(A)',advance='NO',iostat=istat) me%quote
349+
end if
350+
351+
select type (val)
352+
type is (integer(ip))
353+
if (present(int_fmt)) then
354+
ifmt = trim(adjustl(int_fmt))
355+
else
356+
ifmt = '(I10)'
357+
end if
358+
write(me%iunit,fmt=ifmt,advance='NO',iostat=istat) val !TODO trim(adjustl()) this string
359+
type is (real(wp))
360+
if (present(real_fmt)) then
361+
rfmt = trim(adjustl(real_fmt))
362+
else
363+
rfmt = '(E27.17)'
364+
end if
365+
write(me%iunit,fmt=rfmt,advance='NO',iostat=istat) val !TODO trim(adjustl()) this string
366+
type is (logical)
367+
if (val) then
368+
write(me%iunit,fmt='(A)',advance='NO',iostat=istat) 'T' !TODO make these user-defined strings
369+
else
370+
write(me%iunit,fmt='(A)',advance='NO',iostat=istat) 'F'
371+
end if
372+
type is (character(len=*))
373+
if (me%enclose_strings_in_quotes .and. .not. me%enclose_all_in_quotes) &
374+
write(me%iunit,fmt='(A)',advance='NO',iostat=istat) me%quote
375+
if (present(trim_str)) then
376+
trimstr = trim_str
377+
else
378+
trimstr = .false.
379+
end if
380+
if (trimstr) then
381+
write(me%iunit,fmt='(A)',advance='NO',iostat=istat) trim(val)
382+
else
383+
write(me%iunit,fmt='(A)',advance='NO',iostat=istat) val
384+
end if
385+
if (me%enclose_strings_in_quotes .and. .not. me%enclose_all_in_quotes) &
386+
write(me%iunit,fmt='(A)',advance='NO',iostat=istat) me%quote
387+
type is (csv_string)
388+
if (me%enclose_strings_in_quotes .and. .not. me%enclose_all_in_quotes) &
389+
write(me%iunit,fmt='(A)',advance='NO',iostat=istat) me%quote
390+
if (present(trim_str)) then
391+
trimstr = trim_str
392+
else
393+
trimstr = .false.
394+
end if
395+
if (trimstr) then
396+
write(me%iunit,fmt='(A)',advance='NO',iostat=istat) trim(val%str)
397+
else
398+
write(me%iunit,fmt='(A)',advance='NO',iostat=istat) val%str
399+
end if
400+
if (me%enclose_strings_in_quotes .and. .not. me%enclose_all_in_quotes) &
401+
write(me%iunit,fmt='(A)',advance='NO',iostat=istat) me%quote
402+
class default
403+
write(error_unit,'(A)') 'Error: cannot write unknown variable type to CSV file.'
404+
end select
405+
406+
if (me%enclose_all_in_quotes) then
407+
write(me%iunit,fmt='(A)',advance='NO',iostat=istat) me%quote
408+
end if
409+
if (me%icol<me%n_cols) write(me%iunit,fmt='(A)',advance='NO',iostat=istat) me%delimiter
410+
411+
else
412+
write(error_unit,'(A)') 'Error: cannot write more cells to the current row.'
413+
end if
414+
415+
end subroutine add_cell
416+
!*****************************************************************************************
417+
418+
!*****************************************************************************************
419+
!>
420+
! Advance to the next row in the CSV file
421+
! (write any blank cells that are necessary to finish the row)
422+
423+
subroutine next_row(me)
424+
425+
implicit none
426+
427+
class(csv_file),intent(inout) :: me
428+
429+
integer :: i !! counter
430+
integer :: n !! number of blank cells to write
431+
432+
if (me%icol>0) then
433+
n = me%n_cols - me%icol
434+
do i=1,n
435+
if (i==n) then !no trailing delimiter
436+
if (me%enclose_strings_in_quotes) then
437+
write(me%iunit,'(A)',advance='NO') me%quote//me%quote
438+
end if
439+
else
440+
if (me%enclose_strings_in_quotes) then
441+
write(me%iunit,'(A)',advance='NO') me%quote//me%quote//me%delimiter
442+
else
443+
write(me%iunit,'(A)',advance='NO') me%delimiter
444+
end if
445+
end if
446+
end do
447+
write(me%iunit,'(A)') '' ! new line
448+
end if
449+
450+
me%icol = 0 ! this row is finished
451+
452+
end subroutine next_row
453+
!*****************************************************************************************
454+
234455
!*****************************************************************************************
235456
!>
236457
! Returns the header as a `type(csv_string)` array.
@@ -869,7 +1090,7 @@ pure subroutine split(str,token,chunk_size,vals)
8691090
type(csv_string),dimension(:),allocatable,intent(out) :: vals
8701091

8711092
integer :: i
872-
integer :: len_str
1093+
integer :: len_str
8731094
integer :: len_token !! length of the token
8741095
integer :: n_tokens !! number of tokens
8751096
integer :: i1 !! index

src/tests/csv_test.f90

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ program csv_test
1212
implicit none
1313

1414
type(csv_file) :: f
15+
type(csv_file) :: f2
1516
integer :: i !! counter
1617
character(len=30),dimension(:),allocatable :: header !! the header
1718
character(len=30),dimension(:,:),allocatable :: csv_data !! the data from the file as strings
@@ -59,5 +60,23 @@ program csv_test
5960
write(*,'(F27.16,1x)',advance='NO') z
6061
write(*,*) ''
6162

63+
!now test creating a CSF
64+
call f2%open('test2.csv',n_cols=3,status_ok=status_ok)
65+
66+
call f2%add('x')
67+
call f2%add('y')
68+
call f2%add('z')
69+
call f2%next_row()
70+
71+
call f2%add(1.0_wp)
72+
call f2%add(2.0_wp)
73+
call f2%add(3.0_wp)
74+
call f2%next_row()
75+
call f2%add(4.0_wp)
76+
call f2%add(5.0_wp)
77+
call f2%add(6.0_wp)
78+
call f2%next_row()
79+
call f2%close(status_ok)
80+
6281
end program csv_test
6382
!*****************************************************************************************

0 commit comments

Comments
 (0)