├── COPYING ├── Make.options ├── Makefile ├── README.md ├── samples ├── csv_ex.m ├── data.csv ├── init_from_header_ex.csv └── init_from_header_ex.m ├── src ├── Makefile ├── Mercury.options ├── csv.char_buffer.m ├── csv.m ├── csv.raw_reader.m ├── csv.record_parser.m ├── csv.typed_reader.m └── mercury_csv.m └── tests ├── Makefile ├── blank_lines.exp ├── blank_lines.inp ├── bool1.exp ├── bool1.inp ├── bool2.exp ├── bool2.inp ├── bool3.exp ├── bool3.inp ├── bool4.exp ├── bool4.inp ├── comments.exp ├── comments.inp ├── companies.exp ├── companies.inp ├── conv_funcs_valid.exp ├── conv_funcs_valid.inp ├── countrylist.exp ├── countrylist.inp ├── date1.exp ├── date1.inp ├── date2.exp ├── date2.inp ├── date_time.exp ├── date_time.inp ├── date_time2.exp ├── date_time2.inp ├── discard-bad.err_exp ├── discard-bad.inp ├── discard.exp ├── discard.inp ├── field_desc_mismatch.err_exp ├── field_desc_mismatch.inp ├── field_limit.err_exp ├── field_limit.inp ├── harness.m ├── header_eof.err_exp ├── header_eof.inp ├── invalid_date1.err_exp ├── invalid_date1.inp ├── maybe.exp ├── maybe.inp ├── qm_in_uqf1.exp ├── qm_in_uqf1.inp ├── qm_in_uqf2.err_exp ├── qm_in_uqf2.inp ├── real1.exp ├── real1.inp ├── real2.exp ├── real2.inp ├── regression1.exp ├── regression1.inp ├── regression2.exp ├── regression2.inp ├── regression3.exp ├── regression3.inp ├── regression4-bad.err_exp ├── regression4-bad.inp ├── regression4.exp ├── regression4.inp ├── test_cases.m ├── test_csv.m ├── trailing_fields.exp ├── trailing_fields.inp ├── univ.exp ├── univ.inp ├── unmatched_quote.err_exp ├── unmatched_quote.inp ├── unmatched_quote2.err_exp ├── unmatched_quote2.inp ├── unmatched_quote3.err_exp ├── unmatched_quote3.inp ├── width_limit.err_exp ├── width_limit.inp ├── width_limit2.err_exp ├── width_limit2.inp ├── wiki1.exp ├── wiki1.inp ├── wiki2.exp └── wiki2.inp /COPYING: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013-2024, Julien Fischer. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | Redistributions in binary form must reproduce the above copyright notice, this 11 | list of conditions and the following disclaimer in the documentation and/or 12 | other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 15 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 18 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 20 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 21 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 22 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | -------------------------------------------------------------------------------- /Make.options: -------------------------------------------------------------------------------- 1 | # Both of these can be overridden on the command line. 2 | # 3 | MMC = mmc 4 | INSTALL_PREFIX = /usr/lib/mercury_csv 5 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | include Make.options 2 | 3 | .PHONY: default 4 | default: 5 | cd src && $(MAKE) default 6 | 7 | .PHONY: install 8 | install: 9 | cd src && $(MAKE) install 10 | 11 | .PHONY: runtests 12 | runtests: 13 | cd tests && $(MAKE) runtests 14 | 15 | .PHONY: realclean 16 | realclean: 17 | cd src && $(MAKE) realclean 18 | cd tests && $(MAKE) realclean 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Mercury-CSV 2 | 3 | `mercury_csv` is a [Mercury](http://www.mercurylang.org) library for reading 4 | CSV data from character streams. 5 | 6 | ## Features 7 | 8 | * follows [RFC 4180](https://tools.ietf.org/rfc/rfc4180.txt) where possible 9 | * can optionally trim whitespace from fields 10 | * handles CRLF line endings 11 | * allows blank fields 12 | * supports reading fields either as strings (raw) or as Mercury standard 13 | library types (typed). 14 | * the "typed" interface allows an arbitrary number of actions 15 | (e.g. transformations, validity checks etc) to be performed on 16 | each field after it is read. 17 | * allows limits to be imposed on number of characters in a field 18 | and the number of fields in a record 19 | * supports the presence of an optional header record 20 | * field delimiter character can be selected by the user 21 | * can optionally ignore blank lines 22 | * can optionally ignore trailing fields 23 | 24 | ## License 25 | 26 | `mercury_csv` is licensed under a simple 2-clause BSD style license. 27 | See the file [COPYING](COPYING) for details. 28 | 29 | ## Installation 30 | 31 | Check the values in the file [Make.options](Make.options) to see if they agree 32 | with your system, then do: 33 | 34 | $ make install 35 | 36 | You can also override values in [Make.options](Make.options) on the command 37 | line, for example 38 | 39 | $ make INSTALL_PREFIX=/foo/bar install 40 | 41 | causes the library to be installed in the directory `/foo/bar`. 42 | 43 | ## Testing 44 | 45 | To run the regression test suite, do: 46 | 47 | $ make runtests 48 | 49 | ## TODO 50 | 51 | * write some samples 52 | * write some more documentation 53 | * support CSV writers as well as readers 54 | 55 | ## Mercury 22.01.X Compatibility 56 | 57 | The code on the master branch is **not** compatible with Mercury 22.01.X. 58 | If you require a version of `mercury_csv` that works with Mercury 22.01.X, 59 | then checkout the `mercury_22_01` branch. 60 | 61 | ## Mercury 20.06.X Compatibility 62 | 63 | The code on the master branch is **not** compatible with Mercury 20.06.X. 64 | If you require a version of `mercury_csv` that works with Mercury 20.06.X, 65 | then checkout the `mercury_20_06` branch. 66 | 67 | ## Mercury 20.01.X Compatibility 68 | 69 | The code on the master branch is **not** compatible with Mercury 20.01.X. 70 | If you require a version of `mercury_csv` that works with Mercury 20.01.X, 71 | then checkout the `mercury_20_01` branch. 72 | 73 | ## Author 74 | 75 | Julien Fischer 76 | -------------------------------------------------------------------------------- /samples/csv_ex.m: -------------------------------------------------------------------------------- 1 | %-----------------------------------------------------------------------------% 2 | % vim: ft=mercury ts=4 sw=4 et 3 | %-----------------------------------------------------------------------------% 4 | % 5 | % Author: Julien Fischer 6 | % 7 | % A small example of how to use the CSV reader. 8 | % 9 | %-----------------------------------------------------------------------------% 10 | 11 | :- module csv_ex. 12 | :- interface. 13 | 14 | %-----------------------------------------------------------------------------% 15 | 16 | :- import_module io. 17 | 18 | :- pred main(io::di, io::uo) is det. 19 | 20 | %-----------------------------------------------------------------------------% 21 | %-----------------------------------------------------------------------------% 22 | 23 | :- implementation. 24 | 25 | :- import_module csv. 26 | 27 | :- import_module bool. 28 | :- import_module int. 29 | :- import_module list. 30 | :- import_module maybe. 31 | :- import_module require. 32 | :- import_module stream. 33 | 34 | %-----------------------------------------------------------------------------% 35 | 36 | main(!IO) :- 37 | io.command_line_arguments(Args, !IO), 38 | ( 39 | % If there are no command line arguments then read our input 40 | % from the standard input. 41 | % 42 | Args = [], 43 | io.stdin_stream(InputFile, !IO), 44 | process_csv(InputFile, !IO) 45 | ; 46 | Args = [InputFileName], 47 | io.open_input(InputFileName, OpenInputResult, !IO), 48 | ( 49 | OpenInputResult = ok(InputFile), 50 | process_csv(InputFile, !IO), 51 | io.close_input(InputFile, !IO) 52 | ; 53 | OpenInputResult = error(_IO_Error), 54 | error("cannot open input file") 55 | ) 56 | ; 57 | Args = [_, _ | _], 58 | error("multiple input arguments") 59 | ). 60 | 61 | %-----------------------------------------------------------------------------% 62 | 63 | :- pred process_csv(io.text_input_stream::in, io::di, io::uo) is det. 64 | 65 | process_csv(InputFile, !IO) :- 66 | 67 | % A CSV header descriptor (type: csv.header_desc/0) tells the CSV reader 68 | % whether we expect the file to have a header line and, if so, is there 69 | % any field width limit for the header fields? 70 | % 71 | % Here we expect the presence of an initial header line and limit the 72 | % width of the header fields to at most 255 characters. 73 | % 74 | HeaderDesc = csv.header_desc(limited(255)), 75 | 76 | % A CSV record descriptor (type: csv.record_desc/0) describes the structure 77 | % of a record (i.e. a single line) in the CSV data. It is a list of 78 | % field descriptors (type: csv.field_desc/0), where the order of the elements 79 | % in the list corresponds to the order of the fields in the record. 80 | % 81 | RecordDesc = [ 82 | 83 | % This field descriptor tells the CSV reader to expect the first field 84 | % to be an integer. The "do_not_allow_floats" parameter tells the 85 | % reader not to allow a floating point value in place of an int. 86 | % (Using this parameter you can tell the reader to accept a floating 87 | % point value in place of an int, so long as you provide a method 88 | % for converting the floating point value to an int. 89 | % The user-supplied "field action" function ensure_gt_zero/1 will be 90 | % called with the field value as its argument after the field has been 91 | % read in (see below for the definition of ensure_gt_zero/1). 92 | % 93 | % The second argument of this field descriptor, "limited(10)", says 94 | % that this maximum number of characters that can occur in this field 95 | % is 10 characters. The CSV reader will return an error if this limit 96 | % is exceeded. 97 | % 98 | % The third argument, "do_not_trim_whitespace", tells the CSV reader 99 | % not to trim any leading- or trailing-whitespace from the raw field 100 | % value (i.e. the character data we read in) before attempting to 101 | % convert it into an int. 102 | % 103 | field_desc(int(do_not_allow_floats, [ensure_gt_zero]), 104 | limited(10), do_not_trim_whitespace), 105 | 106 | % The first argument of this field descriptor says that this field 107 | % represents a date. The format of the date is DD-B-YYYY, where 108 | % DD is the day of the month as an integer, B is the abbreviated 109 | % name of the month (e.g. Jan, Feb etc) and YYYY is the year as 110 | % an integer. The components of the data are expected to be separated 111 | % by the string "-". 112 | % There are no user-supplied actions associated with this field. 113 | % 114 | % The second argument limits the number of characters in the field 115 | % to 11 characters, whilst the third argument says that we *should* 116 | % trim leading- and trailing-whitespace from this field before 117 | % attempting to convert into a date. 118 | % 119 | % NOTE: the CSV reader will return an error if the date is not 120 | % actually a valid date, e.g. February 29 2013. 121 | % 122 | field_desc(date(dd_b_yyyy("-"), []), limited(11), trim_whitespace), 123 | 124 | % XXX TODO - document me. 125 | % 126 | field_desc(string([]), no_limit, do_not_trim_whitespace), 127 | 128 | % XXX TODO - document me. 129 | % 130 | field_desc(float([]), limited(20), do_not_trim_whitespace) 131 | ], 132 | 133 | % Create the CSV reader. 134 | % 135 | csv.init_reader(InputFile, HeaderDesc, RecordDesc, Reader, !IO), 136 | 137 | % Read in the entire CSV file. 138 | % Since CSV readers are instances of the standard library's stream 139 | % type classes we can just use the generic stream.get/4 method to do 140 | % this. 141 | % 142 | stream.get(Reader, MaybeData, !IO), 143 | ( 144 | MaybeData = ok(Data), 145 | Data = csv(_StreamName, Header, Records), 146 | 147 | % Print out the CSV data we have just read. 148 | % 149 | io.write_string("Header: ", !IO), 150 | io.write(Header, !IO), 151 | io.nl(!IO), 152 | io.write_list(Records, "\n", io.write, !IO), 153 | io.nl(!IO) 154 | ; 155 | MaybeData = eof 156 | ; 157 | MaybeData = error(Error), 158 | ErrorMsg = stream.error_message(Error), 159 | io.stderr_stream(Stderr, !IO), 160 | io.write_string(Stderr, ErrorMsg, !IO), 161 | io.nl(Stderr, !IO), 162 | io.set_exit_status(1, !IO) 163 | ). 164 | 165 | %-----------------------------------------------------------------------------% 166 | 167 | :- func ensure_gt_zero(int) = field_action_result(int). 168 | 169 | ensure_gt_zero(N) = Result :- 170 | ( if N > 0 171 | then Result = ok(N) 172 | else Result = error("less than or equal to zero") 173 | ). 174 | 175 | %-----------------------------------------------------------------------------% 176 | :- end_module csv_ex. 177 | %-----------------------------------------------------------------------------% 178 | -------------------------------------------------------------------------------- /samples/data.csv: -------------------------------------------------------------------------------- 1 | Column A,Column B,Column C,Column D 2 | 1, 1-Jan-2013,"Description for column A",5 3 | 2, 2-Jul-2013,"Description for column B",6.0 4 | 3, 3-Aug-2013,,10 5 | 4, 4-Dec-2013,"Description for column D",12.3 6 | -------------------------------------------------------------------------------- /samples/init_from_header_ex.csv: -------------------------------------------------------------------------------- 1 | INT_1," INT_2 ",STRING_3, DATE_FOO ,jsdfkdjsf,DATE_BAR 2 | 1,2,"record 1",2015-04-03,4.52,2015-04-03 3 | 2,4,"record 2",2015-06-03,5.67,2015-03-01 4 | -------------------------------------------------------------------------------- /samples/init_from_header_ex.m: -------------------------------------------------------------------------------- 1 | %-----------------------------------------------------------------------------% 2 | % vim: ft=mercury ts=4 sw=4 et 3 | %-----------------------------------------------------------------------------% 4 | % 5 | % An example of how to initialize a CSV reader from the header fields in the 6 | % CSV data. 7 | % 8 | % CSV data is read from the standard input. 9 | % 10 | % Fields whose header has the prefix "INT_" are treated as integer 11 | % fields. 12 | % Fields whose header has the prefix "FLOAT_" are treated as floats fields. 13 | % Fields whose header has the prefix "STRING_" are treated as strings. 14 | % Fields whose header has the prefix "DATE_" are treated as dates in 15 | % YYYY-MM-DD format. 16 | % Field whose header does not match one of the above are discarded. 17 | % 18 | % After the CSV data is read the records are printed, one per line, and then 19 | % the number of discarded fields is printed. 20 | % 21 | %-----------------------------------------------------------------------------% 22 | 23 | :- module init_from_header_ex. 24 | :- interface. 25 | 26 | :- import_module io. 27 | 28 | :- pred main(io::di, io::uo) is det. 29 | 30 | %-----------------------------------------------------------------------------% 31 | %-----------------------------------------------------------------------------% 32 | 33 | :- implementation. 34 | 35 | :- import_module csv. 36 | 37 | :- import_module int. 38 | :- import_module list. 39 | :- import_module stream. 40 | :- import_module string. 41 | 42 | %-----------------------------------------------------------------------------% 43 | 44 | main(!IO) :- 45 | io.stdin_stream(Stdin, !IO), 46 | FromHeaderParams = init_from_header_params( 47 | no_limit, 48 | no_limit, 49 | trim_whitespace, 50 | (',') 51 | ), 52 | csv.init_reader_from_header_foldl(Stdin, FromHeaderParams, make_field_desc, 53 | InitReaderResult, 0, NumDiscards, !IO), 54 | ( 55 | InitReaderResult = ok(Reader, _Header), 56 | stream.get(Reader, CSVDataResult, !IO), 57 | ( 58 | CSVDataResult = ok(CSVData), 59 | io.write_list(CSVData ^ csv_records, "", io.print_line, !IO) 60 | ; 61 | CSVDataResult = eof 62 | ; 63 | CSVDataResult = error(Error), 64 | ErrorMsg = stream.error_message(Error), 65 | io.stderr_stream(Stderr, !IO), 66 | io.format(Stderr, "error: %s.\n.", [s(ErrorMsg)], !IO), 67 | io.set_exit_status(1, !IO) 68 | ), 69 | ( if NumDiscards = 1 then 70 | io.write_string("1 field was discarded.\n", !IO) 71 | else 72 | io.format("%d fields were discard\n", [i(NumDiscards)], !IO) 73 | ) 74 | ; 75 | InitReaderResult = eof, 76 | io.stderr_stream(Stderr, !IO), 77 | io.write_string(Stderr, "error: EOF at start of stream.\n", !IO), 78 | io.set_exit_status(1, !IO) 79 | ; 80 | InitReaderResult = error(Error), 81 | ErrorMsg = stream.error_message(Error), 82 | io.stderr_stream(Stderr, !IO), 83 | io.format(Stderr, "error: %s.\n", [s(ErrorMsg)], !IO), 84 | io.set_exit_status(1, !IO) 85 | ). 86 | 87 | :- pred make_field_desc(string::in, line_number::in, column_number::in, 88 | field_desc::out, int::in, int::out, io::di, io::uo) is det. 89 | 90 | make_field_desc(FieldName, _LineNumber, _ColumnNumber, FieldDesc, 91 | !NumDiscards, !IO) :- 92 | ( if string.prefix(FieldName, "INT_") then 93 | FieldDesc = int_field_desc 94 | else if string.prefix(FieldName, "FLOAT_") then 95 | FieldDesc = float_field_desc 96 | else if string.prefix(FieldName, "STRING_") then 97 | FieldDesc = string_field_desc 98 | else if string.prefix(FieldName, "DATE_") then 99 | FieldDesc = field_desc(date(yyyy_mm_dd("-"), []), no_limit, 100 | trim_whitespace) 101 | else 102 | % Otherwise it's not a field we recognise so just discard it. 103 | !:NumDiscards = !.NumDiscards + 1, 104 | FieldDesc = discard(no_limit) 105 | ). 106 | 107 | %-----------------------------------------------------------------------------% 108 | :- end_module init_from_header_ex. 109 | %-----------------------------------------------------------------------------% 110 | -------------------------------------------------------------------------------- /src/Makefile: -------------------------------------------------------------------------------- 1 | include ../Make.options 2 | 3 | .PHONY: default 4 | default: 5 | $(MMC) --make libmercury_csv 6 | 7 | .PHONY: rebuild 8 | rebuild: 9 | $(MMC) --rebuild libmercury_csv 10 | 11 | .PHONY: install 12 | install: 13 | $(MMC) --make libmercury_csv.install 14 | 15 | tags: $(wildcard *.m) 16 | mtags $^ 17 | 18 | .PHONY: clean 19 | clean: 20 | $(MMC) --make mercury_csv.clean 21 | 22 | .PHONY: realclean 23 | realclean: 24 | $(MMC) --make mercury_csv.realclean 25 | /bin/rm -rf Mercury 26 | /bin/rm -f $(wildcard *.err) $(wildcard *.mh) 27 | /bin/rm -f tags 28 | -------------------------------------------------------------------------------- /src/Mercury.options: -------------------------------------------------------------------------------- 1 | include ../Make.options 2 | 3 | MCFLAGS = --use-grade-subdirs -O5 --intermodule-optimization 4 | 5 | MCFLAGS-mercury_csv = --no-warn-nothing-exported --no-warn-interface-imports 6 | -------------------------------------------------------------------------------- /src/csv.char_buffer.m: -------------------------------------------------------------------------------- 1 | %-----------------------------------------------------------------------------% 2 | % vim: ft=mercury ts=4 sw=4 et 3 | %-----------------------------------------------------------------------------% 4 | % Copyright (C) 2013-2017, 2022 Julien Fischer. 5 | % See the file COPYING for license details. 6 | %-----------------------------------------------------------------------------% 7 | 8 | :- module csv.char_buffer. 9 | :- interface. 10 | 11 | %-----------------------------------------------------------------------------% 12 | 13 | :- type char_buffer. 14 | 15 | :- pred init(char_buffer::out, S::di, S::uo) is det. 16 | 17 | :- pred add(char_buffer::in, char::in, S::di, S::uo) is det. 18 | 19 | :- func num_chars(char_buffer::in, S::ui) = (int::out) is det. 20 | 21 | :- func to_string(char_buffer::in, S::ui) = (string::out) is det. 22 | 23 | :- pred chomp_cr(char_buffer::in, S::di, S::uo) is det. 24 | 25 | %-----------------------------------------------------------------------------% 26 | %-----------------------------------------------------------------------------% 27 | 28 | :- implementation. 29 | 30 | :- import_module mutvar. 31 | 32 | %-----------------------------------------------------------------------------% 33 | 34 | :- type char_buffer == mutvar(char_buffer_rep). 35 | 36 | % XXX this could be much more efficient if we implemented it directly 37 | % as code in the target language. 38 | % NOTE: if we do so, we should use the maximum field width, if available 39 | % to initialise the buffer. 40 | % 41 | :- type char_buffer_rep 42 | ---> char_buffer_rep(list(char), int). 43 | 44 | init(Buffer, !State) :- 45 | promise_pure ( 46 | BufferRep = char_buffer_rep([], 0), 47 | impure new_mutvar(BufferRep, Buffer), 48 | !:State = !.State 49 | ). 50 | 51 | add(Buffer, Char, !State) :- 52 | promise_pure ( 53 | impure get_mutvar(Buffer, BufferRep0), 54 | BufferRep0 = char_buffer_rep(Chars, NumChars), 55 | BufferRep = char_buffer_rep([Char | Chars], NumChars + 1), 56 | impure set_mutvar(Buffer, BufferRep), 57 | !:State = !.State 58 | ). 59 | 60 | num_chars(Buffer, _State) = NumChars :- 61 | promise_pure ( 62 | impure get_mutvar(Buffer, BufferRep), 63 | BufferRep = char_buffer_rep(_, NumChars) 64 | ). 65 | 66 | to_string(Buffer, _State) = String :- 67 | promise_pure ( 68 | impure get_mutvar(Buffer, BufferRep), 69 | BufferRep = char_buffer_rep(RevChars, _), 70 | String = string.from_rev_char_list(RevChars) 71 | ). 72 | 73 | chomp_cr(Buffer, !State) :- 74 | promise_pure ( 75 | impure get_mutvar(Buffer, BufferRep0), 76 | BufferRep0 = char_buffer_rep(Chars, NumChars), 77 | ( 78 | Chars = [] 79 | ; 80 | Chars = [LastChar | RestChars], 81 | ( if LastChar = ('\r') then 82 | BufferRep = char_buffer_rep(RestChars, NumChars - 1), 83 | impure set_mutvar(Buffer, BufferRep) 84 | else 85 | true 86 | ) 87 | ), 88 | !:State = !.State 89 | ). 90 | 91 | %-----------------------------------------------------------------------------% 92 | :- end_module char_buffer. 93 | %-----------------------------------------------------------------------------% 94 | -------------------------------------------------------------------------------- /src/csv.raw_reader.m: -------------------------------------------------------------------------------- 1 | %-----------------------------------------------------------------------------% 2 | % vim: ft=mercury ts=4 sw=4 et 3 | %-----------------------------------------------------------------------------% 4 | % Copyright (C) 2013-2015, 2019-2020, 2022 Julien Fischer. 5 | % See the file COPYING for license details. 6 | %-----------------------------------------------------------------------------% 7 | 8 | :- module csv.raw_reader. 9 | :- interface. 10 | 11 | :- import_module bool. 12 | 13 | %-----------------------------------------------------------------------------% 14 | 15 | :- pred get_raw_record(csv.raw_reader(Stream)::in, 16 | csv.result(raw_record, Error)::out, State::di, State::uo) is det 17 | <= ( 18 | stream.line_oriented(Stream, State), 19 | stream.unboxed_reader(Stream, char, State, Error) 20 | ). 21 | 22 | :- pred get_raw_csv(csv.raw_reader(Stream)::in, 23 | csv.result(raw_csv, Error)::out, State::di, State::uo) is det 24 | <= ( 25 | stream.line_oriented(Stream, State), 26 | stream.unboxed_reader(Stream, char, State, Error) 27 | ). 28 | 29 | %-----------------------------------------------------------------------------% 30 | 31 | :- pred fold(csv.raw_reader(Stream), pred(raw_record, T, T), 32 | T, csv.maybe_partial_res(T, Error), State, State) 33 | <= ( 34 | stream.line_oriented(Stream, State), 35 | stream.unboxed_reader(Stream, char, State, Error) 36 | ). 37 | :- mode fold(in, in(pred(in, in, out) is det), 38 | in, out, di, uo) is det. 39 | :- mode fold(in, in(pred(in, in, out) is cc_multi), 40 | in, out, di, uo) is cc_multi. 41 | 42 | :- pred fold_state(csv.raw_reader(Stream), 43 | pred(raw_record, State, State), csv.res(Error), State, State) 44 | <= ( 45 | stream.line_oriented(Stream, State), 46 | stream.unboxed_reader(Stream, char, State, Error) 47 | ). 48 | :- mode fold_state(in, in(pred(in, di, uo) is det), 49 | out, di, uo) is det. 50 | :- mode fold_state(in, in(pred(in, di, uo) is cc_multi), 51 | out, di, uo) is cc_multi. 52 | 53 | :- pred fold2_state(csv.raw_reader(Stream), 54 | pred(raw_record, T, T, State, State), T, csv.maybe_partial_res(T, Error), 55 | State, State) <= ( 56 | stream.line_oriented(Stream, State), 57 | stream.unboxed_reader(Stream, char, State, Error) 58 | ). 59 | :- mode fold2_state(in, in(pred(in, in, out, di, uo) is det), 60 | in, out, di, uo) is det. 61 | :- mode fold2_state(in, in(pred(in, in, out, di, uo) is cc_multi), 62 | in, out, di, uo) is cc_multi. 63 | 64 | :- pred fold_maybe_stop(csv.raw_reader(Stream), pred(raw_record, bool, T, T), 65 | T, csv.maybe_partial_res(T, Error), State, State) 66 | <= ( 67 | stream.line_oriented(Stream, State), 68 | stream.unboxed_reader(Stream, char, State, Error) 69 | ). 70 | :- mode fold_maybe_stop(in, in(pred(in, out, in, out) is det), 71 | in, out, di, uo) is det. 72 | :- mode fold_maybe_stop(in, in(pred(in, out, in, out) is cc_multi), 73 | in, out, di, uo) is cc_multi. 74 | 75 | :- pred fold_state_maybe_stop(csv.raw_reader(Stream), 76 | pred(raw_record, bool, State, State), csv.res(Error), State, State) 77 | <= ( 78 | stream.line_oriented(Stream, State), 79 | stream.unboxed_reader(Stream, char, State, Error) 80 | ). 81 | :- mode fold_state_maybe_stop(in, in(pred(in, out, di, uo) is det), 82 | out, di, uo) is det. 83 | :- mode fold_state_maybe_stop(in, in(pred(in, out, di, uo) is cc_multi), 84 | out, di, uo) is cc_multi. 85 | 86 | :- pred fold2_state_maybe_stop(csv.raw_reader(Stream), 87 | pred(raw_record, bool, T, T, State, State), T, 88 | csv.maybe_partial_res(T, Error), State, State) 89 | <= ( 90 | stream.line_oriented(Stream, State), 91 | stream.unboxed_reader(Stream, char, State, Error) 92 | ). 93 | :- mode fold2_state_maybe_stop(in, 94 | in(pred(in, out, in, out, di, uo) is det), in, out, 95 | di, uo) is det. 96 | :- mode fold2_state_maybe_stop(in, 97 | in(pred(in, out, in, out, di, uo) is cc_multi), in, out, 98 | di, uo) is cc_multi. 99 | 100 | %-----------------------------------------------------------------------------% 101 | %----------------------------------------------------------------------------% 102 | 103 | :- implementation. 104 | 105 | :- import_module csv.record_parser. 106 | 107 | %----------------------------------------------------------------------------% 108 | 109 | get_raw_record(Reader, Result, !State) :- 110 | Client = client_raw_reader(Reader), 111 | get_next_record(Client, RecordResult, !State), 112 | ( 113 | RecordResult = ok(RawRecord), 114 | Result = ok(RawRecord) 115 | ; 116 | RecordResult = eof, 117 | Result = eof 118 | ; 119 | RecordResult = error(Error), 120 | Result = error(Error) 121 | ). 122 | 123 | %----------------------------------------------------------------------------% 124 | 125 | get_raw_csv(Reader, Result, !State) :- 126 | raw_reader.fold(Reader, list.cons, [], FoldResult, !State), 127 | ( 128 | FoldResult = ok(RevRawRecords), 129 | list.reverse(RevRawRecords, RawRecords), 130 | Result = ok(raw_csv(RawRecords)) 131 | ; 132 | FoldResult = error(_, Error), 133 | Result = error(Error) 134 | ). 135 | 136 | %----------------------------------------------------------------------------% 137 | 138 | fold(Reader, Pred, !.Acc, Result, !State) :- 139 | Client = client_raw_reader(Reader), 140 | get_next_record(Client, RecordResult, !State), 141 | ( 142 | RecordResult = ok(RawRecord), 143 | Pred(RawRecord, !Acc), 144 | fold(Reader, Pred, !.Acc, Result, !State) 145 | ; 146 | RecordResult = eof, 147 | Result = ok(!.Acc) 148 | ; 149 | RecordResult = error(Error), 150 | Result = error(!.Acc, Error) 151 | ). 152 | 153 | fold_state(Reader, Pred, Result, !State) :- 154 | Client = client_raw_reader(Reader), 155 | get_next_record(Client, RecordResult, !State), 156 | ( 157 | RecordResult = ok(RawRecord), 158 | Pred(RawRecord, !State), 159 | fold_state(Reader, Pred, Result, !State) 160 | ; 161 | RecordResult = eof, 162 | Result = ok 163 | ; 164 | RecordResult = error(Error), 165 | Result = error(Error) 166 | ). 167 | 168 | fold2_state(Reader, Pred, !.Acc, Result, !State) :- 169 | Client = client_raw_reader(Reader), 170 | get_next_record(Client, RecordResult, !State), 171 | ( 172 | RecordResult = ok(RawRecord), 173 | Pred(RawRecord, !Acc, !State), 174 | fold2_state(Reader, Pred, !.Acc, Result, !State) 175 | ; 176 | RecordResult = eof, 177 | Result = ok(!.Acc) 178 | ; 179 | RecordResult = error(Error), 180 | Result = error(!.Acc, Error) 181 | ). 182 | 183 | fold_maybe_stop(Reader, Pred, !.Acc, Result, !State) :- 184 | Client = client_raw_reader(Reader), 185 | get_next_record(Client, RecordResult, !State), 186 | ( 187 | RecordResult = ok(RawRecord), 188 | Pred(RawRecord, Continue, !Acc), 189 | ( 190 | Continue = yes, 191 | fold_maybe_stop(Reader, Pred, !.Acc, Result, !State) 192 | ; 193 | Continue = no, 194 | Result = ok(!.Acc) 195 | ) 196 | ; 197 | RecordResult = eof, 198 | Result = ok(!.Acc) 199 | ; 200 | RecordResult = error(Error), 201 | Result = error(!.Acc, Error) 202 | ). 203 | 204 | fold_state_maybe_stop(Reader, Pred, Result, !State) :- 205 | Client = client_raw_reader(Reader), 206 | get_next_record(Client, RecordResult, !State), 207 | ( 208 | RecordResult = ok(RawRecord), 209 | Pred(RawRecord, Continue, !State), 210 | ( 211 | Continue = yes, 212 | fold_state_maybe_stop(Reader, Pred, Result, !State) 213 | ; 214 | Continue = no, 215 | Result = ok 216 | ) 217 | ; 218 | RecordResult = eof, 219 | Result = ok 220 | ; 221 | RecordResult = error(Error), 222 | Result = error(Error) 223 | ). 224 | 225 | fold2_state_maybe_stop(Reader, Pred, !.Acc, Result, !State) :- 226 | Client = client_raw_reader(Reader), 227 | get_next_record(Client, RecordResult, !State), 228 | ( 229 | RecordResult = ok(RawRecord), 230 | Pred(RawRecord, Continue, !Acc, !State), 231 | ( 232 | Continue = yes, 233 | fold2_state_maybe_stop(Reader, Pred, !.Acc, Result, !State) 234 | ; 235 | Continue = no, 236 | Result = ok(!.Acc) 237 | ) 238 | ; 239 | RecordResult = eof, 240 | Result = ok(!.Acc) 241 | ; 242 | RecordResult = error(Error), 243 | Result = error(!.Acc, Error) 244 | ). 245 | 246 | %----------------------------------------------------------------------------% 247 | :- end_module csv.raw_reader. 248 | %----------------------------------------------------------------------------% 249 | -------------------------------------------------------------------------------- /src/csv.record_parser.m: -------------------------------------------------------------------------------- 1 | %-----------------------------------------------------------------------------% 2 | % vim: ft=mercury ts=4 sw=4 et 3 | %-----------------------------------------------------------------------------% 4 | % Copyright (C) 2013-2020, 2022, 2024 Julien Fischer. 5 | % See the file COPYING for license details. 6 | %-----------------------------------------------------------------------------% 7 | 8 | :- module csv.record_parser. 9 | :- interface. 10 | 11 | :- type client(Stream) 12 | ---> client_reader(csv.reader(Stream)) 13 | ; client_raw_reader(csv.raw_reader(Stream)). 14 | 15 | :- type record_result(Error) == stream.result(raw_record, csv.error(Error)). 16 | 17 | :- pred get_next_record(client(Stream)::in, record_result(Error)::out, 18 | State::di, State::uo) is det 19 | <= ( 20 | stream.line_oriented(Stream, State), 21 | stream.unboxed_reader(Stream, character, State, Error) 22 | ). 23 | 24 | %-----------------------------------------------------------------------------% 25 | %-----------------------------------------------------------------------------% 26 | 27 | :- implementation. 28 | 29 | :- import_module csv.char_buffer. 30 | 31 | %-----------------------------------------------------------------------------% 32 | % 33 | % Access information about the parser client. 34 | % 35 | 36 | :- func get_client_stream(client(Stream)) = Stream. 37 | 38 | get_client_stream(client_reader(R)) = R ^ csv_stream. 39 | get_client_stream(client_raw_reader(R)) = R ^ csv_raw_stream. 40 | 41 | :- func get_client_field_limit(client(Stream)) = record_field_limit. 42 | 43 | get_client_field_limit(client_reader(R)) = R ^ csv_field_limit. 44 | get_client_field_limit(client_raw_reader(R)) = R ^ csv_raw_field_limit. 45 | 46 | :- func get_client_field_width(client(Stream)) = field_width_limit. 47 | 48 | get_client_field_width(client_reader(R)) = R ^ csv_width_limit. 49 | get_client_field_width(client_raw_reader(R)) = R ^ csv_raw_width_limit. 50 | 51 | :- func get_client_field_delimiter(client(Stream)) = char. 52 | 53 | get_client_field_delimiter(client_reader(R)) = R ^ csv_field_delimiter. 54 | get_client_field_delimiter(client_raw_reader(R)) = R ^ csv_raw_delimiter. 55 | 56 | :- func get_client_comments(client(Stream)) = comments. 57 | 58 | get_client_comments(client_reader(R)) = R ^ csv_comments. 59 | get_client_comments(client_raw_reader(_)) = no_comments. 60 | 61 | :- func get_client_quotation_mark_in_unquoted_field(client(Stream)) = 62 | quotation_mark_in_unquoted_field. 63 | 64 | get_client_quotation_mark_in_unquoted_field(client_reader(R)) = 65 | R ^ csv_quotation_mark_in_unquoted_field. 66 | get_client_quotation_mark_in_unquoted_field(client_raw_reader(R)) = 67 | R ^ csv_raw_quotation_mark_in_unquoted_field. 68 | 69 | %-----------------------------------------------------------------------------% 70 | % 71 | % Reading raw records. 72 | % 73 | 74 | % Was the last character we saw when scanning the previous field an 75 | % unquoted delimiter? 76 | % We need to keep track of this information in order to handle unquoted 77 | % empty trailing fields properly. 78 | % 79 | :- type last_seen 80 | ---> last_seen_start 81 | % We have not started scanning yet. 82 | 83 | ; last_seen_delimiter 84 | % The last character we saw was an unquoted delimiter. 85 | 86 | ; last_seen_other. 87 | % The last character we saw was something else. 88 | 89 | :- type next_char 90 | ---> next_char_none 91 | ; next_char_nl. 92 | 93 | get_next_record(Reader, Result, !State) :- 94 | get_fields(Reader, next_char_none, [], FieldsResult, last_seen_start, _, 95 | 0, _NumFields, 0, _ColNo, !State), 96 | ( 97 | FieldsResult = fsr_fields(RawRecord), 98 | Result = ok(RawRecord) 99 | ; 100 | FieldsResult = fsr_comment_line, 101 | get_next_record(Reader, Result, !State) 102 | ; 103 | FieldsResult = fsr_eof, 104 | Result = eof 105 | ; 106 | FieldsResult = fsr_error(Error), 107 | Result = error(Error) 108 | ). 109 | 110 | :- type fields_result(Error) 111 | ---> fsr_fields(raw_record) 112 | ; fsr_comment_line 113 | ; fsr_eof 114 | ; fsr_error(csv.error(Error)). 115 | 116 | :- pred get_fields(client(Stream)::in, next_char::in, raw_fields::in, 117 | fields_result(Error)::out, last_seen::in, last_seen::out, 118 | int::in, int::out, column_number::in, column_number::out, 119 | State::di, State::uo) is det 120 | <= ( 121 | stream.line_oriented(Stream, State), 122 | stream.unboxed_reader(Stream, character, State, Error) 123 | ). 124 | 125 | get_fields(Reader, NextChar, !.Fields, Result, !LastSeen, !FieldsRead, 126 | !ColNo, !State) :- 127 | NextFieldNo = !.FieldsRead + 1, 128 | stream.get_line(Stream, StartLineNo0, !State), 129 | ( 130 | NextChar = next_char_none, 131 | StartLineNo = StartLineNo0 132 | ; 133 | NextChar = next_char_nl, 134 | StartLineNo = StartLineNo0 - 1 135 | ), 136 | next_raw_field(Reader, NextChar, StartLineNo, NextFieldNo, FieldResult, 137 | !LastSeen, !ColNo, !State), 138 | Stream = get_client_stream(Reader), 139 | % NOTE: LineNo is not necessarily the same as StartLineNo since quoted 140 | % fields can contain newlines. 141 | stream.get_line(Stream, LineNo, !State), 142 | ( 143 | ( 144 | FieldResult = fr_field(Field), 145 | NextCharPrime = next_char_none, 146 | LineNoOffset = 0 147 | ; 148 | FieldResult = fr_field_and_nl(Field), 149 | NextCharPrime = next_char_nl, 150 | LineNoOffset = 1 151 | 152 | ), 153 | !:FieldsRead = !.FieldsRead + 1, 154 | MaybeFieldLimit = get_client_field_limit(Reader), 155 | ( if 156 | MaybeFieldLimit = exactly(FieldLimit), 157 | !.FieldsRead > FieldLimit 158 | then 159 | stream.name(Stream, Name, !State), 160 | StartCol = Field ^ raw_field_col_no, 161 | Error = csv_error( 162 | Name, 163 | LineNo - LineNoOffset, 164 | StartCol, 165 | !.FieldsRead, 166 | "record field limit exceeded" 167 | ), 168 | Result = fsr_error(Error) 169 | else 170 | !:Fields = [Field | !.Fields], 171 | get_fields(Reader, NextCharPrime, !.Fields, Result, !LastSeen, 172 | !FieldsRead, !ColNo, !State) 173 | ) 174 | ; 175 | FieldResult = fr_comment_line, 176 | Result = fsr_comment_line 177 | ; 178 | FieldResult = fr_error(Error), 179 | Result = fsr_error(Error) 180 | ; 181 | FieldResult = fr_end_of_record, 182 | list.reverse(!Fields), 183 | Result = fsr_fields(raw_record(StartLineNo, !.Fields)) 184 | ; 185 | FieldResult = fr_eof, 186 | ( 187 | !.Fields = [], 188 | Result = fsr_eof 189 | ; 190 | % The EOF was the terminating this record. 191 | !.Fields = [_ | _], 192 | list.reverse(!Fields), 193 | Result = fsr_fields(raw_record(StartLineNo, !.Fields)) 194 | ) 195 | ). 196 | 197 | %-----------------------------------------------------------------------------% 198 | % 199 | % Reading raw fields. 200 | % 201 | 202 | :- type field_result(Error) 203 | ---> fr_field(raw_field) 204 | ; fr_field_and_nl(raw_field) 205 | ; fr_error(csv.error(Error)) 206 | ; fr_comment_line 207 | ; fr_end_of_record 208 | ; fr_eof. 209 | 210 | :- pred next_raw_field(client(Stream)::in, next_char::in, line_number::in, 211 | field_number::in, field_result(Error)::out, last_seen::in, last_seen::out, 212 | column_number::in, column_number::out, State::di, State::uo) is det 213 | <= ( 214 | stream.line_oriented(Stream, State), 215 | stream.unboxed_reader(Stream, character, State, Error) 216 | ). 217 | 218 | next_raw_field(Reader, NextChar, StartLineNo, FieldNo, FieldResult, !LastSeen, 219 | !ColNo, !State) :- 220 | Stream = get_client_stream(Reader), 221 | ( 222 | NextChar = next_char_none, 223 | stream.unboxed_get(Stream, GetResult, Char, !State) 224 | ; 225 | NextChar = next_char_nl, 226 | Char = '\n', 227 | GetResult = ok 228 | ), 229 | ( 230 | GetResult = ok, 231 | increment_col_no(!ColNo), 232 | ( if 233 | Char = ('\n') 234 | then 235 | ( if !.LastSeen = last_seen_delimiter then 236 | !:LastSeen = last_seen_other, 237 | Field = raw_field("", StartLineNo, !.ColNo), 238 | FieldResult = fr_field_and_nl(Field) 239 | else 240 | FieldResult = fr_end_of_record 241 | ) 242 | else if 243 | % We allow empty fields here. 244 | Char = get_client_field_delimiter(Reader) 245 | then 246 | !:LastSeen = last_seen_delimiter, 247 | Field = raw_field("", StartLineNo, !.ColNo), 248 | FieldResult = fr_field(Field) 249 | else if 250 | Comments = get_client_comments(Reader), 251 | Comments = allow_comments(CommentChar), 252 | Char = CommentChar 253 | then 254 | consume_until_next_nl_or_eof(Reader, CommentResult, !State), 255 | ( 256 | CommentResult = ok, 257 | ( if !.LastSeen = last_seen_start then 258 | FieldResult = fr_comment_line 259 | else 260 | FieldResult = fr_end_of_record 261 | ) 262 | ; 263 | CommentResult = error(Error), 264 | FieldResult = fr_error(Error) 265 | ) 266 | else if 267 | Char = ('"') 268 | then 269 | !:LastSeen = last_seen_other, 270 | char_buffer.init(Buffer, !State), 271 | next_quoted_field(Reader, StartLineNo, !.ColNo, FieldNo, 272 | Buffer, FieldResult, !LastSeen, !ColNo, !State) 273 | else 274 | char_buffer.init(Buffer, !State), 275 | char_buffer.add(Buffer, Char, !State), 276 | next_unquoted_field(Reader, StartLineNo, !.ColNo, FieldNo, 277 | Buffer, FieldResult, !LastSeen, !ColNo, !State) 278 | ) 279 | ; 280 | GetResult = eof, 281 | FieldResult = fr_eof 282 | ; 283 | GetResult = error(Error), 284 | FieldResult = fr_error(stream_error(Error)) 285 | ). 286 | 287 | :- pred next_quoted_field(client(Stream)::in, line_number::in, 288 | column_number::in, field_number::in, char_buffer::in, 289 | field_result(Error)::out, last_seen::in, last_seen::out, 290 | column_number::in, column_number::out, State::di, State::uo) is det 291 | <= ( 292 | stream.line_oriented(Stream, State), 293 | stream.unboxed_reader(Stream, character, State, Error) 294 | ). 295 | 296 | next_quoted_field(Reader, StartLineNo, StartColNo, FieldNo, Buffer, 297 | Result, !LastSeen, !ColNo, !State) :- 298 | Stream = get_client_stream(Reader), 299 | stream.unboxed_get(Stream, GetResult, Char, !State), 300 | ( 301 | GetResult = ok, 302 | increment_col_no(!ColNo), 303 | ( if Char = ('"') then 304 | stream.unboxed_get(Stream, NextGetResult, NextChar, !State), 305 | ( 306 | NextGetResult = ok, 307 | increment_col_no(!ColNo), 308 | % Double quotes are used to escape a quote. 309 | ( if 310 | NextChar = ('"') 311 | then 312 | add(Buffer, Char, !State), 313 | !:LastSeen = last_seen_other, 314 | next_quoted_field(Reader, StartLineNo, StartColNo, FieldNo, 315 | Buffer, Result, !LastSeen, !ColNo, !State) 316 | else if 317 | NextChar = get_client_field_delimiter(Reader) 318 | then 319 | !:LastSeen = last_seen_delimiter, 320 | FieldValue = char_buffer.to_string(Buffer, !.State), 321 | Field = raw_field(FieldValue, StartLineNo, StartColNo), 322 | Result = fr_field(Field) 323 | else if 324 | NextChar = ('\n') 325 | then 326 | !:LastSeen = last_seen_other, 327 | FieldValue = char_buffer.to_string(Buffer, !.State), 328 | Field = raw_field(FieldValue, StartLineNo, StartColNo), 329 | Result = fr_field_and_nl(Field) 330 | else if 331 | NextChar = ('\r') 332 | then 333 | stream.unboxed_get(Stream, AfterCR_Result, AfterCR_Char, 334 | !State), 335 | ( 336 | AfterCR_Result = ok, 337 | ( if AfterCR_Char = ('\n') then 338 | FieldValue = char_buffer.to_string(Buffer, 339 | !.State), 340 | Field = raw_field(FieldValue, StartLineNo, 341 | StartColNo), 342 | Result = fr_field_and_nl(Field) 343 | else 344 | increment_col_no(!ColNo), 345 | stream.name(Stream, Name, !State), 346 | Error = csv_error( 347 | Name, 348 | StartLineNo, 349 | StartColNo, 350 | FieldNo, 351 | "missing closing quote" 352 | ), 353 | Result = fr_error(Error) 354 | ) 355 | ; 356 | AfterCR_Result = eof, 357 | stream.name(Stream, Name, !State), 358 | stream.get_line(Stream, LineNo, !State), 359 | Error = csv_error(Name, LineNo, !.ColNo, FieldNo, 360 | "unexpected end-of-file"), 361 | Result = fr_error(Error) 362 | ; 363 | AfterCR_Result = error(Error), 364 | Result = fr_error(stream_error(Error)) 365 | ) 366 | else 367 | stream.name(Stream, Name, !State), 368 | Error = csv_error( 369 | Name, 370 | StartLineNo, 371 | StartColNo, 372 | FieldNo, 373 | "missing closing quote" 374 | ), 375 | Result = fr_error(Error) 376 | ) 377 | ; 378 | % This might be the last field in the file, so don't expect a 379 | % newline. 380 | NextGetResult = eof, 381 | FieldValue = char_buffer.to_string(Buffer, !.State), 382 | Field = raw_field(FieldValue, StartLineNo, StartColNo), 383 | Result = fr_field(Field) 384 | ; 385 | NextGetResult = error(Error), 386 | Result = fr_error(stream_error(Error)) 387 | ) 388 | else 389 | % NOTE: quoted delimiter characters do not count has having 390 | % seen a delimiter. 391 | !:LastSeen = last_seen_other, 392 | add(Buffer, Char, !State), 393 | % Reset the column number if we see a newline. 394 | ( if Char = ('\n') then !:ColNo = 0 else true ), 395 | MaybeFieldWidthLimit = get_client_field_width(Reader), 396 | ( 397 | MaybeFieldWidthLimit = no_limit, 398 | next_quoted_field(Reader, StartLineNo, StartColNo, FieldNo, 399 | Buffer, Result, !LastSeen, !ColNo, !State) 400 | ; 401 | MaybeFieldWidthLimit = limited(Limit), 402 | NumChars = char_buffer.num_chars(Buffer, !.State), 403 | ( if NumChars > Limit then 404 | stream.name(Stream, Name, !State), 405 | Error = csv_error(Name, StartLineNo, StartColNo, FieldNo, 406 | "field width limit exceeded"), 407 | Result = fr_error(Error) 408 | else 409 | next_quoted_field(Reader, StartLineNo, StartColNo, FieldNo, 410 | Buffer, Result, !LastSeen, !ColNo, !State) 411 | ) 412 | ) 413 | ) 414 | ; 415 | GetResult = eof, 416 | stream.name(Stream, Name, !State), 417 | Error = csv_error(Name, StartLineNo, StartColNo, FieldNo, 418 | "missing closing quote"), 419 | Result = fr_error(Error) 420 | ; 421 | GetResult = error(Error), 422 | Result = fr_error(stream_error(Error)) 423 | ). 424 | 425 | :- pred next_unquoted_field(client(Stream)::in, line_number::in, 426 | column_number::in, field_number::in, char_buffer::in, 427 | field_result(Error)::out, last_seen::in, last_seen::out, 428 | column_number::in, column_number::out, State::di, State::uo) is det 429 | <= ( 430 | stream.line_oriented(Stream, State), 431 | stream.unboxed_reader(Stream, character, State, Error) 432 | ). 433 | 434 | next_unquoted_field(Reader, StartLineNo, StartColNo, FieldNo, Buffer, 435 | Result, !LastSeen, !ColNo, !State) :- 436 | Stream = get_client_stream(Reader), 437 | stream.unboxed_get(Stream, GetResult, Char, !State), 438 | ( 439 | GetResult = ok, 440 | increment_col_no(!ColNo), 441 | ( if 442 | Char = get_client_field_delimiter(Reader) 443 | then 444 | !:LastSeen = last_seen_delimiter, 445 | FieldValue = char_buffer.to_string(Buffer, !.State), 446 | Field = raw_field(FieldValue, StartLineNo, StartColNo), 447 | Result = fr_field(Field) 448 | else if 449 | Char = ('"'), 450 | QuotationMarkInUnquotedField = 451 | get_client_quotation_mark_in_unquoted_field(Reader), 452 | QuotationMarkInUnquotedField = no_quotation_mark_in_unquoted_field 453 | then 454 | stream.name(Stream, Name, !State), 455 | stream.get_line(Stream, LineNo, !State), 456 | Result = fr_error(csv_error(Name, LineNo, !.ColNo, FieldNo, 457 | "unexpected quote")) 458 | else if 459 | Char = ('\n') 460 | then 461 | !:LastSeen = last_seen_other, 462 | chomp_cr(Buffer, !State), 463 | FieldValue = char_buffer.to_string(Buffer, !.State), 464 | Field = raw_field(FieldValue, StartLineNo, StartColNo), 465 | Result = fr_field_and_nl(Field) 466 | else 467 | !:LastSeen = last_seen_other, 468 | add(Buffer, Char, !State), 469 | MaybeFieldWidthLimit = get_client_field_width(Reader), 470 | ( 471 | MaybeFieldWidthLimit = no_limit, 472 | next_unquoted_field(Reader, StartLineNo, StartColNo, FieldNo, 473 | Buffer, Result, !LastSeen, !ColNo, !State) 474 | ; 475 | MaybeFieldWidthLimit = limited(Limit), 476 | NumChars = char_buffer.num_chars(Buffer, !.State), 477 | ( if NumChars > Limit then 478 | stream.name(Stream, Name, !State), 479 | stream.get_line(Stream, LineNo, !State), 480 | Error = csv_error(Name, LineNo, StartColNo, FieldNo, 481 | "field width exceeded"), 482 | Result = fr_error(Error) 483 | else 484 | next_unquoted_field(Reader, StartLineNo, StartColNo, 485 | FieldNo, Buffer, Result, !LastSeen, !ColNo, !State) 486 | ) 487 | ) 488 | ) 489 | ; 490 | % This might be the end of the file 491 | GetResult = eof, 492 | FieldValue = char_buffer.to_string(Buffer, !.State), 493 | Field = raw_field(FieldValue, StartLineNo, StartColNo), 494 | Result = fr_field(Field) 495 | ; 496 | GetResult = error(Error), 497 | Result = fr_error(stream_error(Error)) 498 | ). 499 | 500 | 501 | %-----------------------------------------------------------------------------% 502 | 503 | :- pred consume_until_next_nl_or_eof(client(Stream)::in, 504 | csv.res(Error)::out, State::di, State::uo) is det 505 | <= ( 506 | stream.line_oriented(Stream, State), 507 | stream.unboxed_reader(Stream, character, State, Error) 508 | ). 509 | 510 | consume_until_next_nl_or_eof(Reader, Result, !State) :- 511 | Stream = get_client_stream(Reader), 512 | stream.unboxed_get(Stream, ReadResult, Char, !State), 513 | ( 514 | ReadResult = ok, 515 | ( if Char = ('\n') then 516 | Result = ok 517 | else 518 | consume_until_next_nl_or_eof(Reader, Result, !State) 519 | ) 520 | ; 521 | ReadResult = eof, 522 | Result = ok 523 | ; 524 | ReadResult = error(Error), 525 | Result = error(stream_error(Error)) 526 | ). 527 | 528 | %-----------------------------------------------------------------------------% 529 | 530 | :- pred increment_col_no(column_number::in, column_number::out) is det. 531 | 532 | increment_col_no(I, I + 1). 533 | 534 | %-----------------------------------------------------------------------------% 535 | :- end_module csv.record_parser. 536 | %-----------------------------------------------------------------------------% 537 | -------------------------------------------------------------------------------- /src/mercury_csv.m: -------------------------------------------------------------------------------- 1 | %-----------------------------------------------------------------------------% 2 | % vim: ft=mercury ts=4 sw=4 et 3 | %-----------------------------------------------------------------------------% 4 | % Copyright (C) 2013-2014 Julien Fischer. 5 | % See the file COPYING for license details. 6 | %-----------------------------------------------------------------------------% 7 | 8 | :- module mercury_csv. 9 | :- interface. 10 | 11 | :- import_module csv. 12 | 13 | %-----------------------------------------------------------------------------% 14 | :- end_module mercury_csv. 15 | %-----------------------------------------------------------------------------% 16 | -------------------------------------------------------------------------------- /tests/Makefile: -------------------------------------------------------------------------------- 1 | MMC = mmc 2 | 3 | ifdef GRADE 4 | GRADEFLAGS=--grade $(GRADE) 5 | else 6 | GRADEFLAGS= 7 | endif 8 | 9 | .PHONY: runtests 10 | runtests: test_csv 11 | @./test_csv 12 | 13 | .PHONY: runtests-verbose 14 | runtests-verbose: test_csv 15 | @./test_csv -v 16 | 17 | Mercury.modules: $(wildcard *.m) $(wildcard ../src/*.m) 18 | $(MMC) -f $(wildcard *.m) $(wildcard ../src/*.m) 19 | 20 | test_csv: Mercury.modules 21 | $(MMC) $(GRADEFLAGS) --make $@ 22 | 23 | tags: $(wildcard *.m) $(wildcard ../src/*.m) 24 | mtags $(wildcard *.m) $(wildcard ../src/*.m) 25 | 26 | # We're only interested in coverage information from the CSV library modules. 27 | # 28 | COVERAGE_RESTRICTION = \ 29 | -m csv \ 30 | -m csv.char_buffer \ 31 | -m csv.raw_reader \ 32 | -m csv.record_parser \ 33 | -m csv.typed_reader 34 | 35 | .PHONY: coverage 36 | coverage: test_csv 37 | mtc --coverage-test -o test_csv.trace_counts ./test_csv 38 | mcov $(COVERAGE_RESTRICTION) test_csv.trace_counts -o test_csv.coverage-procs 39 | mcov $(COVERAGE_RESTRICTION) test_csv.trace_counts -d -o test_csv.coverage-labels 40 | 41 | .PHONY: realclean 42 | realclean: 43 | $(MMC) --make test_csv.realclean 44 | /bin/rm -rf Mercury 45 | /bin/rm -f Mercury.modules 46 | /bin/rm -f FAILED_TESTS ABORTED_TESTS 47 | /bin/rm -f test_csv.trace_counts test_csv.coverage-procs test_csv.coverage-labels 48 | -------------------------------------------------------------------------------- /tests/blank_lines.exp: -------------------------------------------------------------------------------- 1 | "Header1","Header2","Header3" 2 | "abc","def","ghi" 3 | -------------------------------------------------------------------------------- /tests/blank_lines.inp: -------------------------------------------------------------------------------- 1 | 2 | 3 | Header1,Header2,Header3 4 | 5 | 6 | abc,def,ghi 7 | 8 | 9 | -------------------------------------------------------------------------------- /tests/bool1.exp: -------------------------------------------------------------------------------- 1 | "no","yes" 2 | "yes","yes" 3 | "no","no" 4 | "yes","no" 5 | -------------------------------------------------------------------------------- /tests/bool1.inp: -------------------------------------------------------------------------------- 1 | F,F 2 | T,F 3 | F,T 4 | T,T 5 | -------------------------------------------------------------------------------- /tests/bool2.exp: -------------------------------------------------------------------------------- 1 | "no","yes" 2 | "yes","yes" 3 | "no","no" 4 | "yes","no" 5 | -------------------------------------------------------------------------------- /tests/bool2.inp: -------------------------------------------------------------------------------- 1 | F , F 2 | T , F 3 | F, T 4 | T, T 5 | -------------------------------------------------------------------------------- /tests/bool3.exp: -------------------------------------------------------------------------------- 1 | "no","yes" 2 | "yes","yes" 3 | "no","no" 4 | "yes","no" 5 | -------------------------------------------------------------------------------- /tests/bool3.inp: -------------------------------------------------------------------------------- 1 | "F","F" 2 | "T","F" 3 | "F", T 4 | "T ", T 5 | -------------------------------------------------------------------------------- /tests/bool4.exp: -------------------------------------------------------------------------------- 1 | "Header 1","Header 2 which contains some whitespace in fact quite a lot of it " 2 | "no","yes" 3 | "yes","yes" 4 | "no","no" 5 | "yes","no" 6 | -------------------------------------------------------------------------------- /tests/bool4.inp: -------------------------------------------------------------------------------- 1 | "Header 1",Header 2 which contains some whitespace in fact quite a lot of it 2 | "F","F" 3 | "T","F" 4 | "F", T 5 | "T ", T 6 | -------------------------------------------------------------------------------- /tests/comments.exp: -------------------------------------------------------------------------------- 1 | "Header1","Header2","Header3" 2 | "foo","bar","baz" 3 | "####"," ","#foo" 4 | -------------------------------------------------------------------------------- /tests/comments.inp: -------------------------------------------------------------------------------- 1 | # This line is a comment. 2 | Header1,Header2,Header3 3 | # This line is another comment 4 | # ... so is this one. 5 | foo,bar,baz 6 | "####"," ","#foo" 7 | -------------------------------------------------------------------------------- /tests/conv_funcs_valid.exp: -------------------------------------------------------------------------------- 1 | "561","date(2016, 7, 14, 0, 0, 0, 0)","1.2345","6.7890","Hello World","yes(date(2016, 7, 15, 0, 0, 0, 0))","yes(4.4444)","yes("5.5555")","yes(680)","yes("foobarbaz")" 2 | "562","date(2016, 7, 13, 0, 0, 0, 0)","3.3333","8.8888","Hello again","no","no","no","no","no" 3 | -------------------------------------------------------------------------------- /tests/conv_funcs_valid.inp: -------------------------------------------------------------------------------- 1 | 561,2016-07-14,1.2345,6.7890,"Hello World",2016-07-15,4.4444,5.5555,680,"foobarbaz" 2 | 562,2016-07-13,3.3333,8.8888,"Hello again",,,,, 3 | -------------------------------------------------------------------------------- /tests/countrylist.exp: -------------------------------------------------------------------------------- 1 | "Sort Order","Common Name","Formal Name","Type","Sub Type","Sovereignty","Capital","ISO 4217 Currency Code","ISO 4217 Currency Name","ITU-T Telephone Code","ISO 3166-1 2 Letter Code","ISO 3166-1 3 Letter Code","ISO 3166-1 Number","IANA Country Code TLD" 2 | "1","Afghanistan","Islamic State of Afghanistan","Independent State","","","Kabul","AFN","Afghani","+93","AF","AFG","004",".af" 3 | "2","Albania","Republic of Albania","Independent State","","","Tirana","ALL","Lek","+355","AL","ALB","008",".al" 4 | "3","Algeria","People's Democratic Republic of Algeria","Independent State","","","Algiers","DZD","Dinar","+213","DZ","DZA","012",".dz" 5 | "4","Andorra","Principality of Andorra","Independent State","","","Andorra la Vella","EUR","Euro","+376","AD","AND","020",".ad" 6 | "5","Angola","Republic of Angola","Independent State","","","Luanda","AOA","Kwanza","+244","AO","AGO","024",".ao" 7 | "6","Antigua and Barbuda","","Independent State","","","Saint John's","XCD","Dollar","+1-268","AG","ATG","028",".ag" 8 | "7","Argentina","Argentine Republic","Independent State","","","Buenos Aires","ARS","Peso","+54","AR","ARG","032",".ar" 9 | "8","Armenia","Republic of Armenia","Independent State","","","Yerevan","AMD","Dram","+374","AM","ARM","051",".am" 10 | "9","Australia","Commonwealth of Australia","Independent State","","","Canberra","AUD","Dollar","+61","AU","AUS","036",".au" 11 | "10","Austria","Republic of Austria","Independent State","","","Vienna","EUR","Euro","+43","AT","AUT","040",".at" 12 | "11","Azerbaijan","Republic of Azerbaijan","Independent State","","","Baku","AZN","Manat","+994","AZ","AZE","031",".az" 13 | "12","Bahamas, The","Commonwealth of The Bahamas","Independent State","","","Nassau","BSD","Dollar","+1-242","BS","BHS","044",".bs" 14 | "13","Bahrain","Kingdom of Bahrain","Independent State","","","Manama","BHD","Dinar","+973","BH","BHR","048",".bh" 15 | "14","Bangladesh","People's Republic of Bangladesh","Independent State","","","Dhaka","BDT","Taka","+880","BD","BGD","050",".bd" 16 | "15","Barbados","","Independent State","","","Bridgetown","BBD","Dollar","+1-246","BB","BRB","052",".bb" 17 | "16","Belarus","Republic of Belarus","Independent State","","","Minsk","BYR","Ruble","+375","BY","BLR","112",".by" 18 | "17","Belgium","Kingdom of Belgium","Independent State","","","Brussels","EUR","Euro","+32","BE","BEL","056",".be" 19 | "18","Belize","","Independent State","","","Belmopan","BZD","Dollar","+501","BZ","BLZ","084",".bz" 20 | "19","Benin","Republic of Benin","Independent State","","","Porto-Novo","XOF","Franc","+229","BJ","BEN","204",".bj" 21 | "20","Bhutan","Kingdom of Bhutan","Independent State","","","Thimphu","BTN","Ngultrum","+975","BT","BTN","064",".bt" 22 | "21","Bolivia","Republic of Bolivia","Independent State","","","La Paz (administrative/legislative) and Sucre (judical)","BOB","Boliviano","+591","BO","BOL","068",".bo" 23 | "22","Bosnia and Herzegovina","","Independent State","","","Sarajevo","BAM","Marka","+387","BA","BIH","070",".ba" 24 | "23","Botswana","Republic of Botswana","Independent State","","","Gaborone","BWP","Pula","+267","BW","BWA","072",".bw" 25 | "24","Brazil","Federative Republic of Brazil","Independent State","","","Brasilia","BRL","Real","+55","BR","BRA","076",".br" 26 | "25","Brunei","Negara Brunei Darussalam","Independent State","","","Bandar Seri Begawan","BND","Dollar","+673","BN","BRN","096",".bn" 27 | "26","Bulgaria","Republic of Bulgaria","Independent State","","","Sofia","BGN","Lev","+359","BG","BGR","100",".bg" 28 | "27","Burkina Faso","","Independent State","","","Ouagadougou","XOF","Franc","+226","BF","BFA","854",".bf" 29 | "28","Burundi","Republic of Burundi","Independent State","","","Bujumbura","BIF","Franc","+257","BI","BDI","108",".bi" 30 | "29","Cambodia","Kingdom of Cambodia","Independent State","","","Phnom Penh","KHR","Riels","+855","KH","KHM","116",".kh" 31 | "30","Cameroon","Republic of Cameroon","Independent State","","","Yaounde","XAF","Franc","+237","CM","CMR","120",".cm" 32 | "31","Canada","","Independent State","","","Ottawa","CAD","Dollar","+1","CA","CAN","124",".ca" 33 | "32","Cape Verde","Republic of Cape Verde","Independent State","","","Praia","CVE","Escudo","+238","CV","CPV","132",".cv" 34 | "33","Central African Republic","","Independent State","","","Bangui","XAF","Franc","+236","CF","CAF","140",".cf" 35 | "34","Chad","Republic of Chad","Independent State","","","N'Djamena","XAF","Franc","+235","TD","TCD","148",".td" 36 | "35","Chile","Republic of Chile","Independent State","","","Santiago (administrative/judical) and Valparaiso (legislative)","CLP","Peso","+56","CL","CHL","152",".cl" 37 | "36","China, People's Republic of","People's Republic of China","Independent State","","","Beijing","CNY","Yuan Renminbi","+86","CN","CHN","156",".cn" 38 | "37","Colombia","Republic of Colombia","Independent State","","","Bogota","COP","Peso","+57","CO","COL","170",".co" 39 | "38","Comoros","Union of Comoros","Independent State","","","Moroni","KMF","Franc","+269","KM","COM","174",".km" 40 | "39","Congo, Democratic Republic of the (Congo Kinshasa)","Democratic Republic of the Congo","Independent State","","","Kinshasa","CDF","Franc","+243","CD","COD","180",".cd" 41 | "40","Congo, Republic of the (Congo Brazzaville)","Republic of the Congo","Independent State","","","Brazzaville","XAF","Franc","+242","CG","COG","178",".cg" 42 | "41","Costa Rica","Republic of Costa Rica","Independent State","","","San Jose","CRC","Colon","+506","CR","CRI","188",".cr" 43 | "42","Cote d'Ivoire (Ivory Coast)","Republic of Cote d'Ivoire","Independent State","","","Yamoussoukro","XOF","Franc","+225","CI","CIV","384",".ci" 44 | "43","Croatia","Republic of Croatia","Independent State","","","Zagreb","HRK","Kuna","+385","HR","HRV","191",".hr" 45 | "44","Cuba","Republic of Cuba","Independent State","","","Havana","CUP","Peso","+53","CU","CUB","192",".cu" 46 | "45","Cyprus","Republic of Cyprus","Independent State","","","Nicosia","CYP","Pound","+357","CY","CYP","196",".cy" 47 | "46","Czech Republic","","Independent State","","","Prague","CZK","Koruna","+420","CZ","CZE","203",".cz" 48 | "47","Denmark","Kingdom of Denmark","Independent State","","","Copenhagen","DKK","Krone","+45","DK","DNK","208",".dk" 49 | "48","Djibouti","Republic of Djibouti","Independent State","","","Djibouti","DJF","Franc","+253","DJ","DJI","262",".dj" 50 | "49","Dominica","Commonwealth of Dominica","Independent State","","","Roseau","XCD","Dollar","+1-767","DM","DMA","212",".dm" 51 | "50","Dominican Republic","","Independent State","","","Santo Domingo","DOP","Peso","+1-809 and 1-829","DO","DOM","214",".do" 52 | "51","Ecuador","Republic of Ecuador","Independent State","","","Quito","USD","Dollar","+593","EC","ECU","218",".ec" 53 | "52","Egypt","Arab Republic of Egypt","Independent State","","","Cairo","EGP","Pound","+20","EG","EGY","818",".eg" 54 | "53","El Salvador","Republic of El Salvador","Independent State","","","San Salvador","USD","Dollar","+503","SV","SLV","222",".sv" 55 | "54","Equatorial Guinea","Republic of Equatorial Guinea","Independent State","","","Malabo","XAF","Franc","+240","GQ","GNQ","226",".gq" 56 | "55","Eritrea","State of Eritrea","Independent State","","","Asmara","ERN","Nakfa","+291","ER","ERI","232",".er" 57 | "56","Estonia","Republic of Estonia","Independent State","","","Tallinn","EEK","Kroon","+372","EE","EST","233",".ee" 58 | "57","Ethiopia","Federal Democratic Republic of Ethiopia","Independent State","","","Addis Ababa","ETB","Birr","+251","ET","ETH","231",".et" 59 | "58","Fiji","Republic of the Fiji Islands","Independent State","","","Suva","FJD","Dollar","+679","FJ","FJI","242",".fj" 60 | "59","Finland","Republic of Finland","Independent State","","","Helsinki","EUR","Euro","+358","FI","FIN","246",".fi" 61 | "60","France","French Republic","Independent State","","","Paris","EUR","Euro","+33","FR","FRA","250",".fr" 62 | "61","Gabon","Gabonese Republic","Independent State","","","Libreville","XAF","Franc","+241","GA","GAB","266",".ga" 63 | "62","Gambia, The","Republic of The Gambia","Independent State","","","Banjul","GMD","Dalasi","+220","GM","GMB","270",".gm" 64 | "63","Georgia","Republic of Georgia","Independent State","","","Tbilisi","GEL","Lari","+995","GE","GEO","268",".ge" 65 | "64","Germany","Federal Republic of Germany","Independent State","","","Berlin","EUR","Euro","+49","DE","DEU","276",".de" 66 | "65","Ghana","Republic of Ghana","Independent State","","","Accra","GHS","Cedi","+233","GH","GHA","288",".gh" 67 | "66","Greece","Hellenic Republic","Independent State","","","Athens","EUR","Euro","+30","GR","GRC","300",".gr" 68 | "67","Grenada","","Independent State","","","Saint George's","XCD","Dollar","+1-473","GD","GRD","308",".gd" 69 | "68","Guatemala","Republic of Guatemala","Independent State","","","Guatemala","GTQ","Quetzal","+502","GT","GTM","320",".gt" 70 | "69","Guinea","Republic of Guinea","Independent State","","","Conakry","GNF","Franc","+224","GN","GIN","324",".gn" 71 | "70","Guinea-Bissau","Republic of Guinea-Bissau","Independent State","","","Bissau","XOF","Franc","+245","GW","GNB","624",".gw" 72 | "71","Guyana","Co-operative Republic of Guyana","Independent State","","","Georgetown","GYD","Dollar","+592","GY","GUY","328",".gy" 73 | "72","Haiti","Republic of Haiti","Independent State","","","Port-au-Prince","HTG","Gourde","+509","HT","HTI","332",".ht" 74 | "73","Honduras","Republic of Honduras","Independent State","","","Tegucigalpa","HNL","Lempira","+504","HN","HND","340",".hn" 75 | "74","Hungary","Republic of Hungary","Independent State","","","Budapest","HUF","Forint","+36","HU","HUN","348",".hu" 76 | "75","Iceland","Republic of Iceland","Independent State","","","Reykjavik","ISK","Krona","+354","IS","ISL","352",".is" 77 | "76","India","Republic of India","Independent State","","","New Delhi","INR","Rupee","+91","IN","IND","356",".in" 78 | "77","Indonesia","Republic of Indonesia","Independent State","","","Jakarta","IDR","Rupiah","+62","ID","IDN","360",".id" 79 | "78","Iran","Islamic Republic of Iran","Independent State","","","Tehran","IRR","Rial","+98","IR","IRN","364",".ir" 80 | "79","Iraq","Republic of Iraq","Independent State","","","Baghdad","IQD","Dinar","+964","IQ","IRQ","368",".iq" 81 | "80","Ireland","","Independent State","","","Dublin","EUR","Euro","+353","IE","IRL","372",".ie" 82 | "81","Israel","State of Israel","Independent State","","","Jerusalem","ILS","Shekel","+972","IL","ISR","376",".il" 83 | "82","Italy","Italian Republic","Independent State","","","Rome","EUR","Euro","+39","IT","ITA","380",".it" 84 | "83","Jamaica","","Independent State","","","Kingston","JMD","Dollar","+1-876","JM","JAM","388",".jm" 85 | "84","Japan","","Independent State","","","Tokyo","JPY","Yen","+81","JP","JPN","392",".jp" 86 | "85","Jordan","Hashemite Kingdom of Jordan","Independent State","","","Amman","JOD","Dinar","+962","JO","JOR","400",".jo" 87 | "86","Kazakhstan","Republic of Kazakhstan","Independent State","","","Astana","KZT","Tenge","+7","KZ","KAZ","398",".kz" 88 | "87","Kenya","Republic of Kenya","Independent State","","","Nairobi","KES","Shilling","+254","KE","KEN","404",".ke" 89 | "88","Kiribati","Republic of Kiribati","Independent State","","","Tarawa","AUD","Dollar","+686","KI","KIR","296",".ki" 90 | "89","Korea, Democratic People's Republic of (North Korea)","Democratic People's Republic of Korea","Independent State","","","Pyongyang","KPW","Won","+850","KP","PRK","408",".kp" 91 | "90","Korea, Republic of (South Korea)","Republic of Korea","Independent State","","","Seoul","KRW","Won","+82","KR","KOR","410",".kr" 92 | "91","Kuwait","State of Kuwait","Independent State","","","Kuwait","KWD","Dinar","+965","KW","KWT","414",".kw" 93 | "92","Kyrgyzstan","Kyrgyz Republic","Independent State","","","Bishkek","KGS","Som","+996","KG","KGZ","417",".kg" 94 | "93","Laos","Lao People's Democratic Republic","Independent State","","","Vientiane","LAK","Kip","+856","LA","LAO","418",".la" 95 | "94","Latvia","Republic of Latvia","Independent State","","","Riga","LVL","Lat","+371","LV","LVA","428",".lv" 96 | "95","Lebanon","Lebanese Republic","Independent State","","","Beirut","LBP","Pound","+961","LB","LBN","422",".lb" 97 | "96","Lesotho","Kingdom of Lesotho","Independent State","","","Maseru","LSL","Loti","+266","LS","LSO","426",".ls" 98 | "97","Liberia","Republic of Liberia","Independent State","","","Monrovia","LRD","Dollar","+231","LR","LBR","430",".lr" 99 | "98","Libya","Great Socialist People's Libyan Arab Jamahiriya","Independent State","","","Tripoli","LYD","Dinar","+218","LY","LBY","434",".ly" 100 | "99","Liechtenstein","Principality of Liechtenstein","Independent State","","","Vaduz","CHF","Franc","+423","LI","LIE","438",".li" 101 | "100","Lithuania","Republic of Lithuania","Independent State","","","Vilnius","LTL","Litas","+370","LT","LTU","440",".lt" 102 | "101","Luxembourg","Grand Duchy of Luxembourg","Independent State","","","Luxembourg","EUR","Euro","+352","LU","LUX","442",".lu" 103 | "102","Macedonia","Republic of Macedonia","Independent State","","","Skopje","MKD","Denar","+389","MK","MKD","807",".mk" 104 | "103","Madagascar","Republic of Madagascar","Independent State","","","Antananarivo","MGA","Ariary","+261","MG","MDG","450",".mg" 105 | "104","Malawi","Republic of Malawi","Independent State","","","Lilongwe","MWK","Kwacha","+265","MW","MWI","454",".mw" 106 | "105","Malaysia","","Independent State","","","Kuala Lumpur (legislative/judical) and Putrajaya (administrative)","MYR","Ringgit","+60","MY","MYS","458",".my" 107 | "106","Maldives","Republic of Maldives","Independent State","","","Male","MVR","Rufiyaa","+960","MV","MDV","462",".mv" 108 | "107","Mali","Republic of Mali","Independent State","","","Bamako","XOF","Franc","+223","ML","MLI","466",".ml" 109 | "108","Malta","Republic of Malta","Independent State","","","Valletta","MTL","Lira","+356","MT","MLT","470",".mt" 110 | "109","Marshall Islands","Republic of the Marshall Islands","Independent State","","","Majuro","USD","Dollar","+692","MH","MHL","584",".mh" 111 | "110","Mauritania","Islamic Republic of Mauritania","Independent State","","","Nouakchott","MRO","Ouguiya","+222","MR","MRT","478",".mr" 112 | "111","Mauritius","Republic of Mauritius","Independent State","","","Port Louis","MUR","Rupee","+230","MU","MUS","480",".mu" 113 | "112","Mexico","United Mexican States","Independent State","","","Mexico","MXN","Peso","+52","MX","MEX","484",".mx" 114 | "113","Micronesia","Federated States of Micronesia","Independent State","","","Palikir","USD","Dollar","+691","FM","FSM","583",".fm" 115 | "114","Moldova","Republic of Moldova","Independent State","","","Chisinau","MDL","Leu","+373","MD","MDA","498",".md" 116 | "115","Monaco","Principality of Monaco","Independent State","","","Monaco","EUR","Euro","+377","MC","MCO","492",".mc" 117 | "116","Mongolia","","Independent State","","","Ulaanbaatar","MNT","Tugrik","+976","MN","MNG","496",".mn" 118 | "117","Montenegro","Republic of Montenegro","Independent State","","","Podgorica","EUR","Euro","+382","ME","MNE","499",".me and .yu" 119 | "118","Morocco","Kingdom of Morocco","Independent State","","","Rabat","MAD","Dirham","+212","MA","MAR","504",".ma" 120 | "119","Mozambique","Republic of Mozambique","Independent State","","","Maputo","MZM","Meticail","+258","MZ","MOZ","508",".mz" 121 | "120","Myanmar (Burma)","Union of Myanmar","Independent State","","","Naypyidaw","MMK","Kyat","+95","MM","MMR","104",".mm" 122 | "121","Namibia","Republic of Namibia","Independent State","","","Windhoek","NAD","Dollar","+264","NA","NAM","516",".na" 123 | "122","Nauru","Republic of Nauru","Independent State","","","Yaren","AUD","Dollar","+674","NR","NRU","520",".nr" 124 | "123","Nepal","","Independent State","","","Kathmandu","NPR","Rupee","+977","NP","NPL","524",".np" 125 | "124","Netherlands","Kingdom of the Netherlands","Independent State","","","Amsterdam (administrative) and The Hague (legislative/judical)","EUR","Euro","+31","NL","NLD","528",".nl" 126 | "125","New Zealand","","Independent State","","","Wellington","NZD","Dollar","+64","NZ","NZL","554",".nz" 127 | "126","Nicaragua","Republic of Nicaragua","Independent State","","","Managua","NIO","Cordoba","+505","NI","NIC","558",".ni" 128 | "127","Niger","Republic of Niger","Independent State","","","Niamey","XOF","Franc","+227","NE","NER","562",".ne" 129 | "128","Nigeria","Federal Republic of Nigeria","Independent State","","","Abuja","NGN","Naira","+234","NG","NGA","566",".ng" 130 | "129","Norway","Kingdom of Norway","Independent State","","","Oslo","NOK","Krone","+47","NO","NOR","578",".no" 131 | "130","Oman","Sultanate of Oman","Independent State","","","Muscat","OMR","Rial","+968","OM","OMN","512",".om" 132 | "131","Pakistan","Islamic Republic of Pakistan","Independent State","","","Islamabad","PKR","Rupee","+92","PK","PAK","586",".pk" 133 | "132","Palau","Republic of Palau","Independent State","","","Melekeok","USD","Dollar","+680","PW","PLW","585",".pw" 134 | "133","Panama","Republic of Panama","Independent State","","","Panama","PAB","Balboa","+507","PA","PAN","591",".pa" 135 | "134","Papua New Guinea","Independent State of Papua New Guinea","Independent State","","","Port Moresby","PGK","Kina","+675","PG","PNG","598",".pg" 136 | "135","Paraguay","Republic of Paraguay","Independent State","","","Asuncion","PYG","Guarani","+595","PY","PRY","600",".py" 137 | "136","Peru","Republic of Peru","Independent State","","","Lima","PEN","Sol","+51","PE","PER","604",".pe" 138 | "137","Philippines","Republic of the Philippines","Independent State","","","Manila","PHP","Peso","+63","PH","PHL","608",".ph" 139 | "138","Poland","Republic of Poland","Independent State","","","Warsaw","PLN","Zloty","+48","PL","POL","616",".pl" 140 | "139","Portugal","Portuguese Republic","Independent State","","","Lisbon","EUR","Euro","+351","PT","PRT","620",".pt" 141 | "140","Qatar","State of Qatar","Independent State","","","Doha","QAR","Rial","+974","QA","QAT","634",".qa" 142 | "141","Romania","","Independent State","","","Bucharest","RON","Leu","+40","RO","ROU","642",".ro" 143 | "142","Russia","Russian Federation","Independent State","","","Moscow","RUB","Ruble","+7","RU","RUS","643",".ru and .su" 144 | "143","Rwanda","Republic of Rwanda","Independent State","","","Kigali","RWF","Franc","+250","RW","RWA","646",".rw" 145 | "144","Saint Kitts and Nevis","Federation of Saint Kitts and Nevis","Independent State","","","Basseterre","XCD","Dollar","+1-869","KN","KNA","659",".kn" 146 | "145","Saint Lucia","","Independent State","","","Castries","XCD","Dollar","+1-758","LC","LCA","662",".lc" 147 | "146","Saint Vincent and the Grenadines","","Independent State","","","Kingstown","XCD","Dollar","+1-784","VC","VCT","670",".vc" 148 | "147","Samoa","Independent State of Samoa","Independent State","","","Apia","WST","Tala","+685","WS","WSM","882",".ws" 149 | "148","San Marino","Republic of San Marino","Independent State","","","San Marino","EUR","Euro","+378","SM","SMR","674",".sm" 150 | "149","Sao Tome and Principe","Democratic Republic of Sao Tome and Principe","Independent State","","","Sao Tome","STD","Dobra","+239","ST","STP","678",".st" 151 | "150","Saudi Arabia","Kingdom of Saudi Arabia","Independent State","","","Riyadh","SAR","Rial","+966","SA","SAU","682",".sa" 152 | "151","Senegal","Republic of Senegal","Independent State","","","Dakar","XOF","Franc","+221","SN","SEN","686",".sn" 153 | "152","Serbia","Republic of Serbia","Independent State","","","Belgrade","RSD","Dinar","+381","RS","SRB","688",".rs and .yu" 154 | "153","Seychelles","Republic of Seychelles","Independent State","","","Victoria","SCR","Rupee","+248","SC","SYC","690",".sc" 155 | "154","Sierra Leone","Republic of Sierra Leone","Independent State","","","Freetown","SLL","Leone","+232","SL","SLE","694",".sl" 156 | "155","Singapore","Republic of Singapore","Independent State","","","Singapore","SGD","Dollar","+65","SG","SGP","702",".sg" 157 | "156","Slovakia","Slovak Republic","Independent State","","","Bratislava","SKK","Koruna","+421","SK","SVK","703",".sk" 158 | "157","Slovenia","Republic of Slovenia","Independent State","","","Ljubljana","EUR","Euro","+386","SI","SVN","705",".si" 159 | "158","Solomon Islands","","Independent State","","","Honiara","SBD","Dollar","+677","SB","SLB","090",".sb" 160 | "159","Somalia","","Independent State","","","Mogadishu","SOS","Shilling","+252","SO","SOM","706",".so" 161 | "160","South Africa","Republic of South Africa","Independent State","","","Pretoria (administrative), Cape Town (legislative), and Bloemfontein (judical)","ZAR","Rand","+27","ZA","ZAF","710",".za" 162 | "161","Spain","Kingdom of Spain","Independent State","","","Madrid","EUR","Euro","+34","ES","ESP","724",".es" 163 | "162","Sri Lanka","Democratic Socialist Republic of Sri Lanka","Independent State","","","Colombo (administrative/judical) and Sri Jayewardenepura Kotte (legislative)","LKR","Rupee","+94","LK","LKA","144",".lk" 164 | "163","Sudan","Republic of the Sudan","Independent State","","","Khartoum","SDG","Pound","+249","SD","SDN","736",".sd" 165 | "164","Suriname","Republic of Suriname","Independent State","","","Paramaribo","SRD","Dollar","+597","SR","SUR","740",".sr" 166 | "165","Swaziland","Kingdom of Swaziland","Independent State","","","Mbabane (administrative) and Lobamba (legislative)","SZL","Lilangeni","+268","SZ","SWZ","748",".sz" 167 | "166","Sweden","Kingdom of Sweden","Independent State","","","Stockholm","SEK","Kronoa","+46","SE","SWE","752",".se" 168 | "167","Switzerland","Swiss Confederation","Independent State","","","Bern","CHF","Franc","+41","CH","CHE","756",".ch" 169 | "168","Syria","Syrian Arab Republic","Independent State","","","Damascus","SYP","Pound","+963","SY","SYR","760",".sy" 170 | "169","Tajikistan","Republic of Tajikistan","Independent State","","","Dushanbe","TJS","Somoni","+992","TJ","TJK","762",".tj" 171 | "170","Tanzania","United Republic of Tanzania","Independent State","","","Dar es Salaam (administrative/judical) and Dodoma (legislative)","TZS","Shilling","+255","TZ","TZA","834",".tz" 172 | "171","Thailand","Kingdom of Thailand","Independent State","","","Bangkok","THB","Baht","+66","TH","THA","764",".th" 173 | "172","Timor-Leste (East Timor)","Democratic Republic of Timor-Leste","Independent State","","","Dili","USD","Dollar","+670","TL","TLS","626",".tp and .tl" 174 | "173","Togo","Togolese Republic","Independent State","","","Lome","XOF","Franc","+228","TG","TGO","768",".tg" 175 | "174","Tonga","Kingdom of Tonga","Independent State","","","Nuku'alofa","TOP","Pa'anga","+676","TO","TON","776",".to" 176 | "175","Trinidad and Tobago","Republic of Trinidad and Tobago","Independent State","","","Port-of-Spain","TTD","Dollar","+1-868","TT","TTO","780",".tt" 177 | "176","Tunisia","Tunisian Republic","Independent State","","","Tunis","TND","Dinar","+216","TN","TUN","788",".tn" 178 | "177","Turkey","Republic of Turkey","Independent State","","","Ankara","TRY","Lira","+90","TR","TUR","792",".tr" 179 | "178","Turkmenistan","","Independent State","","","Ashgabat","TMM","Manat","+993","TM","TKM","795",".tm" 180 | "179","Tuvalu","","Independent State","","","Funafuti","AUD","Dollar","+688","TV","TUV","798",".tv" 181 | "180","Uganda","Republic of Uganda","Independent State","","","Kampala","UGX","Shilling","+256","UG","UGA","800",".ug" 182 | "181","Ukraine","","Independent State","","","Kiev","UAH","Hryvnia","+380","UA","UKR","804",".ua" 183 | "182","United Arab Emirates","United Arab Emirates","Independent State","","","Abu Dhabi","AED","Dirham","+971","AE","ARE","784",".ae" 184 | "183","United Kingdom","United Kingdom of Great Britain and Northern Ireland","Independent State","","","London","GBP","Pound","+44","GB","GBR","826",".uk" 185 | "184","United States","United States of America","Independent State","","","Washington","USD","Dollar","+1","US","USA","840",".us" 186 | "185","Uruguay","Oriental Republic of Uruguay","Independent State","","","Montevideo","UYU","Peso","+598","UY","URY","858",".uy" 187 | "186","Uzbekistan","Republic of Uzbekistan","Independent State","","","Tashkent","UZS","Som","+998","UZ","UZB","860",".uz" 188 | "187","Vanuatu","Republic of Vanuatu","Independent State","","","Port-Vila","VUV","Vatu","+678","VU","VUT","548",".vu" 189 | "188","Vatican City","State of the Vatican City","Independent State","","","Vatican City","EUR","Euro","+379","VA","VAT","336",".va" 190 | "189","Venezuela","Bolivarian Republic of Venezuela","Independent State","","","Caracas","VEB","Bolivar","+58","VE","VEN","862",".ve" 191 | "190","Vietnam","Socialist Republic of Vietnam","Independent State","","","Hanoi","VND","Dong","+84","VN","VNM","704",".vn" 192 | "191","Yemen","Republic of Yemen","Independent State","","","Sanaa","YER","Rial","+967","YE","YEM","887",".ye" 193 | "192","Zambia","Republic of Zambia","Independent State","","","Lusaka","ZMK","Kwacha","+260","ZM","ZMB","894",".zm" 194 | "193","Zimbabwe","Republic of Zimbabwe","Independent State","","","Harare","ZWD","Dollar","+263","ZW","ZWE","716",".zw" 195 | "194","Abkhazia","Republic of Abkhazia","Proto Independent State","","","Sokhumi","RUB","Ruble","+995","GE","GEO","268",".ge" 196 | "195","China, Republic of (Taiwan)","Republic of China","Proto Independent State","","","Taipei","TWD","Dollar","+886","TW","TWN","158",".tw" 197 | "196","Nagorno-Karabakh","Nagorno-Karabakh Republic","Proto Independent State","","","Stepanakert","AMD","Dram","+374-97","AZ","AZE","031",".az" 198 | "197","Northern Cyprus","Turkish Republic of Northern Cyprus","Proto Independent State","","","Nicosia","TRY","Lira","+90-392","CY","CYP","196",".nc.tr" 199 | "198","Pridnestrovie (Transnistria)","Pridnestrovian Moldavian Republic","Proto Independent State","","","Tiraspol","","Ruple","+373-533","MD","MDA","498",".md" 200 | "199","Somaliland","Republic of Somaliland","Proto Independent State","","","Hargeisa","","Shilling","+252","SO","SOM","706",".so" 201 | "200","South Ossetia","Republic of South Ossetia","Proto Independent State","","","Tskhinvali","RUB and GEL","Ruble and Lari","+995","GE","GEO","268",".ge" 202 | "201","Ashmore and Cartier Islands","Territory of Ashmore and Cartier Islands","Dependency","External Territory","Australia","","","","","AU","AUS","036",".au" 203 | "202","Christmas Island","Territory of Christmas Island","Dependency","External Territory","Australia","The Settlement (Flying Fish Cove)","AUD","Dollar","+61","CX","CXR","162",".cx" 204 | "203","Cocos (Keeling) Islands","Territory of Cocos (Keeling) Islands","Dependency","External Territory","Australia","West Island","AUD","Dollar","+61","CC","CCK","166",".cc" 205 | "204","Coral Sea Islands","Coral Sea Islands Territory","Dependency","External Territory","Australia","","","","","AU","AUS","036",".au" 206 | "205","Heard Island and McDonald Islands","Territory of Heard Island and McDonald Islands","Dependency","External Territory","Australia","","","","","HM","HMD","334",".hm" 207 | "206","Norfolk Island","Territory of Norfolk Island","Dependency","External Territory","Australia","Kingston","AUD","Dollar","+672","NF","NFK","574",".nf" 208 | "207","New Caledonia","","Dependency","Sui generis Collectivity","France","Noumea","XPF","Franc","+687","NC","NCL","540",".nc" 209 | "208","French Polynesia","Overseas Country of French Polynesia","Dependency","Overseas Collectivity","France","Papeete","XPF","Franc","+689","PF","PYF","258",".pf" 210 | "209","Mayotte","Departmental Collectivity of Mayotte","Dependency","Overseas Collectivity","France","Mamoudzou","EUR","Euro","+262","YT","MYT","175",".yt" 211 | "210","Saint Barthelemy","Collectivity of Saint Barthelemy","Dependency","Overseas Collectivity","France","Gustavia","EUR","Euro","+590","GP","GLP","312",".gp" 212 | "211","Saint Martin","Collectivity of Saint Martin","Dependency","Overseas Collectivity","France","Marigot","EUR","Euro","+590","GP","GLP","312",".gp" 213 | "212","Saint Pierre and Miquelon","Territorial Collectivity of Saint Pierre and Miquelon","Dependency","Overseas Collectivity","France","Saint-Pierre","EUR","Euro","+508","PM","SPM","666",".pm" 214 | "213","Wallis and Futuna","Collectivity of the Wallis and Futuna Islands","Dependency","Overseas Collectivity","France","Mata'utu","XPF","Franc","+681","WF","WLF","876",".wf" 215 | "214","French Southern and Antarctic Lands","Territory of the French Southern and Antarctic Lands","Dependency","Overseas Territory","France","Martin-de-Vivies","","","","TF","ATF","260",".tf" 216 | "215","Clipperton Island","","Dependency","Possession","France","","","","","PF","PYF","258",".pf" 217 | "216","Bouvet Island","","Dependency","Territory","Norway","","","","","BV","BVT","074",".bv" 218 | "217","Cook Islands","","Dependency","Self-Governing in Free Association","New Zealand","Avarua","NZD","Dollar","+682","CK","COK","184",".ck" 219 | "218","Niue","","Dependency","Self-Governing in Free Association","New Zealand","Alofi","NZD","Dollar","+683","NU","NIU","570",".nu" 220 | "219","Tokelau","","Dependency","Territory","New Zealand","","NZD","Dollar","+690","TK","TKL","772",".tk" 221 | "220","Guernsey","Bailiwick of Guernsey","Dependency","Crown Dependency","United Kingdom","Saint Peter Port","GGP","Pound","+44","GG","GGY","831",".gg" 222 | "221","Isle of Man","","Dependency","Crown Dependency","United Kingdom","Douglas","IMP","Pound","+44","IM","IMN","833",".im" 223 | "222","Jersey","Bailiwick of Jersey","Dependency","Crown Dependency","United Kingdom","Saint Helier","JEP","Pound","+44","JE","JEY","832",".je" 224 | "223","Anguilla","","Dependency","Overseas Territory","United Kingdom","The Valley","XCD","Dollar","+1-264","AI","AIA","660",".ai" 225 | "224","Bermuda","","Dependency","Overseas Territory","United Kingdom","Hamilton","BMD","Dollar","+1-441","BM","BMU","060",".bm" 226 | "225","British Indian Ocean Territory","","Dependency","Overseas Territory","United Kingdom","","","","+246","IO","IOT","086",".io" 227 | "226","British Sovereign Base Areas","","Dependency","Overseas Territory","United Kingdom","Episkopi","CYP","Pound","+357","","","","" 228 | "227","British Virgin Islands","","Dependency","Overseas Territory","United Kingdom","Road Town","USD","Dollar","+1-284","VG","VGB","092",".vg" 229 | "228","Cayman Islands","","Dependency","Overseas Territory","United Kingdom","George Town","KYD","Dollar","+1-345","KY","CYM","136",".ky" 230 | "229","Falkland Islands (Islas Malvinas)","","Dependency","Overseas Territory","United Kingdom","Stanley","FKP","Pound","+500","FK","FLK","238",".fk" 231 | "230","Gibraltar","","Dependency","Overseas Territory","United Kingdom","Gibraltar","GIP","Pound","+350","GI","GIB","292",".gi" 232 | "231","Montserrat","","Dependency","Overseas Territory","United Kingdom","Plymouth","XCD","Dollar","+1-664","MS","MSR","500",".ms" 233 | "232","Pitcairn Islands","","Dependency","Overseas Territory","United Kingdom","Adamstown","NZD","Dollar","","PN","PCN","612",".pn" 234 | "233","Saint Helena","","Dependency","Overseas Territory","United Kingdom","Jamestown","SHP","Pound","+290","SH","SHN","654",".sh" 235 | "234","South Georgia and the South Sandwich Islands","","Dependency","Overseas Territory","United Kingdom","","","","","GS","SGS","239",".gs" 236 | "235","Turks and Caicos Islands","","Dependency","Overseas Territory","United Kingdom","Grand Turk","USD","Dollar","+1-649","TC","TCA","796",".tc" 237 | "236","Northern Mariana Islands","Commonwealth of The Northern Mariana Islands","Dependency","Commonwealth","United States","Saipan","USD","Dollar","+1-670","MP","MNP","580",".mp" 238 | "237","Puerto Rico","Commonwealth of Puerto Rico","Dependency","Commonwealth","United States","San Juan","USD","Dollar","+1-787 and 1-939","PR","PRI","630",".pr" 239 | "238","American Samoa","Territory of American Samoa","Dependency","Territory","United States","Pago Pago","USD","Dollar","+1-684","AS","ASM","016",".as" 240 | "239","Baker Island","","Dependency","Territory","United States","","","","","UM","UMI","581","" 241 | "240","Guam","Territory of Guam","Dependency","Territory","United States","Hagatna","USD","Dollar","+1-671","GU","GUM","316",".gu" 242 | "241","Howland Island","","Dependency","Territory","United States","","","","","UM","UMI","581","" 243 | "242","Jarvis Island","","Dependency","Territory","United States","","","","","UM","UMI","581","" 244 | "243","Johnston Atoll","","Dependency","Territory","United States","","","","","UM","UMI","581","" 245 | "244","Kingman Reef","","Dependency","Territory","United States","","","","","UM","UMI","581","" 246 | "245","Midway Islands","","Dependency","Territory","United States","","","","","UM","UMI","581","" 247 | "246","Navassa Island","","Dependency","Territory","United States","","","","","UM","UMI","581","" 248 | "247","Palmyra Atoll","","Dependency","Territory","United States","","","","","UM","UMI","581","" 249 | "248","U.S. Virgin Islands","United States Virgin Islands","Dependency","Territory","United States","Charlotte Amalie","USD","Dollar","+1-340","VI","VIR","850",".vi" 250 | "249","Wake Island","","Dependency","Territory","United States","","","","","UM","UMI","850","" 251 | "250","Hong Kong","Hong Kong Special Administrative Region","Proto Dependency","Special Administrative Region","China","","HKD","Dollar","+852","HK","HKG","344",".hk" 252 | "251","Macau","Macau Special Administrative Region","Proto Dependency","Special Administrative Region","China","Macau","MOP","Pataca","+853","MO","MAC","446",".mo" 253 | "252","Faroe Islands","","Proto Dependency","","Denmark","Torshavn","DKK","Krone","+298","FO","FRO","234",".fo" 254 | "253","Greenland","","Proto Dependency","","Denmark","Nuuk (Godthab)","DKK","Krone","+299","GL","GRL","304",".gl" 255 | "254","French Guiana","Overseas Region of Guiana","Proto Dependency","Overseas Region","France","Cayenne","EUR","Euro","+594","GF","GUF","254",".gf" 256 | "255","Guadeloupe","Overseas Region of Guadeloupe","Proto Dependency","Overseas Region","France","Basse-Terre","EUR","Euro","+590","GP","GLP","312",".gp" 257 | "256","Martinique","Overseas Region of Martinique","Proto Dependency","Overseas Region","France","Fort-de-France","EUR","Euro","+596","MQ","MTQ","474",".mq" 258 | "257","Reunion","Overseas Region of Reunion","Proto Dependency","Overseas Region","France","Saint-Denis","EUR","Euro","+262","RE","REU","638",".re" 259 | "258","Aland","","Proto Dependency","","Finland","Mariehamn","EUR","Euro","+358-18","AX","ALA","248",".ax" 260 | "259","Aruba","","Proto Dependency","","Netherlands","Oranjestad","AWG","Guilder","+297","AW","ABW","533",".aw" 261 | "260","Netherlands Antilles","","Proto Dependency","","Netherlands","Willemstad","ANG","Guilder","+599","AN","ANT","530",".an" 262 | "261","Svalbard","","Proto Dependency","","Norway","Longyearbyen","NOK","Krone","+47","SJ","SJM","744",".sj" 263 | "262","Ascension","","Proto Dependency","Dependency of Saint Helena","United Kingdom","Georgetown","SHP","Pound","+247","AC","ASC","",".ac" 264 | "263","Tristan da Cunha","","Proto Dependency","Dependency of Saint Helena","United Kingdom","Edinburgh","SHP","Pound","+290","TA","TAA","","" 265 | "264","Antarctica","","Disputed Territory","","Undetermined","","","","","AQ","ATA","010",".aq" 266 | "265","Kosovo","","Disputed Territory","","Administrated by the UN","Pristina","CSD and EUR","Dinar and Euro","+381","CS","SCG","891",".cs and .yu" 267 | "266","Palestinian Territories (Gaza Strip and West Bank)","","Disputed Territory","","Administrated by Israel","Gaza City (Gaza Strip) and Ramallah (West Bank)","ILS","Shekel","+970","PS","PSE","275",".ps" 268 | "267","Western Sahara","","Disputed Territory","","Administrated by Morocco","El-Aaiun","MAD","Dirham","+212","EH","ESH","732",".eh" 269 | "268","Australian Antarctic Territory","","Antarctic Territory","External Territory","Australia","","","","","AQ","ATA","010",".aq" 270 | "269","Ross Dependency","","Antarctic Territory","Territory","New Zealand","","","","","AQ","ATA","010",".aq" 271 | "270","Peter I Island","","Antarctic Territory","Territory","Norway","","","","","AQ","ATA","010",".aq" 272 | "271","Queen Maud Land","","Antarctic Territory","Territory","Norway","","","","","AQ","ATA","010",".aq" 273 | "272","British Antarctic Territory","","Antarctic Territory","Overseas Territory","United Kingdom","","","","","AQ","ATA","010",".aq" 274 | -------------------------------------------------------------------------------- /tests/countrylist.inp: -------------------------------------------------------------------------------- 1 | "Sort Order","Common Name","Formal Name","Type","Sub Type","Sovereignty","Capital","ISO 4217 Currency Code","ISO 4217 Currency Name","ITU-T Telephone Code","ISO 3166-1 2 Letter Code","ISO 3166-1 3 Letter Code","ISO 3166-1 Number","IANA Country Code TLD" 2 | "1","Afghanistan","Islamic State of Afghanistan","Independent State",,,"Kabul","AFN","Afghani","+93","AF","AFG","004",".af" 3 | "2","Albania","Republic of Albania","Independent State",,,"Tirana","ALL","Lek","+355","AL","ALB","008",".al" 4 | "3","Algeria","People's Democratic Republic of Algeria","Independent State",,,"Algiers","DZD","Dinar","+213","DZ","DZA","012",".dz" 5 | "4","Andorra","Principality of Andorra","Independent State",,,"Andorra la Vella","EUR","Euro","+376","AD","AND","020",".ad" 6 | "5","Angola","Republic of Angola","Independent State",,,"Luanda","AOA","Kwanza","+244","AO","AGO","024",".ao" 7 | "6","Antigua and Barbuda",,"Independent State",,,"Saint John's","XCD","Dollar","+1-268","AG","ATG","028",".ag" 8 | "7","Argentina","Argentine Republic","Independent State",,,"Buenos Aires","ARS","Peso","+54","AR","ARG","032",".ar" 9 | "8","Armenia","Republic of Armenia","Independent State",,,"Yerevan","AMD","Dram","+374","AM","ARM","051",".am" 10 | "9","Australia","Commonwealth of Australia","Independent State",,,"Canberra","AUD","Dollar","+61","AU","AUS","036",".au" 11 | "10","Austria","Republic of Austria","Independent State",,,"Vienna","EUR","Euro","+43","AT","AUT","040",".at" 12 | "11","Azerbaijan","Republic of Azerbaijan","Independent State",,,"Baku","AZN","Manat","+994","AZ","AZE","031",".az" 13 | "12","Bahamas, The","Commonwealth of The Bahamas","Independent State",,,"Nassau","BSD","Dollar","+1-242","BS","BHS","044",".bs" 14 | "13","Bahrain","Kingdom of Bahrain","Independent State",,,"Manama","BHD","Dinar","+973","BH","BHR","048",".bh" 15 | "14","Bangladesh","People's Republic of Bangladesh","Independent State",,,"Dhaka","BDT","Taka","+880","BD","BGD","050",".bd" 16 | "15","Barbados",,"Independent State",,,"Bridgetown","BBD","Dollar","+1-246","BB","BRB","052",".bb" 17 | "16","Belarus","Republic of Belarus","Independent State",,,"Minsk","BYR","Ruble","+375","BY","BLR","112",".by" 18 | "17","Belgium","Kingdom of Belgium","Independent State",,,"Brussels","EUR","Euro","+32","BE","BEL","056",".be" 19 | "18","Belize",,"Independent State",,,"Belmopan","BZD","Dollar","+501","BZ","BLZ","084",".bz" 20 | "19","Benin","Republic of Benin","Independent State",,,"Porto-Novo","XOF","Franc","+229","BJ","BEN","204",".bj" 21 | "20","Bhutan","Kingdom of Bhutan","Independent State",,,"Thimphu","BTN","Ngultrum","+975","BT","BTN","064",".bt" 22 | "21","Bolivia","Republic of Bolivia","Independent State",,,"La Paz (administrative/legislative) and Sucre (judical)","BOB","Boliviano","+591","BO","BOL","068",".bo" 23 | "22","Bosnia and Herzegovina",,"Independent State",,,"Sarajevo","BAM","Marka","+387","BA","BIH","070",".ba" 24 | "23","Botswana","Republic of Botswana","Independent State",,,"Gaborone","BWP","Pula","+267","BW","BWA","072",".bw" 25 | "24","Brazil","Federative Republic of Brazil","Independent State",,,"Brasilia","BRL","Real","+55","BR","BRA","076",".br" 26 | "25","Brunei","Negara Brunei Darussalam","Independent State",,,"Bandar Seri Begawan","BND","Dollar","+673","BN","BRN","096",".bn" 27 | "26","Bulgaria","Republic of Bulgaria","Independent State",,,"Sofia","BGN","Lev","+359","BG","BGR","100",".bg" 28 | "27","Burkina Faso",,"Independent State",,,"Ouagadougou","XOF","Franc","+226","BF","BFA","854",".bf" 29 | "28","Burundi","Republic of Burundi","Independent State",,,"Bujumbura","BIF","Franc","+257","BI","BDI","108",".bi" 30 | "29","Cambodia","Kingdom of Cambodia","Independent State",,,"Phnom Penh","KHR","Riels","+855","KH","KHM","116",".kh" 31 | "30","Cameroon","Republic of Cameroon","Independent State",,,"Yaounde","XAF","Franc","+237","CM","CMR","120",".cm" 32 | "31","Canada",,"Independent State",,,"Ottawa","CAD","Dollar","+1","CA","CAN","124",".ca" 33 | "32","Cape Verde","Republic of Cape Verde","Independent State",,,"Praia","CVE","Escudo","+238","CV","CPV","132",".cv" 34 | "33","Central African Republic",,"Independent State",,,"Bangui","XAF","Franc","+236","CF","CAF","140",".cf" 35 | "34","Chad","Republic of Chad","Independent State",,,"N'Djamena","XAF","Franc","+235","TD","TCD","148",".td" 36 | "35","Chile","Republic of Chile","Independent State",,,"Santiago (administrative/judical) and Valparaiso (legislative)","CLP","Peso","+56","CL","CHL","152",".cl" 37 | "36","China, People's Republic of","People's Republic of China","Independent State",,,"Beijing","CNY","Yuan Renminbi","+86","CN","CHN","156",".cn" 38 | "37","Colombia","Republic of Colombia","Independent State",,,"Bogota","COP","Peso","+57","CO","COL","170",".co" 39 | "38","Comoros","Union of Comoros","Independent State",,,"Moroni","KMF","Franc","+269","KM","COM","174",".km" 40 | "39","Congo, Democratic Republic of the (Congo Kinshasa)","Democratic Republic of the Congo","Independent State",,,"Kinshasa","CDF","Franc","+243","CD","COD","180",".cd" 41 | "40","Congo, Republic of the (Congo Brazzaville)","Republic of the Congo","Independent State",,,"Brazzaville","XAF","Franc","+242","CG","COG","178",".cg" 42 | "41","Costa Rica","Republic of Costa Rica","Independent State",,,"San Jose","CRC","Colon","+506","CR","CRI","188",".cr" 43 | "42","Cote d'Ivoire (Ivory Coast)","Republic of Cote d'Ivoire","Independent State",,,"Yamoussoukro","XOF","Franc","+225","CI","CIV","384",".ci" 44 | "43","Croatia","Republic of Croatia","Independent State",,,"Zagreb","HRK","Kuna","+385","HR","HRV","191",".hr" 45 | "44","Cuba","Republic of Cuba","Independent State",,,"Havana","CUP","Peso","+53","CU","CUB","192",".cu" 46 | "45","Cyprus","Republic of Cyprus","Independent State",,,"Nicosia","CYP","Pound","+357","CY","CYP","196",".cy" 47 | "46","Czech Republic",,"Independent State",,,"Prague","CZK","Koruna","+420","CZ","CZE","203",".cz" 48 | "47","Denmark","Kingdom of Denmark","Independent State",,,"Copenhagen","DKK","Krone","+45","DK","DNK","208",".dk" 49 | "48","Djibouti","Republic of Djibouti","Independent State",,,"Djibouti","DJF","Franc","+253","DJ","DJI","262",".dj" 50 | "49","Dominica","Commonwealth of Dominica","Independent State",,,"Roseau","XCD","Dollar","+1-767","DM","DMA","212",".dm" 51 | "50","Dominican Republic",,"Independent State",,,"Santo Domingo","DOP","Peso","+1-809 and 1-829","DO","DOM","214",".do" 52 | "51","Ecuador","Republic of Ecuador","Independent State",,,"Quito","USD","Dollar","+593","EC","ECU","218",".ec" 53 | "52","Egypt","Arab Republic of Egypt","Independent State",,,"Cairo","EGP","Pound","+20","EG","EGY","818",".eg" 54 | "53","El Salvador","Republic of El Salvador","Independent State",,,"San Salvador","USD","Dollar","+503","SV","SLV","222",".sv" 55 | "54","Equatorial Guinea","Republic of Equatorial Guinea","Independent State",,,"Malabo","XAF","Franc","+240","GQ","GNQ","226",".gq" 56 | "55","Eritrea","State of Eritrea","Independent State",,,"Asmara","ERN","Nakfa","+291","ER","ERI","232",".er" 57 | "56","Estonia","Republic of Estonia","Independent State",,,"Tallinn","EEK","Kroon","+372","EE","EST","233",".ee" 58 | "57","Ethiopia","Federal Democratic Republic of Ethiopia","Independent State",,,"Addis Ababa","ETB","Birr","+251","ET","ETH","231",".et" 59 | "58","Fiji","Republic of the Fiji Islands","Independent State",,,"Suva","FJD","Dollar","+679","FJ","FJI","242",".fj" 60 | "59","Finland","Republic of Finland","Independent State",,,"Helsinki","EUR","Euro","+358","FI","FIN","246",".fi" 61 | "60","France","French Republic","Independent State",,,"Paris","EUR","Euro","+33","FR","FRA","250",".fr" 62 | "61","Gabon","Gabonese Republic","Independent State",,,"Libreville","XAF","Franc","+241","GA","GAB","266",".ga" 63 | "62","Gambia, The","Republic of The Gambia","Independent State",,,"Banjul","GMD","Dalasi","+220","GM","GMB","270",".gm" 64 | "63","Georgia","Republic of Georgia","Independent State",,,"Tbilisi","GEL","Lari","+995","GE","GEO","268",".ge" 65 | "64","Germany","Federal Republic of Germany","Independent State",,,"Berlin","EUR","Euro","+49","DE","DEU","276",".de" 66 | "65","Ghana","Republic of Ghana","Independent State",,,"Accra","GHS","Cedi","+233","GH","GHA","288",".gh" 67 | "66","Greece","Hellenic Republic","Independent State",,,"Athens","EUR","Euro","+30","GR","GRC","300",".gr" 68 | "67","Grenada",,"Independent State",,,"Saint George's","XCD","Dollar","+1-473","GD","GRD","308",".gd" 69 | "68","Guatemala","Republic of Guatemala","Independent State",,,"Guatemala","GTQ","Quetzal","+502","GT","GTM","320",".gt" 70 | "69","Guinea","Republic of Guinea","Independent State",,,"Conakry","GNF","Franc","+224","GN","GIN","324",".gn" 71 | "70","Guinea-Bissau","Republic of Guinea-Bissau","Independent State",,,"Bissau","XOF","Franc","+245","GW","GNB","624",".gw" 72 | "71","Guyana","Co-operative Republic of Guyana","Independent State",,,"Georgetown","GYD","Dollar","+592","GY","GUY","328",".gy" 73 | "72","Haiti","Republic of Haiti","Independent State",,,"Port-au-Prince","HTG","Gourde","+509","HT","HTI","332",".ht" 74 | "73","Honduras","Republic of Honduras","Independent State",,,"Tegucigalpa","HNL","Lempira","+504","HN","HND","340",".hn" 75 | "74","Hungary","Republic of Hungary","Independent State",,,"Budapest","HUF","Forint","+36","HU","HUN","348",".hu" 76 | "75","Iceland","Republic of Iceland","Independent State",,,"Reykjavik","ISK","Krona","+354","IS","ISL","352",".is" 77 | "76","India","Republic of India","Independent State",,,"New Delhi","INR","Rupee","+91","IN","IND","356",".in" 78 | "77","Indonesia","Republic of Indonesia","Independent State",,,"Jakarta","IDR","Rupiah","+62","ID","IDN","360",".id" 79 | "78","Iran","Islamic Republic of Iran","Independent State",,,"Tehran","IRR","Rial","+98","IR","IRN","364",".ir" 80 | "79","Iraq","Republic of Iraq","Independent State",,,"Baghdad","IQD","Dinar","+964","IQ","IRQ","368",".iq" 81 | "80","Ireland",,"Independent State",,,"Dublin","EUR","Euro","+353","IE","IRL","372",".ie" 82 | "81","Israel","State of Israel","Independent State",,,"Jerusalem","ILS","Shekel","+972","IL","ISR","376",".il" 83 | "82","Italy","Italian Republic","Independent State",,,"Rome","EUR","Euro","+39","IT","ITA","380",".it" 84 | "83","Jamaica",,"Independent State",,,"Kingston","JMD","Dollar","+1-876","JM","JAM","388",".jm" 85 | "84","Japan",,"Independent State",,,"Tokyo","JPY","Yen","+81","JP","JPN","392",".jp" 86 | "85","Jordan","Hashemite Kingdom of Jordan","Independent State",,,"Amman","JOD","Dinar","+962","JO","JOR","400",".jo" 87 | "86","Kazakhstan","Republic of Kazakhstan","Independent State",,,"Astana","KZT","Tenge","+7","KZ","KAZ","398",".kz" 88 | "87","Kenya","Republic of Kenya","Independent State",,,"Nairobi","KES","Shilling","+254","KE","KEN","404",".ke" 89 | "88","Kiribati","Republic of Kiribati","Independent State",,,"Tarawa","AUD","Dollar","+686","KI","KIR","296",".ki" 90 | "89","Korea, Democratic People's Republic of (North Korea)","Democratic People's Republic of Korea","Independent State",,,"Pyongyang","KPW","Won","+850","KP","PRK","408",".kp" 91 | "90","Korea, Republic of (South Korea)","Republic of Korea","Independent State",,,"Seoul","KRW","Won","+82","KR","KOR","410",".kr" 92 | "91","Kuwait","State of Kuwait","Independent State",,,"Kuwait","KWD","Dinar","+965","KW","KWT","414",".kw" 93 | "92","Kyrgyzstan","Kyrgyz Republic","Independent State",,,"Bishkek","KGS","Som","+996","KG","KGZ","417",".kg" 94 | "93","Laos","Lao People's Democratic Republic","Independent State",,,"Vientiane","LAK","Kip","+856","LA","LAO","418",".la" 95 | "94","Latvia","Republic of Latvia","Independent State",,,"Riga","LVL","Lat","+371","LV","LVA","428",".lv" 96 | "95","Lebanon","Lebanese Republic","Independent State",,,"Beirut","LBP","Pound","+961","LB","LBN","422",".lb" 97 | "96","Lesotho","Kingdom of Lesotho","Independent State",,,"Maseru","LSL","Loti","+266","LS","LSO","426",".ls" 98 | "97","Liberia","Republic of Liberia","Independent State",,,"Monrovia","LRD","Dollar","+231","LR","LBR","430",".lr" 99 | "98","Libya","Great Socialist People's Libyan Arab Jamahiriya","Independent State",,,"Tripoli","LYD","Dinar","+218","LY","LBY","434",".ly" 100 | "99","Liechtenstein","Principality of Liechtenstein","Independent State",,,"Vaduz","CHF","Franc","+423","LI","LIE","438",".li" 101 | "100","Lithuania","Republic of Lithuania","Independent State",,,"Vilnius","LTL","Litas","+370","LT","LTU","440",".lt" 102 | "101","Luxembourg","Grand Duchy of Luxembourg","Independent State",,,"Luxembourg","EUR","Euro","+352","LU","LUX","442",".lu" 103 | "102","Macedonia","Republic of Macedonia","Independent State",,,"Skopje","MKD","Denar","+389","MK","MKD","807",".mk" 104 | "103","Madagascar","Republic of Madagascar","Independent State",,,"Antananarivo","MGA","Ariary","+261","MG","MDG","450",".mg" 105 | "104","Malawi","Republic of Malawi","Independent State",,,"Lilongwe","MWK","Kwacha","+265","MW","MWI","454",".mw" 106 | "105","Malaysia",,"Independent State",,,"Kuala Lumpur (legislative/judical) and Putrajaya (administrative)","MYR","Ringgit","+60","MY","MYS","458",".my" 107 | "106","Maldives","Republic of Maldives","Independent State",,,"Male","MVR","Rufiyaa","+960","MV","MDV","462",".mv" 108 | "107","Mali","Republic of Mali","Independent State",,,"Bamako","XOF","Franc","+223","ML","MLI","466",".ml" 109 | "108","Malta","Republic of Malta","Independent State",,,"Valletta","MTL","Lira","+356","MT","MLT","470",".mt" 110 | "109","Marshall Islands","Republic of the Marshall Islands","Independent State",,,"Majuro","USD","Dollar","+692","MH","MHL","584",".mh" 111 | "110","Mauritania","Islamic Republic of Mauritania","Independent State",,,"Nouakchott","MRO","Ouguiya","+222","MR","MRT","478",".mr" 112 | "111","Mauritius","Republic of Mauritius","Independent State",,,"Port Louis","MUR","Rupee","+230","MU","MUS","480",".mu" 113 | "112","Mexico","United Mexican States","Independent State",,,"Mexico","MXN","Peso","+52","MX","MEX","484",".mx" 114 | "113","Micronesia","Federated States of Micronesia","Independent State",,,"Palikir","USD","Dollar","+691","FM","FSM","583",".fm" 115 | "114","Moldova","Republic of Moldova","Independent State",,,"Chisinau","MDL","Leu","+373","MD","MDA","498",".md" 116 | "115","Monaco","Principality of Monaco","Independent State",,,"Monaco","EUR","Euro","+377","MC","MCO","492",".mc" 117 | "116","Mongolia",,"Independent State",,,"Ulaanbaatar","MNT","Tugrik","+976","MN","MNG","496",".mn" 118 | "117","Montenegro","Republic of Montenegro","Independent State",,,"Podgorica","EUR","Euro","+382","ME","MNE","499",".me and .yu" 119 | "118","Morocco","Kingdom of Morocco","Independent State",,,"Rabat","MAD","Dirham","+212","MA","MAR","504",".ma" 120 | "119","Mozambique","Republic of Mozambique","Independent State",,,"Maputo","MZM","Meticail","+258","MZ","MOZ","508",".mz" 121 | "120","Myanmar (Burma)","Union of Myanmar","Independent State",,,"Naypyidaw","MMK","Kyat","+95","MM","MMR","104",".mm" 122 | "121","Namibia","Republic of Namibia","Independent State",,,"Windhoek","NAD","Dollar","+264","NA","NAM","516",".na" 123 | "122","Nauru","Republic of Nauru","Independent State",,,"Yaren","AUD","Dollar","+674","NR","NRU","520",".nr" 124 | "123","Nepal",,"Independent State",,,"Kathmandu","NPR","Rupee","+977","NP","NPL","524",".np" 125 | "124","Netherlands","Kingdom of the Netherlands","Independent State",,,"Amsterdam (administrative) and The Hague (legislative/judical)","EUR","Euro","+31","NL","NLD","528",".nl" 126 | "125","New Zealand",,"Independent State",,,"Wellington","NZD","Dollar","+64","NZ","NZL","554",".nz" 127 | "126","Nicaragua","Republic of Nicaragua","Independent State",,,"Managua","NIO","Cordoba","+505","NI","NIC","558",".ni" 128 | "127","Niger","Republic of Niger","Independent State",,,"Niamey","XOF","Franc","+227","NE","NER","562",".ne" 129 | "128","Nigeria","Federal Republic of Nigeria","Independent State",,,"Abuja","NGN","Naira","+234","NG","NGA","566",".ng" 130 | "129","Norway","Kingdom of Norway","Independent State",,,"Oslo","NOK","Krone","+47","NO","NOR","578",".no" 131 | "130","Oman","Sultanate of Oman","Independent State",,,"Muscat","OMR","Rial","+968","OM","OMN","512",".om" 132 | "131","Pakistan","Islamic Republic of Pakistan","Independent State",,,"Islamabad","PKR","Rupee","+92","PK","PAK","586",".pk" 133 | "132","Palau","Republic of Palau","Independent State",,,"Melekeok","USD","Dollar","+680","PW","PLW","585",".pw" 134 | "133","Panama","Republic of Panama","Independent State",,,"Panama","PAB","Balboa","+507","PA","PAN","591",".pa" 135 | "134","Papua New Guinea","Independent State of Papua New Guinea","Independent State",,,"Port Moresby","PGK","Kina","+675","PG","PNG","598",".pg" 136 | "135","Paraguay","Republic of Paraguay","Independent State",,,"Asuncion","PYG","Guarani","+595","PY","PRY","600",".py" 137 | "136","Peru","Republic of Peru","Independent State",,,"Lima","PEN","Sol","+51","PE","PER","604",".pe" 138 | "137","Philippines","Republic of the Philippines","Independent State",,,"Manila","PHP","Peso","+63","PH","PHL","608",".ph" 139 | "138","Poland","Republic of Poland","Independent State",,,"Warsaw","PLN","Zloty","+48","PL","POL","616",".pl" 140 | "139","Portugal","Portuguese Republic","Independent State",,,"Lisbon","EUR","Euro","+351","PT","PRT","620",".pt" 141 | "140","Qatar","State of Qatar","Independent State",,,"Doha","QAR","Rial","+974","QA","QAT","634",".qa" 142 | "141","Romania",,"Independent State",,,"Bucharest","RON","Leu","+40","RO","ROU","642",".ro" 143 | "142","Russia","Russian Federation","Independent State",,,"Moscow","RUB","Ruble","+7","RU","RUS","643",".ru and .su" 144 | "143","Rwanda","Republic of Rwanda","Independent State",,,"Kigali","RWF","Franc","+250","RW","RWA","646",".rw" 145 | "144","Saint Kitts and Nevis","Federation of Saint Kitts and Nevis","Independent State",,,"Basseterre","XCD","Dollar","+1-869","KN","KNA","659",".kn" 146 | "145","Saint Lucia",,"Independent State",,,"Castries","XCD","Dollar","+1-758","LC","LCA","662",".lc" 147 | "146","Saint Vincent and the Grenadines",,"Independent State",,,"Kingstown","XCD","Dollar","+1-784","VC","VCT","670",".vc" 148 | "147","Samoa","Independent State of Samoa","Independent State",,,"Apia","WST","Tala","+685","WS","WSM","882",".ws" 149 | "148","San Marino","Republic of San Marino","Independent State",,,"San Marino","EUR","Euro","+378","SM","SMR","674",".sm" 150 | "149","Sao Tome and Principe","Democratic Republic of Sao Tome and Principe","Independent State",,,"Sao Tome","STD","Dobra","+239","ST","STP","678",".st" 151 | "150","Saudi Arabia","Kingdom of Saudi Arabia","Independent State",,,"Riyadh","SAR","Rial","+966","SA","SAU","682",".sa" 152 | "151","Senegal","Republic of Senegal","Independent State",,,"Dakar","XOF","Franc","+221","SN","SEN","686",".sn" 153 | "152","Serbia","Republic of Serbia","Independent State",,,"Belgrade","RSD","Dinar","+381","RS","SRB","688",".rs and .yu" 154 | "153","Seychelles","Republic of Seychelles","Independent State",,,"Victoria","SCR","Rupee","+248","SC","SYC","690",".sc" 155 | "154","Sierra Leone","Republic of Sierra Leone","Independent State",,,"Freetown","SLL","Leone","+232","SL","SLE","694",".sl" 156 | "155","Singapore","Republic of Singapore","Independent State",,,"Singapore","SGD","Dollar","+65","SG","SGP","702",".sg" 157 | "156","Slovakia","Slovak Republic","Independent State",,,"Bratislava","SKK","Koruna","+421","SK","SVK","703",".sk" 158 | "157","Slovenia","Republic of Slovenia","Independent State",,,"Ljubljana","EUR","Euro","+386","SI","SVN","705",".si" 159 | "158","Solomon Islands",,"Independent State",,,"Honiara","SBD","Dollar","+677","SB","SLB","090",".sb" 160 | "159","Somalia",,"Independent State",,,"Mogadishu","SOS","Shilling","+252","SO","SOM","706",".so" 161 | "160","South Africa","Republic of South Africa","Independent State",,,"Pretoria (administrative), Cape Town (legislative), and Bloemfontein (judical)","ZAR","Rand","+27","ZA","ZAF","710",".za" 162 | "161","Spain","Kingdom of Spain","Independent State",,,"Madrid","EUR","Euro","+34","ES","ESP","724",".es" 163 | "162","Sri Lanka","Democratic Socialist Republic of Sri Lanka","Independent State",,,"Colombo (administrative/judical) and Sri Jayewardenepura Kotte (legislative)","LKR","Rupee","+94","LK","LKA","144",".lk" 164 | "163","Sudan","Republic of the Sudan","Independent State",,,"Khartoum","SDG","Pound","+249","SD","SDN","736",".sd" 165 | "164","Suriname","Republic of Suriname","Independent State",,,"Paramaribo","SRD","Dollar","+597","SR","SUR","740",".sr" 166 | "165","Swaziland","Kingdom of Swaziland","Independent State",,,"Mbabane (administrative) and Lobamba (legislative)","SZL","Lilangeni","+268","SZ","SWZ","748",".sz" 167 | "166","Sweden","Kingdom of Sweden","Independent State",,,"Stockholm","SEK","Kronoa","+46","SE","SWE","752",".se" 168 | "167","Switzerland","Swiss Confederation","Independent State",,,"Bern","CHF","Franc","+41","CH","CHE","756",".ch" 169 | "168","Syria","Syrian Arab Republic","Independent State",,,"Damascus","SYP","Pound","+963","SY","SYR","760",".sy" 170 | "169","Tajikistan","Republic of Tajikistan","Independent State",,,"Dushanbe","TJS","Somoni","+992","TJ","TJK","762",".tj" 171 | "170","Tanzania","United Republic of Tanzania","Independent State",,,"Dar es Salaam (administrative/judical) and Dodoma (legislative)","TZS","Shilling","+255","TZ","TZA","834",".tz" 172 | "171","Thailand","Kingdom of Thailand","Independent State",,,"Bangkok","THB","Baht","+66","TH","THA","764",".th" 173 | "172","Timor-Leste (East Timor)","Democratic Republic of Timor-Leste","Independent State",,,"Dili","USD","Dollar","+670","TL","TLS","626",".tp and .tl" 174 | "173","Togo","Togolese Republic","Independent State",,,"Lome","XOF","Franc","+228","TG","TGO","768",".tg" 175 | "174","Tonga","Kingdom of Tonga","Independent State",,,"Nuku'alofa","TOP","Pa'anga","+676","TO","TON","776",".to" 176 | "175","Trinidad and Tobago","Republic of Trinidad and Tobago","Independent State",,,"Port-of-Spain","TTD","Dollar","+1-868","TT","TTO","780",".tt" 177 | "176","Tunisia","Tunisian Republic","Independent State",,,"Tunis","TND","Dinar","+216","TN","TUN","788",".tn" 178 | "177","Turkey","Republic of Turkey","Independent State",,,"Ankara","TRY","Lira","+90","TR","TUR","792",".tr" 179 | "178","Turkmenistan",,"Independent State",,,"Ashgabat","TMM","Manat","+993","TM","TKM","795",".tm" 180 | "179","Tuvalu",,"Independent State",,,"Funafuti","AUD","Dollar","+688","TV","TUV","798",".tv" 181 | "180","Uganda","Republic of Uganda","Independent State",,,"Kampala","UGX","Shilling","+256","UG","UGA","800",".ug" 182 | "181","Ukraine",,"Independent State",,,"Kiev","UAH","Hryvnia","+380","UA","UKR","804",".ua" 183 | "182","United Arab Emirates","United Arab Emirates","Independent State",,,"Abu Dhabi","AED","Dirham","+971","AE","ARE","784",".ae" 184 | "183","United Kingdom","United Kingdom of Great Britain and Northern Ireland","Independent State",,,"London","GBP","Pound","+44","GB","GBR","826",".uk" 185 | "184","United States","United States of America","Independent State",,,"Washington","USD","Dollar","+1","US","USA","840",".us" 186 | "185","Uruguay","Oriental Republic of Uruguay","Independent State",,,"Montevideo","UYU","Peso","+598","UY","URY","858",".uy" 187 | "186","Uzbekistan","Republic of Uzbekistan","Independent State",,,"Tashkent","UZS","Som","+998","UZ","UZB","860",".uz" 188 | "187","Vanuatu","Republic of Vanuatu","Independent State",,,"Port-Vila","VUV","Vatu","+678","VU","VUT","548",".vu" 189 | "188","Vatican City","State of the Vatican City","Independent State",,,"Vatican City","EUR","Euro","+379","VA","VAT","336",".va" 190 | "189","Venezuela","Bolivarian Republic of Venezuela","Independent State",,,"Caracas","VEB","Bolivar","+58","VE","VEN","862",".ve" 191 | "190","Vietnam","Socialist Republic of Vietnam","Independent State",,,"Hanoi","VND","Dong","+84","VN","VNM","704",".vn" 192 | "191","Yemen","Republic of Yemen","Independent State",,,"Sanaa","YER","Rial","+967","YE","YEM","887",".ye" 193 | "192","Zambia","Republic of Zambia","Independent State",,,"Lusaka","ZMK","Kwacha","+260","ZM","ZMB","894",".zm" 194 | "193","Zimbabwe","Republic of Zimbabwe","Independent State",,,"Harare","ZWD","Dollar","+263","ZW","ZWE","716",".zw" 195 | "194","Abkhazia","Republic of Abkhazia","Proto Independent State",,,"Sokhumi","RUB","Ruble","+995","GE","GEO","268",".ge" 196 | "195","China, Republic of (Taiwan)","Republic of China","Proto Independent State",,,"Taipei","TWD","Dollar","+886","TW","TWN","158",".tw" 197 | "196","Nagorno-Karabakh","Nagorno-Karabakh Republic","Proto Independent State",,,"Stepanakert","AMD","Dram","+374-97","AZ","AZE","031",".az" 198 | "197","Northern Cyprus","Turkish Republic of Northern Cyprus","Proto Independent State",,,"Nicosia","TRY","Lira","+90-392","CY","CYP","196",".nc.tr" 199 | "198","Pridnestrovie (Transnistria)","Pridnestrovian Moldavian Republic","Proto Independent State",,,"Tiraspol",,"Ruple","+373-533","MD","MDA","498",".md" 200 | "199","Somaliland","Republic of Somaliland","Proto Independent State",,,"Hargeisa",,"Shilling","+252","SO","SOM","706",".so" 201 | "200","South Ossetia","Republic of South Ossetia","Proto Independent State",,,"Tskhinvali","RUB and GEL","Ruble and Lari","+995","GE","GEO","268",".ge" 202 | "201","Ashmore and Cartier Islands","Territory of Ashmore and Cartier Islands","Dependency","External Territory","Australia",,,,,"AU","AUS","036",".au" 203 | "202","Christmas Island","Territory of Christmas Island","Dependency","External Territory","Australia","The Settlement (Flying Fish Cove)","AUD","Dollar","+61","CX","CXR","162",".cx" 204 | "203","Cocos (Keeling) Islands","Territory of Cocos (Keeling) Islands","Dependency","External Territory","Australia","West Island","AUD","Dollar","+61","CC","CCK","166",".cc" 205 | "204","Coral Sea Islands","Coral Sea Islands Territory","Dependency","External Territory","Australia",,,,,"AU","AUS","036",".au" 206 | "205","Heard Island and McDonald Islands","Territory of Heard Island and McDonald Islands","Dependency","External Territory","Australia",,,,,"HM","HMD","334",".hm" 207 | "206","Norfolk Island","Territory of Norfolk Island","Dependency","External Territory","Australia","Kingston","AUD","Dollar","+672","NF","NFK","574",".nf" 208 | "207","New Caledonia",,"Dependency","Sui generis Collectivity","France","Noumea","XPF","Franc","+687","NC","NCL","540",".nc" 209 | "208","French Polynesia","Overseas Country of French Polynesia","Dependency","Overseas Collectivity","France","Papeete","XPF","Franc","+689","PF","PYF","258",".pf" 210 | "209","Mayotte","Departmental Collectivity of Mayotte","Dependency","Overseas Collectivity","France","Mamoudzou","EUR","Euro","+262","YT","MYT","175",".yt" 211 | "210","Saint Barthelemy","Collectivity of Saint Barthelemy","Dependency","Overseas Collectivity","France","Gustavia","EUR","Euro","+590","GP","GLP","312",".gp" 212 | "211","Saint Martin","Collectivity of Saint Martin","Dependency","Overseas Collectivity","France","Marigot","EUR","Euro","+590","GP","GLP","312",".gp" 213 | "212","Saint Pierre and Miquelon","Territorial Collectivity of Saint Pierre and Miquelon","Dependency","Overseas Collectivity","France","Saint-Pierre","EUR","Euro","+508","PM","SPM","666",".pm" 214 | "213","Wallis and Futuna","Collectivity of the Wallis and Futuna Islands","Dependency","Overseas Collectivity","France","Mata'utu","XPF","Franc","+681","WF","WLF","876",".wf" 215 | "214","French Southern and Antarctic Lands","Territory of the French Southern and Antarctic Lands","Dependency","Overseas Territory","France","Martin-de-Vivies",,,,"TF","ATF","260",".tf" 216 | "215","Clipperton Island",,"Dependency","Possession","France",,,,,"PF","PYF","258",".pf" 217 | "216","Bouvet Island",,"Dependency","Territory","Norway",,,,,"BV","BVT","074",".bv" 218 | "217","Cook Islands",,"Dependency","Self-Governing in Free Association","New Zealand","Avarua","NZD","Dollar","+682","CK","COK","184",".ck" 219 | "218","Niue",,"Dependency","Self-Governing in Free Association","New Zealand","Alofi","NZD","Dollar","+683","NU","NIU","570",".nu" 220 | "219","Tokelau",,"Dependency","Territory","New Zealand",,"NZD","Dollar","+690","TK","TKL","772",".tk" 221 | "220","Guernsey","Bailiwick of Guernsey","Dependency","Crown Dependency","United Kingdom","Saint Peter Port","GGP","Pound","+44","GG","GGY","831",".gg" 222 | "221","Isle of Man",,"Dependency","Crown Dependency","United Kingdom","Douglas","IMP","Pound","+44","IM","IMN","833",".im" 223 | "222","Jersey","Bailiwick of Jersey","Dependency","Crown Dependency","United Kingdom","Saint Helier","JEP","Pound","+44","JE","JEY","832",".je" 224 | "223","Anguilla",,"Dependency","Overseas Territory","United Kingdom","The Valley","XCD","Dollar","+1-264","AI","AIA","660",".ai" 225 | "224","Bermuda",,"Dependency","Overseas Territory","United Kingdom","Hamilton","BMD","Dollar","+1-441","BM","BMU","060",".bm" 226 | "225","British Indian Ocean Territory",,"Dependency","Overseas Territory","United Kingdom",,,,"+246","IO","IOT","086",".io" 227 | "226","British Sovereign Base Areas",,"Dependency","Overseas Territory","United Kingdom","Episkopi","CYP","Pound","+357",,,, 228 | "227","British Virgin Islands",,"Dependency","Overseas Territory","United Kingdom","Road Town","USD","Dollar","+1-284","VG","VGB","092",".vg" 229 | "228","Cayman Islands",,"Dependency","Overseas Territory","United Kingdom","George Town","KYD","Dollar","+1-345","KY","CYM","136",".ky" 230 | "229","Falkland Islands (Islas Malvinas)",,"Dependency","Overseas Territory","United Kingdom","Stanley","FKP","Pound","+500","FK","FLK","238",".fk" 231 | "230","Gibraltar",,"Dependency","Overseas Territory","United Kingdom","Gibraltar","GIP","Pound","+350","GI","GIB","292",".gi" 232 | "231","Montserrat",,"Dependency","Overseas Territory","United Kingdom","Plymouth","XCD","Dollar","+1-664","MS","MSR","500",".ms" 233 | "232","Pitcairn Islands",,"Dependency","Overseas Territory","United Kingdom","Adamstown","NZD","Dollar",,"PN","PCN","612",".pn" 234 | "233","Saint Helena",,"Dependency","Overseas Territory","United Kingdom","Jamestown","SHP","Pound","+290","SH","SHN","654",".sh" 235 | "234","South Georgia and the South Sandwich Islands",,"Dependency","Overseas Territory","United Kingdom",,,,,"GS","SGS","239",".gs" 236 | "235","Turks and Caicos Islands",,"Dependency","Overseas Territory","United Kingdom","Grand Turk","USD","Dollar","+1-649","TC","TCA","796",".tc" 237 | "236","Northern Mariana Islands","Commonwealth of The Northern Mariana Islands","Dependency","Commonwealth","United States","Saipan","USD","Dollar","+1-670","MP","MNP","580",".mp" 238 | "237","Puerto Rico","Commonwealth of Puerto Rico","Dependency","Commonwealth","United States","San Juan","USD","Dollar","+1-787 and 1-939","PR","PRI","630",".pr" 239 | "238","American Samoa","Territory of American Samoa","Dependency","Territory","United States","Pago Pago","USD","Dollar","+1-684","AS","ASM","016",".as" 240 | "239","Baker Island",,"Dependency","Territory","United States",,,,,"UM","UMI","581", 241 | "240","Guam","Territory of Guam","Dependency","Territory","United States","Hagatna","USD","Dollar","+1-671","GU","GUM","316",".gu" 242 | "241","Howland Island",,"Dependency","Territory","United States",,,,,"UM","UMI","581", 243 | "242","Jarvis Island",,"Dependency","Territory","United States",,,,,"UM","UMI","581", 244 | "243","Johnston Atoll",,"Dependency","Territory","United States",,,,,"UM","UMI","581", 245 | "244","Kingman Reef",,"Dependency","Territory","United States",,,,,"UM","UMI","581", 246 | "245","Midway Islands",,"Dependency","Territory","United States",,,,,"UM","UMI","581", 247 | "246","Navassa Island",,"Dependency","Territory","United States",,,,,"UM","UMI","581", 248 | "247","Palmyra Atoll",,"Dependency","Territory","United States",,,,,"UM","UMI","581", 249 | "248","U.S. Virgin Islands","United States Virgin Islands","Dependency","Territory","United States","Charlotte Amalie","USD","Dollar","+1-340","VI","VIR","850",".vi" 250 | "249","Wake Island",,"Dependency","Territory","United States",,,,,"UM","UMI","850", 251 | "250","Hong Kong","Hong Kong Special Administrative Region","Proto Dependency","Special Administrative Region","China",,"HKD","Dollar","+852","HK","HKG","344",".hk" 252 | "251","Macau","Macau Special Administrative Region","Proto Dependency","Special Administrative Region","China","Macau","MOP","Pataca","+853","MO","MAC","446",".mo" 253 | "252","Faroe Islands",,"Proto Dependency",,"Denmark","Torshavn","DKK","Krone","+298","FO","FRO","234",".fo" 254 | "253","Greenland",,"Proto Dependency",,"Denmark","Nuuk (Godthab)","DKK","Krone","+299","GL","GRL","304",".gl" 255 | "254","French Guiana","Overseas Region of Guiana","Proto Dependency","Overseas Region","France","Cayenne","EUR","Euro","+594","GF","GUF","254",".gf" 256 | "255","Guadeloupe","Overseas Region of Guadeloupe","Proto Dependency","Overseas Region","France","Basse-Terre","EUR","Euro","+590","GP","GLP","312",".gp" 257 | "256","Martinique","Overseas Region of Martinique","Proto Dependency","Overseas Region","France","Fort-de-France","EUR","Euro","+596","MQ","MTQ","474",".mq" 258 | "257","Reunion","Overseas Region of Reunion","Proto Dependency","Overseas Region","France","Saint-Denis","EUR","Euro","+262","RE","REU","638",".re" 259 | "258","Aland",,"Proto Dependency",,"Finland","Mariehamn","EUR","Euro","+358-18","AX","ALA","248",".ax" 260 | "259","Aruba",,"Proto Dependency",,"Netherlands","Oranjestad","AWG","Guilder","+297","AW","ABW","533",".aw" 261 | "260","Netherlands Antilles",,"Proto Dependency",,"Netherlands","Willemstad","ANG","Guilder","+599","AN","ANT","530",".an" 262 | "261","Svalbard",,"Proto Dependency",,"Norway","Longyearbyen","NOK","Krone","+47","SJ","SJM","744",".sj" 263 | "262","Ascension",,"Proto Dependency","Dependency of Saint Helena","United Kingdom","Georgetown","SHP","Pound","+247","AC","ASC",,".ac" 264 | "263","Tristan da Cunha",,"Proto Dependency","Dependency of Saint Helena","United Kingdom","Edinburgh","SHP","Pound","+290","TA","TAA",, 265 | "264","Antarctica",,"Disputed Territory",,"Undetermined",,,,,"AQ","ATA","010",".aq" 266 | "265","Kosovo",,"Disputed Territory",,"Administrated by the UN","Pristina","CSD and EUR","Dinar and Euro","+381","CS","SCG","891",".cs and .yu" 267 | "266","Palestinian Territories (Gaza Strip and West Bank)",,"Disputed Territory",,"Administrated by Israel","Gaza City (Gaza Strip) and Ramallah (West Bank)","ILS","Shekel","+970","PS","PSE","275",".ps" 268 | "267","Western Sahara",,"Disputed Territory",,"Administrated by Morocco","El-Aaiun","MAD","Dirham","+212","EH","ESH","732",".eh" 269 | "268","Australian Antarctic Territory",,"Antarctic Territory","External Territory","Australia",,,,,"AQ","ATA","010",".aq" 270 | "269","Ross Dependency",,"Antarctic Territory","Territory","New Zealand",,,,,"AQ","ATA","010",".aq" 271 | "270","Peter I Island",,"Antarctic Territory","Territory","Norway",,,,,"AQ","ATA","010",".aq" 272 | "271","Queen Maud Land",,"Antarctic Territory","Territory","Norway",,,,,"AQ","ATA","010",".aq" 273 | "272","British Antarctic Territory",,"Antarctic Territory","Overseas Territory","United Kingdom",,,,,"AQ","ATA","010",".aq" 274 | -------------------------------------------------------------------------------- /tests/date1.exp: -------------------------------------------------------------------------------- 1 | "date(1977, 2, 17, 0, 0, 0, 0)","date(1977, 2, 17, 0, 0, 0, 0)","date(1977, 2, 17, 0, 0, 0, 0)" 2 | "date(1978, 9, 24, 0, 0, 0, 0)","date(1978, 9, 24, 0, 0, 0, 0)","date(1978, 9, 24, 0, 0, 0, 0)" 3 | -------------------------------------------------------------------------------- /tests/date1.inp: -------------------------------------------------------------------------------- 1 | 1977-02-17,17.02.1977,"02,17,1977" 2 | "1978-09-24","24.09.1978","09,24,1978" 3 | -------------------------------------------------------------------------------- /tests/date2.exp: -------------------------------------------------------------------------------- 1 | "date(2013, 1, 1, 0, 0, 0, 0)","date(2013, 1, 1, 0, 0, 0, 0)","date(2013, 1, 1, 0, 0, 0, 0)" 2 | "date(2013, 2, 1, 0, 0, 0, 0)","date(2013, 2, 1, 0, 0, 0, 0)","date(2013, 2, 1, 0, 0, 0, 0)" 3 | "date(2013, 3, 1, 0, 0, 0, 0)","date(2013, 3, 1, 0, 0, 0, 0)","date(2013, 3, 1, 0, 0, 0, 0)" 4 | "date(2013, 4, 1, 0, 0, 0, 0)","date(2013, 4, 1, 0, 0, 0, 0)","date(2013, 4, 1, 0, 0, 0, 0)" 5 | "date(2013, 5, 1, 0, 0, 0, 0)","date(2013, 5, 1, 0, 0, 0, 0)","date(2013, 5, 1, 0, 0, 0, 0)" 6 | "date(2013, 6, 1, 0, 0, 0, 0)","date(2013, 6, 1, 0, 0, 0, 0)","date(2013, 6, 1, 0, 0, 0, 0)" 7 | "date(2013, 6, 1, 0, 0, 0, 0)","date(2013, 6, 1, 0, 0, 0, 0)","date(2013, 6, 1, 0, 0, 0, 0)" 8 | "date(2013, 7, 1, 0, 0, 0, 0)","date(2013, 7, 1, 0, 0, 0, 0)","date(2013, 7, 1, 0, 0, 0, 0)" 9 | "date(2013, 7, 1, 0, 0, 0, 0)","date(2013, 7, 1, 0, 0, 0, 0)","date(2013, 7, 1, 0, 0, 0, 0)" 10 | "date(2013, 8, 1, 0, 0, 0, 0)","date(2013, 8, 1, 0, 0, 0, 0)","date(2013, 8, 1, 0, 0, 0, 0)" 11 | "date(2013, 9, 1, 0, 0, 0, 0)","date(2013, 9, 1, 0, 0, 0, 0)","date(2013, 9, 1, 0, 0, 0, 0)" 12 | "date(2013, 9, 1, 0, 0, 0, 0)","date(2013, 9, 1, 0, 0, 0, 0)","date(2013, 9, 1, 0, 0, 0, 0)" 13 | "date(2013, 10, 1, 0, 0, 0, 0)","date(2013, 10, 1, 0, 0, 0, 0)","date(2013, 10, 1, 0, 0, 0, 0)" 14 | "date(2013, 11, 1, 0, 0, 0, 0)","date(2013, 11, 1, 0, 0, 0, 0)","date(2013, 11, 1, 0, 0, 0, 0)" 15 | "date(2013, 12, 1, 0, 0, 0, 0)","date(2013, 12, 1, 0, 0, 0, 0)","date(2013, 12, 1, 0, 0, 0, 0)" 16 | "date(2014, 1, 1, 0, 0, 0, 0)","date(2014, 1, 1, 0, 0, 0, 0)","date(2014, 1, 1, 0, 0, 0, 0)" 17 | "date(2014, 2, 1, 0, 0, 0, 0)","date(2014, 2, 1, 0, 0, 0, 0)","date(2014, 2, 1, 0, 0, 0, 0)" 18 | "date(2014, 3, 1, 0, 0, 0, 0)","date(2014, 3, 1, 0, 0, 0, 0)","date(2014, 3, 1, 0, 0, 0, 0)" 19 | "date(2014, 4, 1, 0, 0, 0, 0)","date(2014, 4, 1, 0, 0, 0, 0)","date(2014, 4, 1, 0, 0, 0, 0)" 20 | "date(2014, 5, 1, 0, 0, 0, 0)","date(2014, 5, 1, 0, 0, 0, 0)","date(2014, 5, 1, 0, 0, 0, 0)" 21 | "date(2014, 6, 1, 0, 0, 0, 0)","date(2014, 6, 1, 0, 0, 0, 0)","date(2014, 6, 1, 0, 0, 0, 0)" 22 | "date(2014, 6, 1, 0, 0, 0, 0)","date(2014, 6, 1, 0, 0, 0, 0)","date(2014, 6, 1, 0, 0, 0, 0)" 23 | "date(2014, 7, 1, 0, 0, 0, 0)","date(2014, 7, 1, 0, 0, 0, 0)","date(2014, 7, 1, 0, 0, 0, 0)" 24 | "date(2014, 7, 1, 0, 0, 0, 0)","date(2014, 7, 1, 0, 0, 0, 0)","date(2014, 7, 1, 0, 0, 0, 0)" 25 | "date(2014, 8, 1, 0, 0, 0, 0)","date(2014, 8, 1, 0, 0, 0, 0)","date(2014, 8, 1, 0, 0, 0, 0)" 26 | "date(2014, 9, 1, 0, 0, 0, 0)","date(2014, 9, 1, 0, 0, 0, 0)","date(2014, 9, 1, 0, 0, 0, 0)" 27 | "date(2014, 9, 1, 0, 0, 0, 0)","date(2014, 9, 1, 0, 0, 0, 0)","date(2014, 9, 1, 0, 0, 0, 0)" 28 | "date(2014, 10, 1, 0, 0, 0, 0)","date(2014, 10, 1, 0, 0, 0, 0)","date(2014, 10, 1, 0, 0, 0, 0)" 29 | "date(2014, 11, 1, 0, 0, 0, 0)","date(2014, 11, 1, 0, 0, 0, 0)","date(2014, 11, 1, 0, 0, 0, 0)" 30 | "date(2014, 12, 1, 0, 0, 0, 0)","date(2014, 12, 1, 0, 0, 0, 0)","date(2014, 12, 1, 0, 0, 0, 0)" 31 | -------------------------------------------------------------------------------- /tests/date2.inp: -------------------------------------------------------------------------------- 1 | 2013-Jan-1,Jan-1-2013,1-Jan-2013 2 | 2013-Feb-1,Feb-1-2013,1-Feb-2013 3 | 2013-Mar-1,Mar-1-2013,1-Mar-2013 4 | 2013-Apr-1,Apr-1-2013,1-Apr-2013 5 | 2013-May-1,May-1-2013,1-May-2013 6 | 2013-Jun-1,Jun-1-2013,1-Jun-2013 7 | 2013-June-1,June-1-2013,1-June-2013 8 | 2013-Jul-1,Jul-1-2013,1-Jul-2013 9 | 2013-July-1,July-1-2013,1-July-2013 10 | 2013-Aug-1,Aug-1-2013,1-Aug-2013 11 | 2013-Sep-1,Sep-1-2013,1-Sep-2013 12 | 2013-Sept-1,Sept-1-2013,1-Sept-2013 13 | 2013-Oct-1,Oct-1-2013,1-Oct-2013 14 | 2013-Nov-1,Nov-1-2013,1-Nov-2013 15 | 2013-Dec-1,Dec-1-2013,1-Dec-2013 16 | 2014-jan-1,jan-1-2014,1-jan-2014 17 | 2014-feb-1,feb-1-2014,1-feb-2014 18 | 2014-mar-1,mar-1-2014,1-mar-2014 19 | 2014-apr-1,apr-1-2014,1-apr-2014 20 | 2014-may-1,may-1-2014,1-may-2014 21 | 2014-jun-1,jun-1-2014,1-jun-2014 22 | 2014-june-1,june-1-2014,1-june-2014 23 | 2014-jul-1,jul-1-2014,1-jul-2014 24 | 2014-july-1,july-1-2014,1-july-2014 25 | 2014-aug-1,aug-1-2014,1-aug-2014 26 | 2014-sep-1,sep-1-2014,1-sep-2014 27 | 2014-sept-1,sept-1-2014,1-sept-2014 28 | 2014-oct-1,oct-1-2014,1-oct-2014 29 | 2014-nov-1,nov-1-2014,1-nov-2014 30 | 2014-dec-1,dec-1-2014,1-dec-2014 31 | -------------------------------------------------------------------------------- /tests/date_time.exp: -------------------------------------------------------------------------------- 1 | "date(2010, 3, 24, 12, 31, 0, 0)","date(1977, 12, 25, 13, 5, 0, 0)" 2 | "date(1980, 4, 29, 19, 17, 0, 0)","date(2001, 1, 1, 1, 15, 0, 0)" 3 | -------------------------------------------------------------------------------- /tests/date_time.inp: -------------------------------------------------------------------------------- 1 | "03-24-2010 12:31","12-25-1977 13:05" 2 | "04-29-1980 19:17","01-01-2001 1:15" 3 | -------------------------------------------------------------------------------- /tests/date_time2.exp: -------------------------------------------------------------------------------- 1 | "date(2014, 7, 11, 0, 1, 0, 0)","100" 2 | "date(2014, 7, 11, 0, 4, 0, 0)","200" 3 | "date(2014, 7, 12, 0, 5, 0, 0)","300" 4 | "date(2014, 7, 13, 23, 1, 0, 0)","400" 5 | -------------------------------------------------------------------------------- /tests/date_time2.inp: -------------------------------------------------------------------------------- 1 | 11/07/2014 00:01,100 2 | 11/07/2014 00:04,200 3 | "12/07/2014 00:05",300 4 | "13/07/2014 23:01","400" 5 | -------------------------------------------------------------------------------- /tests/discard-bad.err_exp: -------------------------------------------------------------------------------- 1 | discard-bad.inp:1:5: error: in field #2, field width exceeded 2 | -------------------------------------------------------------------------------- /tests/discard-bad.inp: -------------------------------------------------------------------------------- 1 | aaa,bbbbbb,ccc 2 | -------------------------------------------------------------------------------- /tests/discard.exp: -------------------------------------------------------------------------------- 1 | "aaa","1" 2 | "ccc","2" 3 | "eee","3" 4 | -------------------------------------------------------------------------------- /tests/discard.inp: -------------------------------------------------------------------------------- 1 | aaa,bbb,1 2 | ccc,ddd,2 3 | eee,fff,3 4 | -------------------------------------------------------------------------------- /tests/field_desc_mismatch.err_exp: -------------------------------------------------------------------------------- 1 | field_desc_mismatch.inp:1:1: error: in field #2, expected 2 fields in record: actual: 1 2 | -------------------------------------------------------------------------------- /tests/field_desc_mismatch.inp: -------------------------------------------------------------------------------- 1 | 1 2 | -------------------------------------------------------------------------------- /tests/field_limit.err_exp: -------------------------------------------------------------------------------- 1 | field_limit.inp:1:13: error: in field #4, record field limit exceeded 2 | -------------------------------------------------------------------------------- /tests/field_limit.inp: -------------------------------------------------------------------------------- 1 | aaa,bbb,ccc,ddd 2 | -------------------------------------------------------------------------------- /tests/harness.m: -------------------------------------------------------------------------------- 1 | %-----------------------------------------------------------------------------% 2 | % vim: ft=mercury ts=4 sw=4 et 3 | %-----------------------------------------------------------------------------% 4 | 5 | :- module harness. 6 | :- interface. 7 | 8 | :- import_module char. 9 | :- import_module csv. 10 | :- import_module io. 11 | :- import_module stream. 12 | 13 | :- pred process_csv(csv.reader(Stream)::in, io.text_output_stream::in, 14 | csv.res(Error)::out, io::di, io::uo) is det 15 | <= ( 16 | stream.line_oriented(Stream, io), 17 | stream.unboxed_reader(Stream, char, io, Error) 18 | ). 19 | 20 | %-----------------------------------------------------------------------------% 21 | %-----------------------------------------------------------------------------% 22 | 23 | :- implementation. 24 | 25 | :- import_module bool. 26 | :- import_module list. 27 | :- import_module maybe. 28 | :- import_module require. 29 | :- import_module string. 30 | 31 | %-----------------------------------------------------------------------------% 32 | 33 | process_csv(Reader, OutFile, Result, !IO) :- 34 | ( if csv.has_header(Reader) then 35 | stream.get(Reader, HeaderResult, !IO), 36 | ( 37 | HeaderResult = ok(Header), 38 | write_header(OutFile, Header, !IO), 39 | process_records(Reader, OutFile, Result, !IO) 40 | ; 41 | HeaderResult = eof, 42 | stream.name(Reader, Name, !IO), 43 | stream.get_line(Reader, LineNo, !IO), 44 | HeaderError = csv_error( 45 | Name, 46 | LineNo, 47 | 1, 48 | 1, 49 | "unexpected EOF in header" 50 | ), 51 | Result = error(HeaderError) 52 | ; 53 | HeaderResult = error(HeaderError), 54 | Result = error(HeaderError) 55 | ) 56 | else 57 | process_records(Reader, OutFile, Result, !IO) 58 | ). 59 | 60 | :- pred process_records(csv.reader(Stream)::in, io.text_output_stream::in, 61 | csv.res(Error)::out, io::di, io::uo) is det 62 | <= ( 63 | stream.line_oriented(Stream, io), 64 | stream.unboxed_reader(Stream, char, io, Error) 65 | ). 66 | 67 | process_records(Reader, OutFile, Result, !IO) :- 68 | stream.input_stream_fold_state(Reader, write_record(OutFile), Result, !IO). 69 | 70 | %-----------------------------------------------------------------------------% 71 | 72 | :- pred write_header(io.text_output_stream::in, header::in, 73 | io::di, io::uo) is det. 74 | 75 | write_header(File, Header, !IO) :- 76 | Header = header(HeaderFields), 77 | io.write_list(File, HeaderFields, ",", write_quoted_string, !IO), 78 | io.nl(File, !IO). 79 | 80 | :- pred write_quoted_string(string::in, io::di, io::uo) is det. 81 | 82 | write_quoted_string(String, !IO) :- 83 | io.write_char(('"'), !IO), 84 | io.write_string(String, !IO), 85 | io.write_char(('"'), !IO). 86 | 87 | %-----------------------------------------------------------------------------% 88 | 89 | :- pred write_record(io.text_output_stream::in, record::in, 90 | io::di, io::uo) is det. 91 | 92 | write_record(File, Record, !IO) :- 93 | Record = record(_LineNo, Fields), 94 | io.write_list(File, Fields, ",", write_field_value, !IO), 95 | io.nl(File, !IO). 96 | 97 | :- pred write_field_value(field_value::in, io::di, io::uo) is det. 98 | 99 | write_field_value(Value, !IO) :- 100 | io.write_char(('"'), !IO), 101 | ( 102 | Value = bool(Bool), 103 | io.write(Bool, !IO) 104 | ; 105 | Value = int(Int), 106 | io.write_int(Int, !IO) 107 | ; 108 | Value = float(Float), 109 | io.write_float(Float, !IO) 110 | ; 111 | Value = floatstr(FloatStr), 112 | io.write_string(FloatStr, !IO) 113 | ; 114 | Value = string(String), 115 | io.write_string(String, !IO) 116 | ; 117 | Value = date(Date), 118 | io.write(Date, !IO) 119 | ; 120 | Value = date_time(DateTime), 121 | io.write(DateTime, !IO) 122 | ; 123 | Value = univ(Univ), 124 | io.write(Univ, !IO) 125 | ; 126 | Value = maybe_bool(MaybeBool), 127 | io.write(MaybeBool, !IO) 128 | ; 129 | Value = maybe_int(MaybeInt), 130 | io.write(MaybeInt, !IO) 131 | ; 132 | Value = maybe_float(MaybeFloat), 133 | io.write(MaybeFloat, !IO) 134 | ; 135 | Value = maybe_floatstr(MaybeFloatStr), 136 | io.write(MaybeFloatStr, !IO) 137 | ; 138 | Value = maybe_string(MaybeString), 139 | io.write(MaybeString, !IO) 140 | ; 141 | Value = maybe_date(MaybeDate), 142 | io.write(MaybeDate, !IO) 143 | ; 144 | Value = maybe_date_time(MaybeDateTime), 145 | io.write(MaybeDateTime, !IO) 146 | ; 147 | Value = maybe_univ(MaybeUniv), 148 | io.write(MaybeUniv, !IO) 149 | ), 150 | io.write_char(('"'), !IO). 151 | 152 | %-----------------------------------------------------------------------------% 153 | :- end_module harness. 154 | %-----------------------------------------------------------------------------% 155 | -------------------------------------------------------------------------------- /tests/header_eof.err_exp: -------------------------------------------------------------------------------- 1 | header_eof.inp:1:1: error: in field #2, expected 3 fields in header: actual 2 2 | -------------------------------------------------------------------------------- /tests/header_eof.inp: -------------------------------------------------------------------------------- 1 | field1,field2 2 | aaa,bbb,ccc 3 | -------------------------------------------------------------------------------- /tests/invalid_date1.err_exp: -------------------------------------------------------------------------------- 1 | invalid_date1.inp:1:1: error: in field #1, not a valid date 2 | -------------------------------------------------------------------------------- /tests/invalid_date1.inp: -------------------------------------------------------------------------------- 1 | 30/02/1977,30/02/1977 2 | -------------------------------------------------------------------------------- /tests/maybe.exp: -------------------------------------------------------------------------------- 1 | "no","no" 2 | "yes(2)","no" 3 | "no","yes(6)" 4 | "yes(3)","yes(4)" 5 | -------------------------------------------------------------------------------- /tests/maybe.inp: -------------------------------------------------------------------------------- 1 | , 2 | 2, 3 | ,6 4 | 3,4 5 | -------------------------------------------------------------------------------- /tests/qm_in_uqf1.exp: -------------------------------------------------------------------------------- 1 | "abc"","quoted""," "" 2 | -------------------------------------------------------------------------------- /tests/qm_in_uqf1.inp: -------------------------------------------------------------------------------- 1 | abc","quoted""", " 2 | -------------------------------------------------------------------------------- /tests/qm_in_uqf2.err_exp: -------------------------------------------------------------------------------- 1 | qm_in_uqf2.inp:1:1: error: in field #1, missing closing quote 2 | -------------------------------------------------------------------------------- /tests/qm_in_uqf2.inp: -------------------------------------------------------------------------------- 1 | ","quoted""", 2 | -------------------------------------------------------------------------------- /tests/real1.exp: -------------------------------------------------------------------------------- 1 | "Std Job","Calculated Labour Hours","Cost" 2 | "IC0001","3.0","200.0" 3 | "IC0002","6.0","200.0" 4 | "IC0003","3.0","200.0" 5 | "IC0004","40.0","200.0" 6 | "IC0005","6.0","200.0" 7 | "IC0006","2.0","200.0" 8 | "IC0007","3.0","200.0" 9 | "IC0008","6.0","200.0" 10 | "IC0009","8.0","200.0" 11 | "IC0010","4.0","200.0" 12 | "IC0011","1.0","200.0" 13 | "IE0001","12.0","200.0" 14 | "IE0002","3.0","200.0" 15 | "IE0003","8.0","200.0" 16 | "IE0004","7.0","200.0" 17 | "IE0005","19.0","200.0" 18 | "IE0006","8.0","200.0" 19 | "IE0007","4.0","200.0" 20 | "IE0008","8.0","200.0" 21 | "IE0009","8.0","200.0" 22 | "IE0010","8.0","200.0" 23 | "IE0011","41.0","200.0" 24 | "IE0012","3.0","200.0" 25 | "IE0013","8.0","200.0" 26 | "IE0014","5.0","200.0" 27 | "IE0015","13.0","200.0" 28 | "IE0016","2.0","200.0" 29 | "IE0017","1.0","200.0" 30 | "IE0018","2.0","200.0" 31 | "IE0019","2.0","200.0" 32 | "IE0020","2.0","200.0" 33 | "IE0021","0.5","200.0" 34 | "IE0022","1.0","200.0" 35 | "IE0023","16.0","200.0" 36 | "IE0024","2.0","200.0" 37 | "IE0025","1.0","200.0" 38 | "IE0026","300.0","200.0" 39 | "IE0027","2.0","200.0" 40 | "IE0028","1.0","200.0" 41 | "IE0029","1.0","200.0" 42 | "IE0030","1.0","200.0" 43 | "IE0031","3.0","200.0" 44 | "IE0032","1.0","200.0" 45 | "IE0033","0.5","200.0" 46 | "IE0034","1.0","200.0" 47 | "IE0035","1.0","200.0" 48 | "IE0036","1.0","200.0" 49 | "IE0037","1.0","200.0" 50 | "IE0038","16.0","200.0" 51 | "IE0039","8.0","200.0" 52 | "IE0040","8.0","200.0" 53 | "IE0041","1.0","200.0" 54 | "IE0042","1.0","200.0" 55 | "IE0043","1.0","200.0" 56 | "IE0044","1.0","200.0" 57 | "IE0045","4.0","200.0" 58 | "IE0046","1.0","200.0" 59 | "IE0047","4.0","200.0" 60 | "IE0048","1.0","200.0" 61 | "IE0049","3.0","200.0" 62 | "IE0050","16.0","200.0" 63 | "IM0001","2.0","200.0" 64 | "IM0002","5.0","200.0" 65 | "IM0003","40.0","200.0" 66 | "IM0004","8.0","200.0" 67 | "IM0005","0.5","200.0" 68 | "IM0006","5.0","200.0" 69 | "IM0007","1.0","200.0" 70 | "IM0008","16.0","200.0" 71 | "IM0009","4.0","200.0" 72 | "IM0010","2.0","200.0" 73 | "IM0011","169.0","200.0" 74 | "IM0012","150.0","200.0" 75 | "IM0013","250.0","200.0" 76 | "IM0014","0.8","200.0" 77 | "IM0015","0.8","200.0" 78 | "IM0016","16.0","200.0" 79 | "IM0017","0.25","200.0" 80 | "IM0018","6.0","200.0" 81 | "IM0019","4.0","200.0" 82 | "IM0020","10.0","200.0" 83 | "IM0021","4.0","200.0" 84 | "IM0022","1.0","200.0" 85 | "IM0023","1.0","200.0" 86 | "IM0024","1.0","200.0" 87 | "IM0025","1.0","200.0" 88 | "IM0026","2.0","200.0" 89 | "IM0027","4.0","200.0" 90 | "IM0028","4.0","200.0" 91 | "IM0029","8.0","200.0" 92 | "IM0030","42.0","200.0" 93 | "IM0031","0.5","200.0" 94 | "IM0032","1.0","200.0" 95 | "IM0033","1.0","200.0" 96 | "IM0034","8.0","200.0" 97 | "IM0035","4.0","200.0" 98 | "IM0036","4.0","200.0" 99 | "IM0037","4.0","200.0" 100 | "IM0038","4.0","200.0" 101 | "IM0039","3.0","200.0" 102 | "IM0040","2.0","200.0" 103 | "IM0041","0.3","200.0" 104 | "IM0042","30.0","200.0" 105 | "IM0043","0.3","200.0" 106 | "IP0001","1.0","200.0" 107 | "IP0002","16.0","200.0" 108 | "IP0003","16.0","200.0" 109 | "IP0004","8.0","200.0" 110 | "IP0005","4.0","200.0" 111 | "IP0006","16.0","200.0" 112 | "IP0007","12.0","200.0" 113 | "IP0008","6.0","200.0" 114 | "IP0009","1.0","200.0" 115 | "IP0010","1.0","200.0" 116 | "IP0011","8.0","200.0" 117 | "IP0012","4.0","200.0" 118 | "IP0013","1.0","200.0" 119 | "IP0014","2.0","200.0" 120 | "IP0015","2.0","200.0" 121 | "IP0016","8.0","200.0" 122 | "IP0017","2.0","200.0" 123 | "IP0018","1.0","200.0" 124 | "IP0019","3.0","200.0" 125 | "IP0020","4.0","200.0" 126 | "IP0021","3.0","200.0" 127 | "IP0022","3.0","200.0" 128 | "IP0023","3.0","200.0" 129 | "IP0024","0.1","200.0" 130 | "IP0025","2.0","200.0" 131 | "IP0026","1.0","200.0" 132 | "IP0027","1.0","200.0" 133 | "IP0028","3.0","200.0" 134 | "IP0029","2.0","200.0" 135 | "IP0030","0.15","200.0" 136 | "IP0032","0.3","200.0" 137 | "IP0033","0.3","200.0" 138 | "IP0034","0.3","200.0" 139 | "IP0035","0.3","200.0" 140 | "IP0036","0.3","200.0" 141 | "IP0037","0.3","200.0" 142 | "IP0038","0.3","200.0" 143 | "IP0039","2.0","200.0" 144 | "IP0040","1.0","200.0" 145 | "IT0001","1.0","200.0" 146 | "IT0002","4.0","200.0" 147 | "IT0003","1.0","200.0" 148 | "IT0004","4.0","200.0" 149 | "IT0005","8.0","200.0" 150 | "IT0006","4.0","200.0" 151 | "IT0007","2.0","200.0" 152 | "IT0008","4.0","200.0" 153 | "IT0009","1.0","200.0" 154 | "IT0010","1.0","200.0" 155 | "IT0011","1.0","200.0" 156 | "IT0012","2.0","200.0" 157 | "IT0013","1.0","200.0" 158 | -------------------------------------------------------------------------------- /tests/real1.inp: -------------------------------------------------------------------------------- 1 | Std Job,Calculated Labour Hours,Cost 2 | IC0001,3,200 3 | IC0002,6,200 4 | IC0003,3,200 5 | IC0004,40,200 6 | IC0005,6,200 7 | IC0006,2,200 8 | IC0007,3,200 9 | IC0008,6,200 10 | IC0009,8,200 11 | IC0010,4,200 12 | IC0011,1,200 13 | IE0001,12,200 14 | IE0002,3,200 15 | IE0003,8,200 16 | IE0004,7,200 17 | IE0005,19,200 18 | IE0006,8,200 19 | IE0007,4,200 20 | IE0008,8,200 21 | IE0009,8,200 22 | IE0010,8,200 23 | IE0011,41,200 24 | IE0012,3,200 25 | IE0013,8,200 26 | IE0014,5,200 27 | IE0015,13,200 28 | IE0016,2,200 29 | IE0017,1,200 30 | IE0018,2,200 31 | IE0019,2,200 32 | IE0020,2,200 33 | IE0021,0.5,200 34 | IE0022,1,200 35 | IE0023,16,200 36 | IE0024,2,200 37 | IE0025,1,200 38 | IE0026,300,200 39 | IE0027,2,200 40 | IE0028,1,200 41 | IE0029,1,200 42 | IE0030,1,200 43 | IE0031,3,200 44 | IE0032,1,200 45 | IE0033,0.5,200 46 | IE0034,1,200 47 | IE0035,1,200 48 | IE0036,1,200 49 | IE0037,1,200 50 | IE0038,16,200 51 | IE0039,8,200 52 | IE0040,8,200 53 | IE0041,1,200 54 | IE0042,1,200 55 | IE0043,1,200 56 | IE0044,1,200 57 | IE0045,4,200 58 | IE0046,1,200 59 | IE0047,4,200 60 | IE0048,1,200 61 | IE0049,3,200 62 | IE0050,16,200 63 | IM0001,2,200 64 | IM0002,5,200 65 | IM0003,40,200 66 | IM0004,8,200 67 | IM0005,0.5,200 68 | IM0006,5,200 69 | IM0007,1,200 70 | IM0008,16,200 71 | IM0009,4,200 72 | IM0010,2,200 73 | IM0011,169,200 74 | IM0012,150,200 75 | IM0013,250,200 76 | IM0014,0.8,200 77 | IM0015,0.8,200 78 | IM0016,16,200 79 | IM0017,0.25,200 80 | IM0018,6,200 81 | IM0019,4,200 82 | IM0020,10,200 83 | IM0021,4,200 84 | IM0022,1,200 85 | IM0023,1,200 86 | IM0024,1,200 87 | IM0025,1,200 88 | IM0026,2,200 89 | IM0027,4,200 90 | IM0028,4,200 91 | IM0029,8,200 92 | IM0030,42,200 93 | IM0031,0.5,200 94 | IM0032,1,200 95 | IM0033,1,200 96 | IM0034,8,200 97 | IM0035,4,200 98 | IM0036,4,200 99 | IM0037,4,200 100 | IM0038,4,200 101 | IM0039,3,200 102 | IM0040,2,200 103 | IM0041,0.3,200 104 | IM0042,30,200 105 | IM0043,0.3,200 106 | IP0001,1,200 107 | IP0002,16,200 108 | IP0003,16,200 109 | IP0004,8,200 110 | IP0005,4,200 111 | IP0006,16,200 112 | IP0007,12,200 113 | IP0008,6,200 114 | IP0009,1,200 115 | IP0010,1,200 116 | IP0011,8,200 117 | IP0012,4,200 118 | IP0013,1,200 119 | IP0014,2,200 120 | IP0015,2,200 121 | IP0016,8,200 122 | IP0017,2,200 123 | IP0018,1,200 124 | IP0019,3,200 125 | IP0020,4,200 126 | IP0021,3,200 127 | IP0022,3,200 128 | IP0023,3,200 129 | IP0024,0.1,200 130 | IP0025,2,200 131 | IP0026,1,200 132 | IP0027,1,200 133 | IP0028,3,200 134 | IP0029,2,200 135 | IP0030,0.15,200 136 | IP0032,0.3,200 137 | IP0033,0.3,200 138 | IP0034,0.3,200 139 | IP0035,0.3,200 140 | IP0036,0.3,200 141 | IP0037,0.3,200 142 | IP0038,0.3,200 143 | IP0039,2,200 144 | IP0040,1,200 145 | IT0001,1,200 146 | IT0002,4,200 147 | IT0003,1,200 148 | IT0004,4,200 149 | IT0005,8,200 150 | IT0006,4,200 151 | IT0007,2,200 152 | IT0008,4,200 153 | IT0009,1,200 154 | IT0010,1,200 155 | IT0011,1,200 156 | IT0012,2,200 157 | IT0013,1,200 158 | -------------------------------------------------------------------------------- /tests/real2.exp: -------------------------------------------------------------------------------- 1 | "Equipment Ref","Task","Std Job","Task Description","Freq","Last Performed Date","Next Sch Date","Type" 2 | "IB-PBP-CBP-E211","P001","IP0007","Vessel Internal Statutory Inspection","1460","date(2006, 9, 29, 0, 0, 0, 0)","date(2010, 9, 28, 0, 0, 0, 0)","SO&M" 3 | "IB-PBP-CBP-E211","P002","IP0019","Vessel External Statutory Inspection","365","date(2007, 1, 23, 0, 0, 0, 0)","date(2008, 1, 23, 0, 0, 0, 0)","SO&M" 4 | "IB-PBP-CBP-E603","P001","IP0007","Vessel Internal Statutory Inspection","1460","date(2006, 11, 10, 0, 0, 0, 0)","date(2010, 11, 9, 0, 0, 0, 0)","SO&M" 5 | "IB-PBP-CBP-E603","P002","IP0019","Vessel External Statutory Inspection","365","date(2006, 11, 15, 0, 0, 0, 0)","date(2007, 11, 15, 0, 0, 0, 0)","SO&M" 6 | "IB-PBP-CBP-HS8840","P001","IP0030","Perform Critical Function Test","180","date(2006, 8, 4, 0, 0, 0, 0)","date(2007, 1, 31, 0, 0, 0, 0)","O&M" 7 | "IB-PBP-CBP-HS8851","P001","IP0030","Perform Critical Function Test","180","date(2007, 1, 31, 0, 0, 0, 0)","date(2007, 7, 30, 0, 0, 0, 0)","O&M" 8 | "IB-PBP-CBP-HS8871","P001","IP0030","Perform Critical Function Test","180","date(2007, 1, 31, 0, 0, 0, 0)","date(2007, 7, 30, 0, 0, 0, 0)","O&M" 9 | "IB-PBP-CBP-HS8888","P001","IP0030","Perform Critical Function Test","180","date(2007, 1, 31, 0, 0, 0, 0)","date(2007, 7, 30, 0, 0, 0, 0)","O&M" 10 | -------------------------------------------------------------------------------- /tests/real2.inp: -------------------------------------------------------------------------------- 1 | Equipment Ref,Task,Std Job,Task Description,Freq,Last Performed Date,Next Sch Date,Type 2 | IB-PBP-CBP-E211,P001,IP0007,Vessel Internal Statutory Inspection,1460,29/09/2006,28/09/2010,SO&M 3 | IB-PBP-CBP-E211,P002,IP0019,Vessel External Statutory Inspection,365,23/01/2007,23/01/2008,SO&M 4 | IB-PBP-CBP-E603,P001,IP0007,Vessel Internal Statutory Inspection,1460,10/11/2006,9/11/2010,SO&M 5 | IB-PBP-CBP-E603,P002,IP0019,Vessel External Statutory Inspection,365,15/11/2006,15/11/2007,SO&M 6 | IB-PBP-CBP-HS8840,P001,IP0030,Perform Critical Function Test,180,4/08/2006,31/01/2007,O&M 7 | IB-PBP-CBP-HS8851,P001,IP0030,Perform Critical Function Test,180,31/01/2007,30/07/2007,O&M 8 | IB-PBP-CBP-HS8871,P001,IP0030,Perform Critical Function Test,180,31/01/2007,30/07/2007,O&M 9 | IB-PBP-CBP-HS8888,P001,IP0030,Perform Critical Function Test,180,31/01/2007,30/07/2007,O&M 10 | -------------------------------------------------------------------------------- /tests/regression1.exp: -------------------------------------------------------------------------------- 1 | "aaa","bbb","ccc","ddd","eee","fff","ggg" 2 | -------------------------------------------------------------------------------- /tests/regression1.inp: -------------------------------------------------------------------------------- 1 | aaa,bbb,ccc,ddd,eee,fff,ggg 2 | -------------------------------------------------------------------------------- /tests/regression2.exp: -------------------------------------------------------------------------------- 1 | "a"," a"," a" 2 | -------------------------------------------------------------------------------- /tests/regression2.inp: -------------------------------------------------------------------------------- 1 | "a"," a"," a" 2 | -------------------------------------------------------------------------------- /tests/regression3.exp: -------------------------------------------------------------------------------- 1 | "aaa","bbb","ccc","ddd" 2 | "eee","fff","ggg","hhh" 3 | "iii","jjj","kkk","lll" 4 | -------------------------------------------------------------------------------- /tests/regression3.inp: -------------------------------------------------------------------------------- 1 | aaa,bbb,ccc,ddd 2 | eee,fff,ggg,hhh 3 | iii,jjj,kkk,lll 4 | -------------------------------------------------------------------------------- /tests/regression4-bad.err_exp: -------------------------------------------------------------------------------- 1 | regression4-bad.inp:3:20: error: in field #2, field width exceeded 2 | -------------------------------------------------------------------------------- /tests/regression4-bad.inp: -------------------------------------------------------------------------------- 1 | a,b,c 2 | "This is a quoted line .... 3 | .... with a break",ddd,eee 4 | ",,,,,", ,fff 5 | -------------------------------------------------------------------------------- /tests/regression4.exp: -------------------------------------------------------------------------------- 1 | "aaa","bbb","ccc" 2 | "This is a quoted line .... 3 | .... with a break","ddd","eee" 4 | ",,,,,"," ","fff" 5 | -------------------------------------------------------------------------------- /tests/regression4.inp: -------------------------------------------------------------------------------- 1 | aaa,bbb,ccc 2 | "This is a quoted line .... 3 | .... with a break",ddd,eee 4 | ",,,,,", ,fff 5 | -------------------------------------------------------------------------------- /tests/test_cases.m: -------------------------------------------------------------------------------- 1 | %-----------------------------------------------------------------------------% 2 | % vim: ft=mercury ts=4 sw=4 et 3 | %-----------------------------------------------------------------------------% 4 | 5 | :- module test_cases. 6 | :- interface. 7 | 8 | :- import_module csv. 9 | :- import_module list. 10 | :- import_module maybe. 11 | 12 | %-----------------------------------------------------------------------------% 13 | 14 | :- type test_type 15 | ---> test_type_valid 16 | ; test_type_invalid. 17 | 18 | :- type test_case 19 | ---> test_case( 20 | test_type :: test_type, 21 | test_name :: string, 22 | test_params :: maybe(reader_params), 23 | test_header :: header_desc, 24 | test_record :: record_desc 25 | ). 26 | 27 | :- type test_cases == list(test_case). 28 | 29 | %-----------------------------------------------------------------------------% 30 | 31 | :- func tests = test_cases. 32 | 33 | %-----------------------------------------------------------------------------% 34 | %-----------------------------------------------------------------------------% 35 | 36 | :- implementation. 37 | 38 | :- import_module bool. 39 | :- import_module int. 40 | :- import_module string. 41 | :- import_module univ. 42 | 43 | %-----------------------------------------------------------------------------% 44 | 45 | tests = [ 46 | test_case( 47 | test_type_valid, 48 | "bool1", 49 | no, 50 | no_header, 51 | [ 52 | field_desc(bool(tf_to_bool, []), no_limit, do_not_trim_whitespace), 53 | field_desc(bool(tf_to_bool, [negate_bool]), no_limit, do_not_trim_whitespace) 54 | ] 55 | ), 56 | 57 | % As above, but allow whitespace trimming. 58 | test_case( 59 | test_type_valid, 60 | "bool2", 61 | no, 62 | no_header, 63 | [ 64 | field_desc(bool(tf_to_bool, []), no_limit, trim_whitespace), 65 | field_desc(bool(tf_to_bool, [negate_bool]), no_limit, trim_whitespace) 66 | ] 67 | ), 68 | 69 | % As above, but with quoted values. 70 | test_case( 71 | test_type_valid, 72 | "bool3", 73 | no, 74 | no_header, 75 | [ 76 | field_desc(bool(tf_to_bool, []), no_limit, trim_whitespace), 77 | field_desc(bool(tf_to_bool, [negate_bool]), no_limit, trim_whitespace) 78 | ] 79 | ), 80 | 81 | % As above, but with a header. 82 | test_case( 83 | test_type_valid, 84 | "bool4", 85 | no, 86 | header_desc(no_limit), 87 | [ 88 | field_desc(bool(tf_to_bool, []), no_limit, trim_whitespace), 89 | field_desc(bool(tf_to_bool, [negate_bool]), no_limit, trim_whitespace) 90 | ] 91 | ), 92 | 93 | test_case( 94 | test_type_valid, 95 | "regression1", 96 | no, 97 | no_header, 98 | [ 99 | field_desc(string([]), limited(3), do_not_trim_whitespace), 100 | field_desc(string([]), limited(3), do_not_trim_whitespace), 101 | field_desc(string([]), limited(3), do_not_trim_whitespace), 102 | field_desc(string([]), limited(3), do_not_trim_whitespace), 103 | field_desc(string([]), limited(3), do_not_trim_whitespace), 104 | field_desc(string([]), limited(3), do_not_trim_whitespace), 105 | field_desc(string([]), limited(3), do_not_trim_whitespace) 106 | ] 107 | ), 108 | 109 | test_case( 110 | test_type_valid, 111 | "regression2", 112 | no, 113 | no_header, 114 | [ 115 | field_desc(string([]), limited(1), do_not_trim_whitespace), 116 | field_desc(string([]), limited(2), do_not_trim_whitespace), 117 | field_desc(string([]), limited(4), do_not_trim_whitespace) 118 | ] 119 | ), 120 | 121 | test_case( 122 | test_type_valid, 123 | "regression3", 124 | no, 125 | no_header, 126 | [ 127 | field_desc(string([]), limited(3), do_not_trim_whitespace), 128 | field_desc(string([]), limited(3), do_not_trim_whitespace), 129 | field_desc(string([]), limited(3), do_not_trim_whitespace), 130 | field_desc(string([]), limited(3), do_not_trim_whitespace) 131 | ] 132 | ), 133 | 134 | test_case( 135 | test_type_valid, 136 | "regression4", 137 | no, 138 | no_header, 139 | [ 140 | field_desc(string([]), no_limit, do_not_trim_whitespace), 141 | field_desc(string([]), no_limit, do_not_trim_whitespace), 142 | field_desc(string([]), no_limit, do_not_trim_whitespace) 143 | ] 144 | ), 145 | 146 | test_case( 147 | test_type_invalid, 148 | "regression4-bad", 149 | no, 150 | no_header, 151 | [ 152 | field_desc(string([]), no_limit, do_not_trim_whitespace), 153 | field_desc(string([]), limited(2), do_not_trim_whitespace), 154 | field_desc(string([]), limited(2), do_not_trim_whitespace) 155 | ] 156 | ), 157 | 158 | test_case( 159 | test_type_valid, 160 | "real1", 161 | no, 162 | header_desc(limited(255)), 163 | [ 164 | field_desc(string([]), limited(100), trim_whitespace), 165 | field_desc(float([]), limited(30), trim_whitespace), 166 | field_desc(float([]), limited(30), trim_whitespace) 167 | ] 168 | ), 169 | 170 | test_case( 171 | test_type_valid, 172 | "real2", 173 | no, 174 | header_desc(limited(255)), 175 | [ 176 | field_desc(string([]), limited(200), trim_whitespace), 177 | field_desc(string([]), limited(50), trim_whitespace), 178 | field_desc(string([]), limited(60), trim_whitespace), 179 | field_desc(string([]), limited(1000), trim_whitespace), 180 | field_desc( 181 | int(do_not_allow_floats, [require_non_negative]), 182 | limited(255), 183 | trim_whitespace 184 | ), 185 | field_desc(date(dd_mm_yyyy("/"), []), limited(255), trim_whitespace), 186 | field_desc(date(dd_mm_yyyy("/"), []), limited(255), trim_whitespace), 187 | field_desc( 188 | string([require_task_type]), 189 | limited(30), 190 | trim_whitespace 191 | ) 192 | ] 193 | ), 194 | 195 | test_case( 196 | test_type_valid, 197 | "wiki1", 198 | no, 199 | no_header, 200 | [ 201 | field_desc(int(do_not_allow_floats, []), limited(4), do_not_trim_whitespace), 202 | field_desc(string([]), limited(100), do_not_trim_whitespace), 203 | field_desc(string([]), limited(4), do_not_trim_whitespace), 204 | field_desc(string([]), no_limit, do_not_trim_whitespace) 205 | ] 206 | ), 207 | 208 | test_case( 209 | test_type_valid, 210 | "wiki2", 211 | no, 212 | header_desc(limited(100)), 213 | [ 214 | field_desc(int(do_not_allow_floats, []), limited(4), do_not_trim_whitespace), 215 | field_desc(string([]), limited(255), do_not_trim_whitespace), 216 | field_desc(string([]), limited(255), do_not_trim_whitespace), 217 | field_desc(string([]), limited(500), do_not_trim_whitespace), 218 | field_desc(float([]), limited(8), do_not_trim_whitespace) 219 | ] 220 | ), 221 | 222 | test_case( 223 | test_type_valid, 224 | "date1", 225 | no, 226 | no_header, 227 | [ 228 | field_desc(date(yyyy_mm_dd("-"), []), limited(10), do_not_trim_whitespace), 229 | field_desc(date(dd_mm_yyyy("."), []), limited(10), do_not_trim_whitespace), 230 | field_desc(date(mm_dd_yyyy(","), []), limited(10), do_not_trim_whitespace) 231 | ] 232 | ), 233 | 234 | test_case( 235 | test_type_valid, 236 | "date2", 237 | no, 238 | no_header, 239 | [ 240 | field_desc(date(yyyy_b_dd("-"), []), limited(14), do_not_trim_whitespace), 241 | field_desc(date(b_dd_yyyy("-"), []), limited(14), do_not_trim_whitespace), 242 | field_desc(date(dd_b_yyyy("-"), []), limited(14), do_not_trim_whitespace) 243 | ] 244 | ), 245 | 246 | test_case( 247 | test_type_valid, 248 | "countrylist", 249 | no, 250 | header_desc(limited(50)), 251 | [ 252 | field_desc(int(do_not_allow_floats, []), limited(3), do_not_trim_whitespace), 253 | field_desc(string([]), limited(200), do_not_trim_whitespace), 254 | field_desc(string([]), limited(200), do_not_trim_whitespace), 255 | field_desc(string([]), no_limit, do_not_trim_whitespace), 256 | field_desc(string([]), no_limit, do_not_trim_whitespace), 257 | field_desc(string([]), no_limit, do_not_trim_whitespace), 258 | field_desc(string([]), no_limit, do_not_trim_whitespace), 259 | field_desc(string([]), no_limit, do_not_trim_whitespace), 260 | field_desc(string([]), no_limit, do_not_trim_whitespace), 261 | field_desc(string([]), no_limit, do_not_trim_whitespace), 262 | field_desc(string([]), no_limit, do_not_trim_whitespace), 263 | field_desc(string([]), no_limit, do_not_trim_whitespace), 264 | field_desc(string([]), no_limit, do_not_trim_whitespace), 265 | field_desc(string([]), no_limit, do_not_trim_whitespace) 266 | ] 267 | ), 268 | 269 | test_case( 270 | test_type_valid, 271 | "companies", 272 | no, 273 | header_desc(limited(50)), 274 | [ 275 | field_desc(string([]), limited(200), do_not_trim_whitespace), 276 | field_desc(string([]), limited(4), do_not_trim_whitespace), 277 | field_desc(string([require_industry_group]), limited(200), do_not_trim_whitespace) 278 | ] 279 | ), 280 | 281 | % Check if a date field contains an invalid date, e.g. Feb. 30. 282 | % 283 | test_case( 284 | test_type_invalid, 285 | "invalid_date1", 286 | no, 287 | no_header, 288 | [ 289 | field_desc(date(dd_mm_yyyy("/"), []), no_limit, do_not_trim_whitespace), 290 | field_desc(date(dd_mm_yyyy("/"), []), no_limit, do_not_trim_whitespace) 291 | ] 292 | ), 293 | 294 | % Test if there are too many fields in a record. 295 | % 296 | test_case( 297 | test_type_invalid, 298 | "field_limit", 299 | no, 300 | no_header, 301 | [ 302 | field_desc(string([]), no_limit, do_not_trim_whitespace), 303 | field_desc(string([]), no_limit, do_not_trim_whitespace), 304 | field_desc(string([]), no_limit, do_not_trim_whitespace) 305 | ] 306 | ), 307 | 308 | test_case( 309 | test_type_invalid, 310 | "header_eof", 311 | no, 312 | header_desc(limited(255)), 313 | [ 314 | field_desc(string([]), no_limit, do_not_trim_whitespace), 315 | field_desc(string([]), no_limit, do_not_trim_whitespace), 316 | field_desc(string([]), no_limit, do_not_trim_whitespace) 317 | ] 318 | ), 319 | 320 | test_case( 321 | test_type_invalid, 322 | "unmatched_quote", 323 | no, 324 | no_header, 325 | [ 326 | field_desc(string([]), no_limit, do_not_trim_whitespace), 327 | field_desc(string([]), no_limit, do_not_trim_whitespace) 328 | ] 329 | ), 330 | 331 | test_case( 332 | test_type_invalid, 333 | "unmatched_quote2", 334 | no, 335 | no_header, 336 | [ 337 | field_desc(string([]), no_limit, do_not_trim_whitespace), 338 | field_desc(string([]), no_limit, do_not_trim_whitespace) 339 | ] 340 | ), 341 | 342 | test_case( 343 | test_type_invalid, 344 | "unmatched_quote3", 345 | no, 346 | no_header, 347 | [ 348 | field_desc(string([]), no_limit, do_not_trim_whitespace), 349 | field_desc(string([]), no_limit, do_not_trim_whitespace) 350 | ] 351 | ), 352 | 353 | test_case( 354 | test_type_valid, 355 | "qm_in_uqf1", 356 | yes(reader_params(no_blank_lines, no_trailing_fields, no_comments, 357 | allow_quotation_mark_in_unquoted_field, ',')), 358 | no_header, 359 | [ 360 | field_desc(string([]), no_limit, do_not_trim_whitespace), 361 | field_desc(string([]), no_limit, do_not_trim_whitespace), 362 | field_desc(string([]), no_limit, do_not_trim_whitespace) 363 | ] 364 | ), 365 | 366 | test_case( 367 | test_type_invalid, 368 | "qm_in_uqf2", 369 | yes(reader_params(no_blank_lines, no_trailing_fields, no_comments, 370 | allow_quotation_mark_in_unquoted_field, ',')), 371 | no_header, 372 | [ 373 | field_desc(string([]), no_limit, do_not_trim_whitespace), 374 | field_desc(string([]), no_limit, do_not_trim_whitespace), 375 | field_desc(string([]), no_limit, do_not_trim_whitespace) 376 | ] 377 | ), 378 | 379 | test_case( 380 | test_type_invalid, 381 | "width_limit", 382 | no, 383 | no_header, 384 | [ 385 | field_desc(string([]), limited(3), do_not_trim_whitespace), 386 | field_desc(string([]), limited(3), do_not_trim_whitespace) 387 | ] 388 | ), 389 | 390 | test_case( 391 | test_type_invalid, 392 | "width_limit2", 393 | no, 394 | no_header, 395 | [ 396 | field_desc(string([]), limited(6), do_not_trim_whitespace), 397 | field_desc(string([]), limited(3), do_not_trim_whitespace) 398 | ] 399 | ), 400 | 401 | test_case( 402 | test_type_valid, 403 | "univ", 404 | no, 405 | no_header, 406 | [ 407 | field_desc(univ(fruit_to_univ, []), limited(6), do_not_trim_whitespace), 408 | field_desc(univ(fruit_to_univ, []), limited(6), do_not_trim_whitespace), 409 | field_desc(univ(fruit_to_univ, []), limited(6), do_not_trim_whitespace) 410 | ] 411 | ), 412 | 413 | test_case( 414 | test_type_valid, 415 | "discard", 416 | no, 417 | no_header, 418 | [ 419 | field_desc(string([]), limited(6), do_not_trim_whitespace), 420 | discard(no_limit), 421 | field_desc(int(do_not_allow_floats, []), no_limit, do_not_trim_whitespace) 422 | ] 423 | ), 424 | 425 | test_case( 426 | test_type_invalid, 427 | "discard-bad", 428 | no, 429 | no_header, 430 | [ 431 | field_desc(string([]), limited(6), do_not_trim_whitespace), 432 | discard(limited(3)), 433 | field_desc(string([]), limited(6), do_not_trim_whitespace) 434 | ] 435 | ), 436 | 437 | test_case( 438 | test_type_valid, 439 | "date_time", 440 | no, 441 | no_header, 442 | [ 443 | field_desc(date_time(mm_dd_yyyy_hh_mm("-", " ", ":"), []), no_limit, do_not_trim_whitespace), 444 | field_desc(date_time(mm_dd_yyyy_hh_mm("-", " ", ":"), []), no_limit, do_not_trim_whitespace) 445 | ] 446 | ), 447 | 448 | test_case( 449 | test_type_valid, 450 | "date_time2", 451 | no, 452 | no_header, 453 | [ 454 | field_desc(date_time(dd_mm_yyyy_hh_mm("/", " ", ":"), []), no_limit, do_not_trim_whitespace), 455 | field_desc(int(do_not_allow_floats, []), no_limit, do_not_trim_whitespace) 456 | ] 457 | ), 458 | 459 | test_case( 460 | test_type_valid, 461 | "maybe", 462 | no, 463 | no_header, 464 | [ 465 | field_desc(maybe(int(do_not_allow_floats, [])), no_limit, do_not_trim_whitespace), 466 | field_desc(maybe(int(do_not_allow_floats, [])), no_limit, do_not_trim_whitespace) 467 | ] 468 | ), 469 | 470 | test_case( 471 | test_type_invalid, 472 | "field_desc_mismatch", 473 | no, 474 | no_header, 475 | [ 476 | field_desc(int(do_not_allow_floats, []), no_limit, do_not_trim_whitespace), 477 | field_desc(int(do_not_allow_floats, []), no_limit, do_not_trim_whitespace) 478 | ] 479 | ), 480 | 481 | test_case( 482 | test_type_valid, 483 | "conv_funcs_valid", 484 | no, 485 | no_header, 486 | [ 487 | int_field_desc, 488 | date_field_desc(yyyy_mm_dd("-")), 489 | float_field_desc, 490 | floatstr_field_desc, 491 | string_field_desc, 492 | maybe_date_field_desc(yyyy_mm_dd("-")), 493 | maybe_float_field_desc, 494 | maybe_floatstr_field_desc, 495 | maybe_int_field_desc, 496 | maybe_string_field_desc 497 | ] 498 | ), 499 | 500 | test_case( 501 | test_type_valid, 502 | "blank_lines", 503 | yes(reader_params(ignore_blank_lines, no_trailing_fields, no_comments, 504 | no_quotation_mark_in_unquoted_field, ',')), 505 | header_desc(no_limit), 506 | [ 507 | string_field_desc, 508 | string_field_desc, 509 | string_field_desc 510 | ] 511 | ), 512 | 513 | test_case( 514 | test_type_valid, 515 | "trailing_fields", 516 | yes(reader_params(no_blank_lines, ignore_trailing_fields, no_comments, 517 | no_quotation_mark_in_unquoted_field, ',')), 518 | header_desc(no_limit), 519 | [ 520 | field_desc(string([]), no_limit, do_not_trim_whitespace), 521 | field_desc(string([]), no_limit, do_not_trim_whitespace) 522 | ] 523 | ), 524 | 525 | test_case( 526 | test_type_valid, 527 | "comments", 528 | yes(reader_params(no_blank_lines, no_trailing_fields, allow_comments('#'), 529 | no_quotation_mark_in_unquoted_field, ',')), 530 | header_desc(no_limit), 531 | [ 532 | field_desc(string([]), no_limit, do_not_trim_whitespace), 533 | field_desc(string([]), no_limit, do_not_trim_whitespace), 534 | field_desc(string([]), no_limit, do_not_trim_whitespace) 535 | ] 536 | ) 537 | ]. 538 | 539 | %-----------------------------------------------------------------------------% 540 | 541 | % For use by the real2 test. 542 | 543 | :- func require_non_negative(int) = field_action_result(int). 544 | 545 | require_non_negative(I) = 546 | ( if I < 0 then error("negative frequency") else ok(I)). 547 | 548 | :- func require_task_type(string) = field_action_result(string). 549 | 550 | require_task_type(S) = 551 | ( if is_task_lov(S) then ok(S) else error("invalid type: " ++ S) ). 552 | 553 | :- pred is_task_lov(string::in) is semidet. 554 | 555 | is_task_lov("O&M"). 556 | is_task_lov("SO&M"). 557 | 558 | %-----------------------------------------------------------------------------% 559 | 560 | :- func require_industry_group(string) = field_action_result(string). 561 | 562 | require_industry_group(S) = 563 | ( if is_industry_group(S) then 564 | ok(S) 565 | else 566 | error("not an industry group: " ++ S) 567 | ). 568 | 569 | :- pred is_industry_group(string::in) is semidet. 570 | 571 | is_industry_group("Energy"). 572 | is_industry_group("Materials"). 573 | is_industry_group("Capital Goods"). 574 | is_industry_group("Commercial & Professional Services"). 575 | is_industry_group("Transportation"). 576 | is_industry_group("Automobile & Components"). 577 | is_industry_group("Consumer Durables & Apparel"). 578 | is_industry_group("Consumer Services"). 579 | is_industry_group("Media"). 580 | is_industry_group("Retailing"). 581 | is_industry_group("Food & Staples Retailing"). 582 | is_industry_group("Food Beverage & Tobacco"). 583 | is_industry_group("Household & Personal Products"). 584 | is_industry_group("Health Care Equipment & Services"). 585 | is_industry_group("Pharmaceuticals, Biotechnology & Life Sciences"). 586 | is_industry_group("Banks"). 587 | is_industry_group("Diversified Financials"). 588 | is_industry_group("Insurance"). 589 | is_industry_group("Real Estate"). 590 | is_industry_group("Software & Services"). 591 | is_industry_group("Technology Hardware & Equipment"). 592 | is_industry_group("Semiconductors & Semiconductor Equipment"). 593 | is_industry_group("Telecommunication Services"). 594 | is_industry_group("Utilities"). 595 | is_industry_group("GICS Sector Code Not Applicable"). 596 | is_industry_group("Classification Pending"). 597 | 598 | %-----------------------------------------------------------------------------% 599 | 600 | :- func tf_to_bool(string) = field_action_result(bool). 601 | 602 | tf_to_bool(Str) = Result :- 603 | ( if Str = "T" then Result = ok(yes) 604 | else if Str = "F" then Result = ok(no) 605 | else Result = error("not 'T' or 'F'") 606 | ). 607 | 608 | :- func negate_bool(bool) = field_action_result(bool). 609 | 610 | negate_bool(B) = ok(bool.not(B)). 611 | 612 | %-----------------------------------------------------------------------------% 613 | 614 | % For the univ test case. 615 | 616 | :- type fruit 617 | ---> apple 618 | ; orange 619 | ; lemon 620 | ; pear. 621 | 622 | :- func fruit_to_univ : univ_handler. 623 | 624 | fruit_to_univ(String) = 625 | ( if string_to_fruit(String, Fruit) 626 | then ok(univ(Fruit)) 627 | else error("not a fruit") 628 | ). 629 | 630 | :- pred string_to_fruit(string::in, fruit::out) is semidet. 631 | 632 | string_to_fruit("apple", apple). 633 | string_to_fruit("orange", orange). 634 | string_to_fruit("lemon", lemon). 635 | string_to_fruit("pear", pear). 636 | 637 | %-----------------------------------------------------------------------------% 638 | :- end_module test_cases. 639 | %-----------------------------------------------------------------------------% 640 | -------------------------------------------------------------------------------- /tests/test_csv.m: -------------------------------------------------------------------------------- 1 | %-----------------------------------------------------------------------------% 2 | % vim: ft=mercury ts=4 sw=4 et 3 | %-----------------------------------------------------------------------------% 4 | 5 | :- module test_csv. 6 | :- interface. 7 | 8 | :- import_module io. 9 | 10 | :- pred main(io::di, io::uo) is det. 11 | 12 | %-----------------------------------------------------------------------------% 13 | %-----------------------------------------------------------------------------% 14 | 15 | :- implementation. 16 | 17 | :- import_module csv. 18 | :- import_module harness. 19 | :- import_module test_cases. 20 | 21 | :- import_module bool. 22 | :- import_module char. 23 | :- import_module getopt. 24 | :- import_module int. 25 | :- import_module io.file. 26 | :- import_module list. 27 | :- import_module maybe. 28 | :- import_module set. 29 | :- import_module string. 30 | :- import_module stream. 31 | 32 | %-----------------------------------------------------------------------------% 33 | %-----------------------------------------------------------------------------% 34 | 35 | main(!IO) :- 36 | io.command_line_arguments(Args, !IO), 37 | OptionOps = option_ops_multi( 38 | (pred(C::in, O::out) is semidet :- short_option(C, O)), 39 | long_option, 40 | (pred(O::out, D::out) is multi :- option_defaults(O, D)) 41 | ), 42 | getopt.process_options_io(OptionOps, Args, NonOptionArgs, OptionResult, 43 | !IO), 44 | ( 45 | OptionResult = ok(OptionTable), 46 | getopt.lookup_bool_option(OptionTable, help, Help), 47 | ( 48 | Help = yes, 49 | help(!IO) 50 | ; 51 | Help = no, 52 | ( 53 | NonOptionArgs = [], 54 | TestsToRun = tests 55 | ; 56 | NonOptionArgs = [_ | _], 57 | list.filter(test_name_matches(NonOptionArgs), tests, TestsToRun) 58 | ), 59 | Results0 = init_test_results, 60 | list.length(TestsToRun, NumTests), 61 | list.foldl3(run_test(OptionTable, NumTests), TestsToRun, 1, _, 62 | Results0, Results, !IO), 63 | print_results(Results, !IO) 64 | ) 65 | ; 66 | OptionResult = error(OptionError), 67 | Msg = option_error_to_string(OptionError), 68 | bad_cmdline(Msg, !IO) 69 | ). 70 | 71 | :- pred test_name_matches(list(string)::in, test_case::in) is semidet. 72 | 73 | test_name_matches(TestNames, TestCase) :- 74 | Name = TestCase ^ test_name, 75 | list.member(Name, TestNames). 76 | 77 | %-----------------------------------------------------------------------------% 78 | 79 | :- pred run_test(option_table(option)::in, int::in, test_case::in, 80 | int::in, int::out, test_results::in, test_results::out, io::di, io::uo) 81 | is det. 82 | 83 | run_test(OptionTable, _TotalNumTests, TestCase, !TestNum, !Results, !IO) :- 84 | increment_total_tests(!Results), 85 | TestCase = test_case(Type, Name, MaybeParams, HeaderDesc, RecordDesc), 86 | maybe_verbose(OptionTable, io.format("RUNNING TEST: %s ... ", [s(Name)]), 87 | !IO), 88 | ( 89 | Type = test_type_valid, 90 | run_test_valid(OptionTable, Name, MaybeParams, HeaderDesc, 91 | RecordDesc, !Results, !IO) 92 | ; 93 | Type = test_type_invalid, 94 | run_test_invalid(OptionTable, Name, MaybeParams, HeaderDesc, 95 | RecordDesc, !Results, !IO) 96 | ), 97 | !:TestNum = !.TestNum + 1. 98 | 99 | %-----------------------------------------------------------------------------% 100 | % 101 | % "Valid" tests are those where we expect the CSV reader to produce some 102 | % CSV data. We then write out that data and diff the result against an 103 | % expected output. 104 | 105 | :- pred run_test_valid(option_table(option)::in, 106 | string::in, maybe(reader_params)::in, header_desc::in, record_desc::in, 107 | test_results::in, test_results::out, io::di, io::uo) is det. 108 | 109 | run_test_valid(OptionTable, Name, MaybeParams, HeaderDesc, RecordDesc, 110 | !Results, !IO) :- 111 | InputFileName = Name ++ ".inp", 112 | io.open_input(InputFileName, InputResult, !IO), 113 | ( 114 | InputResult = ok(InputFile), 115 | OutputFileName = Name ++ ".out", 116 | io.open_output(OutputFileName, OutputResult, !IO), 117 | ( 118 | OutputResult = ok(OutputFile), 119 | ( 120 | MaybeParams = yes(Params), 121 | csv.init_reader(InputFile, Params, HeaderDesc, RecordDesc, 122 | Reader, !IO) 123 | ; 124 | MaybeParams = no, 125 | csv.init_reader(InputFile, HeaderDesc, RecordDesc, 126 | Reader, !IO) 127 | ), 128 | process_csv(Reader, OutputFile, Result, !IO), 129 | ( 130 | Result = ok, 131 | io.close_input(InputFile, !IO), 132 | io.close_output(OutputFile, !IO), 133 | ExpFileName = Name ++ ".exp", 134 | ResFileName = Name ++ ".res", 135 | string.format("diff -u %s %s > %s", 136 | [s(ExpFileName), s(OutputFileName), s(ResFileName)], 137 | DiffCmd), 138 | io.call_system(DiffCmd, DiffCmdRes, !IO), 139 | ( 140 | DiffCmdRes = ok(DiffExitStatus), 141 | ( if DiffExitStatus = 0 then 142 | maybe_verbose(OptionTable, 143 | io.write_string("PASSED\n"), !IO), 144 | lookup_bool_option(OptionTable, keep_files, KeepFiles), 145 | ( 146 | KeepFiles = yes 147 | ; 148 | KeepFiles = no, 149 | io.file.remove_file(ResFileName, _, !IO), 150 | io.file.remove_file(OutputFileName, _, !IO) 151 | ) 152 | else 153 | add_failed_test(Name, !Results), 154 | maybe_verbose(OptionTable, 155 | io.write_string("FAILED (expected output does not match)\n"), !IO) 156 | ) 157 | ; 158 | DiffCmdRes = error(DiffError), 159 | add_aborted_test(Name, !Results), 160 | io.error_message(DiffError, Msg), 161 | maybe_verbose(OptionTable, 162 | io.format("ABORTED (diff: %s)\n", 163 | [s(Msg)]), !IO) 164 | ) 165 | ; 166 | Result = error(ProcessCSV_Error), 167 | ( 168 | ProcessCSV_Error = stream_error(StreamError), 169 | add_aborted_test(Name, !Results), 170 | Msg = stream.error_message(StreamError), 171 | maybe_verbose(OptionTable, 172 | io.format("ABORTED (stream error: %s)\n", 173 | [s(Msg)]), !IO) 174 | ; 175 | ProcessCSV_Error = csv_error(StreamName, LineNo, ColNo, 176 | FieldNo, Msg), 177 | ErrFileName = Name ++ ".err", 178 | io.open_output(ErrFileName, ErrFileResult, !IO), 179 | ( 180 | ErrFileResult = ok(ErrFile), 181 | write_csv_error(ErrFile, StreamName, LineNo, ColNo, 182 | FieldNo, Msg, !IO), 183 | io.close_output(ErrFile, !IO), 184 | add_failed_test(Name, !Results), 185 | maybe_verbose(OptionTable, 186 | io.write_string("FAILED (CSV parse error)\n"), !IO) 187 | ; 188 | ErrFileResult = error(IO_Error), 189 | add_aborted_test(Name, !Results), 190 | report_file_open_error(ErrFileName, IO_Error, !IO) 191 | ) 192 | ) 193 | ) 194 | ; 195 | OutputResult = error(IO_Error), 196 | add_aborted_test(Name, !Results), 197 | report_file_open_error(OutputFileName, IO_Error, !IO) 198 | ) 199 | ; 200 | InputResult = error(IO_Error), 201 | add_aborted_test(Name, !Results), 202 | report_file_open_error(InputFileName, IO_Error, !IO) 203 | ). 204 | 205 | %-----------------------------------------------------------------------------% 206 | 207 | :- pred run_test_invalid(option_table(option)::in, 208 | string::in, maybe(reader_params)::in, header_desc::in, record_desc::in, 209 | test_results::in, test_results::out, io::di, io::uo) is det. 210 | 211 | run_test_invalid(OptionTable, Name, MaybeParams, HeaderDesc, RecordDesc, 212 | !Results, !IO) :- 213 | InputFileName = Name ++ ".inp", 214 | io.open_input(InputFileName, InputResult, !IO), 215 | ( 216 | InputResult = ok(InputFile), 217 | OutputFileName = Name ++ ".out", 218 | io.open_output(OutputFileName, OutputResult, !IO), 219 | ( 220 | OutputResult = ok(OutputFile), 221 | ( 222 | MaybeParams = yes(Params), 223 | csv.init_reader(InputFile, Params, HeaderDesc, RecordDesc, 224 | Reader, !IO) 225 | ; 226 | MaybeParams = no, 227 | csv.init_reader(InputFile, HeaderDesc, RecordDesc, 228 | Reader, !IO) 229 | ), 230 | process_csv(Reader, OutputFile, Result, !IO), 231 | ( 232 | Result = error(ProcessCSV_Error), 233 | ( 234 | ProcessCSV_Error = stream_error(StreamError), 235 | add_aborted_test(Name, !Results), 236 | Msg = stream.error_message(StreamError), 237 | maybe_verbose(OptionTable, 238 | io.format("ABORTED (stream error: %s)\n", 239 | [s(Msg)]), !IO) 240 | ; 241 | ProcessCSV_Error = csv_error(StreamName, LineNo, ColNo, 242 | FieldNo, Msg), 243 | ErrFileName = Name ++ ".err", 244 | io.open_output(ErrFileName, ErrFileResult, !IO), 245 | ( 246 | ErrFileResult = ok(ErrFile), 247 | write_csv_error(ErrFile, StreamName, LineNo, ColNo, 248 | FieldNo, Msg, !IO), 249 | io.close_output(ErrFile, !IO), 250 | ExpErrFileName = Name ++ ".err_exp", 251 | ResFileName = Name ++ ".res", 252 | string.format("diff -u %s %s > %s", 253 | [s(ExpErrFileName), s(ErrFileName), s(ResFileName)], 254 | DiffCmd), 255 | io.call_system(DiffCmd, DiffCmdRes, !IO), 256 | ( 257 | DiffCmdRes = ok(DiffExitStatus), 258 | ( if DiffExitStatus = 0 then 259 | maybe_verbose(OptionTable, 260 | io.write_string("PASSED\n"), !IO), 261 | lookup_bool_option(OptionTable, keep_files, KeepFiles), 262 | ( 263 | KeepFiles = yes 264 | ; 265 | KeepFiles = no, 266 | io.file.remove_file(ResFileName, _, !IO), 267 | io.file.remove_file(OutputFileName, _, !IO), 268 | io.file.remove_file(ErrFileName, _, !IO) 269 | ) 270 | else 271 | add_failed_test(Name, !Results), 272 | maybe_verbose(OptionTable, 273 | io.write_string( 274 | "FAILED (expected error output does not match)\n"), !IO) 275 | ) 276 | ; 277 | DiffCmdRes = error(DiffError), 278 | add_aborted_test(Name, !Results), 279 | io.error_message(DiffError, DiffErrMsg), 280 | maybe_verbose(OptionTable, 281 | io.format("ABORTED (diff: %s)\n", 282 | [s(DiffErrMsg)]), !IO) 283 | ) 284 | ; 285 | ErrFileResult = error(IO_Error), 286 | add_aborted_test(Name, !Results), 287 | report_file_open_error(ErrFileName, IO_Error, !IO) 288 | ) 289 | ) 290 | ; 291 | Result = ok, 292 | add_failed_test(Name, !Results), 293 | maybe_verbose(OptionTable, 294 | io.write_string( 295 | "FAILED (erroneously processed successfully)\n"), !IO) 296 | ) 297 | ; 298 | OutputResult = error(IO_Error), 299 | add_aborted_test(Name, !Results), 300 | report_file_open_error(OutputFileName, IO_Error, !IO) 301 | ) 302 | ; 303 | InputResult = error(IO_Error), 304 | add_aborted_test(Name, !Results), 305 | report_file_open_error(InputFileName, IO_Error, !IO) 306 | ). 307 | 308 | %-----------------------------------------------------------------------------% 309 | %-----------------------------------------------------------------------------% 310 | % 311 | % Test results. 312 | % 313 | 314 | :- type test_results 315 | ---> test_results( 316 | total_tests :: int, 317 | 318 | failed_tests :: list(string), 319 | % Tests that failed. 320 | 321 | aborted_tests :: list(string) 322 | % Tests that caused an error. 323 | ). 324 | 325 | :- func init_test_results = test_results. 326 | 327 | init_test_results = test_results(0, [], []). 328 | 329 | :- pred increment_total_tests(test_results::in, test_results::out) is det. 330 | 331 | increment_total_tests(!Results) :- 332 | !Results ^ total_tests := !.Results ^ total_tests + 1. 333 | 334 | :- pred add_failed_test(string::in, 335 | test_results::in, test_results::out) is det. 336 | 337 | add_failed_test(Name, !Results) :- 338 | !Results ^ failed_tests := [Name | !.Results ^ failed_tests]. 339 | 340 | :- pred add_aborted_test(string::in, 341 | test_results::in, test_results::out) is det. 342 | 343 | add_aborted_test(Name, !Results) :- 344 | !Results ^ aborted_tests := [Name | !.Results ^ aborted_tests]. 345 | 346 | :- pred print_results(test_results::in, io::di, io::uo) is det. 347 | 348 | print_results(Results, !IO) :- 349 | Results = test_results(_Total, Failed, Aborted), 350 | list.length(Failed, NumFailed), 351 | list.length(Aborted, NumAborted), 352 | ( if NumFailed = 0, NumAborted = 0 then 353 | io.write_string("ALL TESTS PASSED\n", !IO) 354 | else 355 | ( if NumFailed > 0 then 356 | io.write_string("SOME TESTS FAILED\n", !IO), 357 | write_tests_to_file(Failed, "FAILED_TESTS", !IO) 358 | else 359 | true 360 | ), 361 | ( if NumAborted > 0 then 362 | io.write_string("SOME TESTS ABORTED\n", !IO), 363 | write_tests_to_file(Aborted, "ABORTED_TESTS", !IO), 364 | io.set_exit_status(1, !IO) 365 | else 366 | true 367 | ) 368 | ). 369 | 370 | :- pred write_tests_to_file(list(string)::in, string::in, 371 | io::di, io::uo) is det. 372 | 373 | write_tests_to_file(Tests, FileName, !IO) :- 374 | io.open_output(FileName, OpenResult, !IO), 375 | ( 376 | OpenResult = ok(File), 377 | io.write_list(File, Tests, "\n", io.write_string, !IO), 378 | io.nl(File, !IO) 379 | ; 380 | OpenResult = error(IO_Error), 381 | report_file_open_error(FileName, IO_Error, !IO) 382 | ). 383 | 384 | %-----------------------------------------------------------------------------% 385 | 386 | :- pred maybe_verbose(option_table(option)::in, 387 | pred(io, io)::in(pred(di, uo) is det), io::di, io::uo) is det. 388 | 389 | maybe_verbose(OptionTable, Pred, !IO) :- 390 | getopt.lookup_bool_option(OptionTable, verbose, Verbose), 391 | ( 392 | Verbose = yes, 393 | Pred(!IO) 394 | ; 395 | Verbose = no 396 | ). 397 | 398 | :- pred maybe_not_verbose(option_table(option)::in, 399 | pred(io, io)::in(pred(di, uo) is det), io::di, io::uo) is det. 400 | 401 | maybe_not_verbose(OptionTable, Pred, !IO) :- 402 | getopt.lookup_bool_option(OptionTable, verbose, Verbose), 403 | ( 404 | Verbose = no, 405 | Pred(!IO) 406 | ; 407 | Verbose = yes 408 | ). 409 | 410 | %-----------------------------------------------------------------------------% 411 | % 412 | % Command line options. 413 | % 414 | 415 | :- type option 416 | ---> help 417 | ; verbose 418 | ; keep_files. 419 | 420 | :- pred short_option(char, option). 421 | :- mode short_option(in, out) is semidet. 422 | :- mode short_option(out, in) is det. 423 | 424 | short_option('h', help). 425 | short_option('v', verbose). 426 | short_option('k', keep_files). 427 | 428 | :- pred long_option(string::in, option::out) is semidet. 429 | 430 | long_option("help", help). 431 | long_option("verbose", verbose). 432 | long_option("keep-files", keep_files). 433 | 434 | :- pred option_defaults(option, option_data). 435 | :- mode option_defaults(in, out) is det. 436 | :- mode option_defaults(out, out) is multi. 437 | 438 | option_defaults(help, bool(no)). 439 | option_defaults(verbose, bool(no)). 440 | option_defaults(keep_files, bool(no)). 441 | 442 | %-----------------------------------------------------------------------------% 443 | 444 | :- pred bad_cmdline(string::in, io::di, io::uo) is det. 445 | 446 | bad_cmdline(Msg, !IO) :- 447 | io.stderr_stream(Stderr, !IO), 448 | io.format(Stderr, "test_csv: %s\n", [s(Msg)], !IO), 449 | io.write_string(Stderr, "test_csv: use --help for more information.\n", 450 | !IO), 451 | io.set_exit_status(1, !IO). 452 | 453 | %-----------------------------------------------------------------------------% 454 | 455 | :- pred write_csv_error(io.text_output_stream::in, string::in, 456 | int::in, int::in, int::in, string::in, io::di, io::uo) is det. 457 | 458 | write_csv_error(File, Name, LineNo, ColNo, FieldNo, Msg, !IO) :- 459 | io.format(File, "%s:%d:%d: error: in field #%d, %s\n", 460 | [s(Name), i(LineNo), i(ColNo), i(FieldNo), s(Msg)], !IO). 461 | 462 | %-----------------------------------------------------------------------------% 463 | 464 | :- pred help(io::di, io::uo) is det. 465 | 466 | help(!IO) :- 467 | io.write_string("Usage: test_csv [] [test-case ...]\n", !IO), 468 | io.write_strings([ 469 | "Options:\n", 470 | "\t-h, --help\n", 471 | "\t\tPrint this message.\n", 472 | "\t-v, -verbose\n", 473 | "\t\tOutput progress information.\n", 474 | "\t-k, --keep-files\n", 475 | "\t\tDo not delete files generated during a test run.\n" 476 | ], !IO). 477 | 478 | %-----------------------------------------------------------------------------% 479 | 480 | :- pred report_file_open_error(string::in, io.error::in, 481 | io::di, io::uo) is det. 482 | 483 | report_file_open_error(FileName, Error, !IO) :- 484 | io.stderr_stream(Stderr, !IO), 485 | Msg = io.error_message(Error), 486 | io.format(Stderr, "error opening file `%s': %s\n", 487 | [s(FileName), s(Msg)], !IO), 488 | io.set_exit_status(1, !IO). 489 | 490 | %-----------------------------------------------------------------------------% 491 | :- end_module test_csv. 492 | %-----------------------------------------------------------------------------% 493 | -------------------------------------------------------------------------------- /tests/trailing_fields.exp: -------------------------------------------------------------------------------- 1 | "ColA","ColB" 2 | "Val1","Val2" 3 | "Val4","Val5" 4 | "Val7","Val8" 5 | -------------------------------------------------------------------------------- /tests/trailing_fields.inp: -------------------------------------------------------------------------------- 1 | ColA,ColB,Col3,,,,,,,,,, 2 | Val1,Val2,Val3, 3 | Val4,Val5,Val6 4 | Val7,Val8,,,,, 5 | -------------------------------------------------------------------------------- /tests/univ.exp: -------------------------------------------------------------------------------- 1 | "univ_cons(apple)","univ_cons(orange)","univ_cons(lemon)" 2 | "univ_cons(pear)","univ_cons(apple)","univ_cons(orange)" 3 | -------------------------------------------------------------------------------- /tests/univ.inp: -------------------------------------------------------------------------------- 1 | apple,orange,lemon 2 | "pear","apple","orange" 3 | -------------------------------------------------------------------------------- /tests/unmatched_quote.err_exp: -------------------------------------------------------------------------------- 1 | unmatched_quote.inp:1:7: error: in field #2, missing closing quote 2 | -------------------------------------------------------------------------------- /tests/unmatched_quote.inp: -------------------------------------------------------------------------------- 1 | "abc","def 2 | -------------------------------------------------------------------------------- /tests/unmatched_quote2.err_exp: -------------------------------------------------------------------------------- 1 | unmatched_quote2.inp:1:1: error: in field #1, missing closing quote 2 | -------------------------------------------------------------------------------- /tests/unmatched_quote2.inp: -------------------------------------------------------------------------------- 1 | "abc,"def" 2 | -------------------------------------------------------------------------------- /tests/unmatched_quote3.err_exp: -------------------------------------------------------------------------------- 1 | unmatched_quote3.inp:1:4: error: in field #1, unexpected quote 2 | -------------------------------------------------------------------------------- /tests/unmatched_quote3.inp: -------------------------------------------------------------------------------- 1 | abc","def" 2 | -------------------------------------------------------------------------------- /tests/width_limit.err_exp: -------------------------------------------------------------------------------- 1 | width_limit.inp:1:5: error: in field #2, field width exceeded 2 | -------------------------------------------------------------------------------- /tests/width_limit.inp: -------------------------------------------------------------------------------- 1 | abc,abcd 2 | -------------------------------------------------------------------------------- /tests/width_limit2.err_exp: -------------------------------------------------------------------------------- 1 | width_limit2.inp:1:5: error: in field #2, field width exceeded 2 | -------------------------------------------------------------------------------- /tests/width_limit2.inp: -------------------------------------------------------------------------------- 1 | abc,abcd 2 | -------------------------------------------------------------------------------- /tests/wiki1.exp: -------------------------------------------------------------------------------- 1 | "1997","Ford","E350","" 2 | "1997","Ford","E350","" 3 | "1997","Ford","E350","Super, luxurious truck" 4 | "1997","Ford","E350","Go get one now 5 | they are going fast" 6 | -------------------------------------------------------------------------------- /tests/wiki1.inp: -------------------------------------------------------------------------------- 1 | 1997,Ford,E350, 2 | "1997","Ford","E350", 3 | 1997,Ford,E350,"Super, luxurious truck" 4 | 1997,Ford,E350,"Go get one now 5 | they are going fast" 6 | -------------------------------------------------------------------------------- /tests/wiki2.exp: -------------------------------------------------------------------------------- 1 | "Year","Make","Model","Description","Price" 2 | "1997","Ford","E350","ac, abs, moon","3000.0" 3 | "1999","Chevy","Venture "Extended Edition"","","4900.0" 4 | "1999","Chevy","Venture "Extended Edition, Very Large"","","5000.0" 5 | "1996","Jeep","Grand Cherokee","MUST SELL! 6 | air, moon roof, loaded","4799.0" 7 | -------------------------------------------------------------------------------- /tests/wiki2.inp: -------------------------------------------------------------------------------- 1 | Year,Make,Model,Description,Price 2 | 1997,Ford,E350,"ac, abs, moon",3000.00 3 | 1999,Chevy,"Venture ""Extended Edition""","",4900.00 4 | 1999,Chevy,"Venture ""Extended Edition, Very Large""",,5000.00 5 | 1996,Jeep,Grand Cherokee,"MUST SELL! 6 | air, moon roof, loaded",4799.00 7 | --------------------------------------------------------------------------------