1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
type csv = string list list
let all_equal (l : int list) =
let rec loop l (elt : int) =
match l with [] -> true | hd :: tl -> hd = elt && loop tl elt
in
match l with [] -> true | hd :: tl -> loop tl hd
module String_set = Set.Make (String)
let (csv1 : csv) (csv2 : csv) =
match (csv1, csv2) with
| [], _ | _, [] -> true
| :: _, :: _ ->
let = String_set.of_list header1 in
let = String_set.of_list header2 in
String_set.disjoint header1 header2
let concat ?( = true) (csv1 : csv) (csv2 : csv) : csv =
if Compare.List_lengths.(csv1 <> csv2) then
Stdlib.failwith "Csv.concat: CSVs have different length"
else
let lengths1 = List.map List.length csv1 in
let lengths2 = List.map List.length csv2 in
if not (all_equal lengths1) then
let msg = "Csv.concat: first argument has uneven # of lines" in
Stdlib.failwith msg
else if not (all_equal lengths2) then
let msg = "Csv.concat: second argument has uneven # of lines" in
Stdlib.failwith msg
else if check_disjoint_headers && not (disjoint_headers csv1 csv2) then
let msg = "Csv.concat: headers are not disjoint" in
Stdlib.failwith msg
else
WithExceptions.List.map2
~loc:__LOC__
(fun line1 line2 -> line1 @ line2)
csv1
csv2
let export ~filename ?(separator = ',') ?(linebreak = '\n') (data : csv) =
Format.eprintf "Exporting to %s@." filename ;
let sep_str = String.make 1 separator in
Out_channel.with_open_text filename @@ fun outfile ->
let fmtr = Format.formatter_of_out_channel outfile in
List.iter
(fun line ->
match line with
| [] -> ()
| _ ->
let s = String.concat sep_str line in
Format.fprintf fmtr "%s%c@?" s linebreak)
data
let read_lines name : string list =
In_channel.with_open_text name @@ fun ic ->
let rec loop acc =
match In_channel.input_line ic with
| Some s -> loop (s :: acc)
| None -> List.rev acc
in
loop []
exception Empty_csv_file
let import ~filename ?(separator = ',') () : csv =
Format.eprintf "Importing %s@." filename ;
let lines = read_lines filename in
let , rows =
match lines with
| [] -> raise Empty_csv_file
| :: tail -> (header, tail)
in
let = String.split_on_char separator header in
let ncols = List.length header in
let rows = List.map (String.split_on_char separator) rows in
if not (List.for_all (fun l -> Compare.List_length_with.(l = ncols)) rows)
then Stdlib.failwith "Csv.import: mismatch between header width and row width" ;
header :: rows
let append_columns ~filename ?(separator = ',') ?(linebreak = '\n') (data : csv)
=
let file_data =
try import ~filename ~separator ()
with Sys_error _ | Empty_csv_file ->
List.map (fun _ -> []) data
in
let csv_data = concat file_data data in
export ~filename ~separator ~linebreak csv_data
let export_stdout ?(separator = ',') ?(linebreak = '\n') (data : csv) =
Format.eprintf "Exporting to stdout@." ;
let sep_str = String.make 1 separator in
List.iter
(fun line ->
let s = String.concat sep_str line in
Format.printf "%s%c" s linebreak)
data ;
flush stdout