/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Predicates for parsing CSV data Read csv files Only two options with default values : - token_separator(',') - with_header(true) Examples * parsing a csv string: ?- use_module(library(csv)). ?- use_module(library(dcgs)). ?- phrase(parse_csv(Data), "col1,col2,col3,col4\none,2,,three"). Data = frame(["col1","col2","col3","col4"],[["one",2,[],"three"]]). * with some options: ?- phrase(parse_csv(Data, [with_header(false), token_separator(';')]), "one;2;;three"). Data = frame([],[["one",2,[],"three"]]). * parsing a csv file: ?- 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, write_csv/2, 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(domain_error(csv_option, W), option/2))). option_extends([], Opt, Opt). option_extends([X | Y], Opt0, Opt) :- functor(X, Name, 1), F0 =.. [Name, _], ( select(F0, Opt0, R) -> option_extends(Y, [X | R], 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(Out, Field, Opt) :- ( Field \== [] -> ensure_escaped(Field, Field0), format(Out, "~w", [Field0]) ; option(null_value(Null_Value), Opt), ( Null_Value == empty -> true ; format(Out, "~w", [Null_Value]))). 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), format(Out, "~w", [Tk_Sep]), write_row(Out, [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(Out, Row, Opt), format(Out, "~w", [Line_Sep]), write_rows(Out, [X | Y], 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), 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, []). %% -- read -- tokens([], Opt), [Tk_Sep] --> { option(token_separator(Tk_Sep), Opt) }, [Tk_Sep], !. tokens([], _), "\r\n" --> "\r\n", !. tokens([], _), "\n" --> "\n", !. tokens([], _), "\r" --> "\r", !. tokens([X | Y], Opt) --> [X], !, tokens(Y, Opt). tokens([], _) --> []. field(R, Opt) --> "\"", !, string_tokens(R, Opt). field(R, Opt) --> tokens(R0, Opt), { R0 \== [], catch(number_chars(R, R0), _, R = R0) }. field([], _) --> []. string_tokens(R, Opt) --> [X], ( { X == '"' } -> ( "\"" -> { R = [X | Y] }, string_tokens(Y, Opt) ; { R = [] }) ; { R = [X | Y] }, string_tokens(Y, Opt)). end_token --> "\r\n". end_token --> "\n". end_token --> "\r". end_token --> []. separator(Opt) --> { option(token_separator(Tk_Sep), Opt) }, [Tk_Sep]. row([X | Y], Opt) --> field(X, Opt), !, ( separator(Opt) -> row(Y, Opt) ; end_token -> { Y = [] }). rows(R, Opt) --> row(X, Opt), !, ( { X \== [[]] } -> rows(Y, Opt), { R = [X | Y] } ; { R = [] }). parse_csv(frame(Header, Rows), Opt) --> { option_extends(Opt, [ with_header(true), token_separator(',') ], Opt0) }, ( { option(with_header(With_Header), Opt0), With_Header == true } -> row(Header, Opt0), { Header \== [[]] }, end_token ; { Header = [] }), rows(Rows, Opt0). parse_csv(R) --> parse_csv(R, []).