From f5f0d00a75d642757964ae1e03bb59505424c2c3 Mon Sep 17 00:00:00 2001 From: 794404 Date: Wed, 15 Jul 2020 13:02:30 +0200 Subject: [PATCH 1/3] add write_csv & change skip_header to with_header option --- src/lib/csv.pl | 113 ++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 103 insertions(+), 10 deletions(-) diff --git a/src/lib/csv.pl b/src/lib/csv.pl index 16216166..b294b2b0 100644 --- a/src/lib/csv.pl +++ b/src/lib/csv.pl @@ -1,9 +1,12 @@ /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Predicates for parsing CSV data - Only two options are provided with default values : - - token_separator(',') - - skip_header(false) + + Read csv files + + Only two options with default values : + - token_separator(',') + - with_header(true) Examples @@ -16,7 +19,7 @@ * with some options: - ?- phrase(parse_csv(Data, [skip_header(true),token_separator(';')]), "col1;col2;col3,col4\none;2;;three"). + ?- phrase(parse_csv(Data, [with_header(false), token_separator(';')]), "one;2;;three"). Data = frame([],[["one",2,[],"three"]]). * parsing a csv file: @@ -24,11 +27,34 @@ ?- use_module(library(csv)). ?- use_module(library(pio)). ?- phrase_from_file(parse_csv(frame(Header, Rows)), './test.csv'). + + + Write csv files + + Four options with default values : + - line_separator('\n') + - token_separator(',') + - with_header(true) + - null_value(empty) + + Examples + + * writing a csv file: + + ?- use_module(library(csv)). + ?- write_csv('./test.csv', frame(["col1","col2","col3","col4"], [["one",2,[],"three"]])). + + * with some options + + ?- use_module(library(csv)). + ?- write_csv('./test.csv', frame(["col1","col2","col3","col4"], [["one",2,[],"three"]]), [with_header(false), line_separator('\r\n'), token_separator(';'), null_value('\\N')]). - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ :- module(csv, [ parse_csv//1, - parse_csv//2 + parse_csv//2, + write_csv/2, + write_csv/3 ]). :- use_module(library(dcgs)). @@ -49,6 +75,74 @@ option_extends([X | Y], Opt0, Opt) :- ; option_extends(Y, [X | Opt0], Opt) ). +%% -- write -- + + +escaped_field([], []). +escaped_field(['"' | Y], ['"', '"' | R]) :- + escaped_field(Y, R). +escaped_field([X | Y], [X | R]) :- + X \== '"', + escaped_field(Y, R). + + +ensure_escaped(Field, Field) :- + (atom(Field); integer(Field); float(Field)). +ensure_escaped([X | Y], Field) :- + escaped_field([X | Y], Field). + + +write_field(Field, Opt) :- +( Field \== [] -> + ensure_escaped(Field, Field0), + write(Field0) + ; option(null_value(Null_Value), Opt), + ( Null_Value == empty -> true + ; write(Null_Value))). + + +write_row([Field], Opt) :- + write_field(Field, Opt). +write_row([Field, X | Y], Opt) :- + write_field(Field, Opt), + option(token_separator(Tk_Sep), Opt), + write(Tk_Sep), + write_row([X | Y], Opt). + + +write_rows([Row], Opt) :- + write_row(Row, Opt). +write_rows([Row, X | Y], Opt) :- + option(line_separator(Line_Sep), Opt), + write_row(Row, Opt), + write(Line_Sep), + write_rows([X | Y], Opt). + + +write_csv(File_Name, frame(Header, Rows), Opt) :- + option_extends(Opt, [ + null_value(empty), + token_separator(','), + with_header(true), + line_separator('\n') + ], Opt0), + open(File_Name, write, Out), + set_output(Out), + option(with_header(With_Header), Opt0), + ( With_Header == true -> + write_row(Header, Opt0), + option(line_separator(Line_Sep), Opt0), + write(Line_Sep) + ; true), + write_rows(Rows, Opt0), + close(Out), + set_output(user_output). +write_csv(File_Name, Frm) :- + write_csv(File_Name, Frm, []). + + +%% -- read -- + tokens([], Opt), [Tk_Sep] --> { option(token_separator(Tk_Sep), Opt) }, @@ -124,17 +218,16 @@ rows(R, Opt) --> parse_csv(frame(Header, Rows), Opt) --> { option_extends(Opt, [ - skip_header(false), + with_header(true), token_separator(',') ], Opt0) }, - ( { member(skip_header(false), Opt0) } -> + ( { option(with_header(With_Header), Opt0), + With_Header == true } -> row(Header, Opt0), { Header \== [[]] }, end_token - ; row(_, Opt0), - end_token, - { Header = [] }), + ; { Header = [] }), rows(Rows, Opt0). parse_csv(R) --> parse_csv(R, []). From 5705c4282075ba337492db3939970223b692e69e Mon Sep 17 00:00:00 2001 From: Cyrille Duret Date: Wed, 15 Jul 2020 18:26:57 +0200 Subject: [PATCH 2/3] set throw() to follow ISO recommendations --- src/lib/csv.pl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/csv.pl b/src/lib/csv.pl index b294b2b0..78775871 100644 --- a/src/lib/csv.pl +++ b/src/lib/csv.pl @@ -63,7 +63,7 @@ option(W, O) :- ( member(W, O) -> true - ; throw(not_found_error(W, O))). + ; throw(error(unknown_opt_error(W, O), option/2)). option_extends([], Opt, Opt). From 920644afaf7c3650769411eac85f39bd29cb76ca Mon Sep 17 00:00:00 2001 From: Cyrille Duret Date: Fri, 17 Jul 2020 01:21:40 +0200 Subject: [PATCH 3/3] set domain_error exception & use format/3 with setup_call_cleanup/3 for stream writing --- src/lib/csv.pl | 65 +++++++++++++++++++++++++++----------------------- 1 file changed, 35 insertions(+), 30 deletions(-) diff --git a/src/lib/csv.pl b/src/lib/csv.pl index 78775871..478d425c 100644 --- a/src/lib/csv.pl +++ b/src/lib/csv.pl @@ -57,13 +57,15 @@ write_csv/3 ]). +:- use_module(library(format)). :- use_module(library(dcgs)). +:- use_module(library(iso_ext)). :- use_module(library(lists)). option(W, O) :- ( member(W, O) -> true - ; throw(error(unknown_opt_error(W, O), option/2)). + ; throw(error(domain_error(csv_option, W), option/2))). option_extends([], Opt, Opt). @@ -92,51 +94,54 @@ ensure_escaped([X | Y], Field) :- escaped_field([X | Y], Field). -write_field(Field, Opt) :- -( Field \== [] -> - ensure_escaped(Field, Field0), - write(Field0) +write_field(Out, Field, Opt) :- + ( Field \== [] -> + ensure_escaped(Field, Field0), + format(Out, "~w", [Field0]) ; option(null_value(Null_Value), Opt), ( Null_Value == empty -> true - ; write(Null_Value))). + ; format(Out, "~w", [Null_Value]))). -write_row([Field], Opt) :- - write_field(Field, Opt). -write_row([Field, X | Y], Opt) :- - write_field(Field, Opt), +write_row(Out, [Field], Opt) :- + write_field(Out, Field, Opt). +write_row(Out, [Field, X | Y], Opt) :- + write_field(Out, Field, Opt), option(token_separator(Tk_Sep), Opt), - write(Tk_Sep), - write_row([X | Y], Opt). + format(Out, "~w", [Tk_Sep]), + write_row(Out, [X | Y], Opt). -write_rows([Row], Opt) :- - write_row(Row, Opt). -write_rows([Row, X | Y], Opt) :- +write_rows(Out, [Row], Opt) :- + write_row(Out, Row, Opt). +write_rows(Out, [Row, X | Y], Opt) :- option(line_separator(Line_Sep), Opt), - write_row(Row, Opt), - write(Line_Sep), - write_rows([X | Y], Opt). + write_row(Out, Row, Opt), + format(Out, "~w", [Line_Sep]), + write_rows(Out, [X | Y], Opt). -write_csv(File_Name, frame(Header, Rows), Opt) :- +write_csv_(Out, frame(Header, Rows), Opt) :- + option(with_header(With_Header), Opt), + ( With_Header == true -> + write_row(Out, Header, Opt), + option(line_separator(Line_Sep), Opt), + format(Out, "~w", [Line_Sep]) + ; true), + write_rows(Out, Rows, Opt). + + +write_csv(File_Name, Frm, Opt) :- option_extends(Opt, [ null_value(empty), token_separator(','), with_header(true), line_separator('\n') ], Opt0), - open(File_Name, write, Out), - set_output(Out), - option(with_header(With_Header), Opt0), - ( With_Header == true -> - write_row(Header, Opt0), - option(line_separator(Line_Sep), Opt0), - write(Line_Sep) - ; true), - write_rows(Rows, Opt0), - close(Out), - set_output(user_output). + setup_call_cleanup( + open(File_Name, write, Out), + write_csv_(Out, Frm, Opt0), + close(Out)). write_csv(File_Name, Frm) :- write_csv(File_Name, Frm, []).