1 /* PSPP - a program for statistical analysis.
2 Copyright (C) 2007, 2009, 2010, 2011, 2012, 2013, 2016 Free Software Foundation, Inc.
4 This program is free software: you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation, either version 3 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program. If not, see <http://www.gnu.org/licenses/>. */
19 #include "language/data-io/data-parser.h"
24 #include "data/casereader-provider.h"
25 #include "data/data-in.h"
26 #include "data/dataset.h"
27 #include "data/dictionary.h"
28 #include "data/format.h"
29 #include "data/file-handle-def.h"
30 #include "data/settings.h"
31 #include "language/data-io/data-reader.h"
32 #include "libpspp/intern.h"
33 #include "libpspp/message.h"
34 #include "libpspp/str.h"
35 #include "libpspp/string-array.h"
36 #include "output/pivot-table.h"
38 #include "gl/xalloc.h"
41 #define N_(msgid) msgid
42 #define _(msgid) gettext (msgid)
44 /* Data parser for textual data like that read by DATA LIST. */
47 enum data_parser_type type; /* Type of data to parse. */
48 int skip_records; /* Records to skip before first real data. */
50 struct field *fields; /* Fields to parse. */
51 size_t n_fields; /* Number of fields. */
52 size_t field_allocated; /* Number of fields spaced allocated for. */
54 /* DP_DELIMITED parsers only. */
55 bool span; /* May cases span multiple records? */
56 bool empty_line_has_field; /* Does an empty line have an (empty) field? */
57 bool warn_missing_fields; /* Should missing fields be considered errors? */
58 struct substring quotes; /* Characters that can quote separators. */
59 bool quote_escape; /* Doubled quote acts as escape? */
60 struct substring soft_seps; /* Two soft separators act like just one. */
61 struct substring hard_seps; /* Two hard separators yield empty fields. */
62 struct string any_sep; /* Concatenation of soft_seps and hard_seps. */
64 /* DP_FIXED parsers only. */
65 int records_per_case; /* Number of records in each case. */
68 /* How to parse one variable. */
71 struct fmt_spec format; /* Input format of this field. */
72 int case_idx; /* First value in case. */
73 char *name; /* Var name for error messages and tables. */
76 int record; /* Record number (1-based). */
77 int first_column; /* First column in record (1-based). */
80 static void set_any_sep (struct data_parser *parser);
82 /* Creates and returns a new data parser. */
84 data_parser_create (void)
86 struct data_parser *parser = xmalloc (sizeof *parser);
87 *parser = (struct data_parser) {
90 .warn_missing_fields = true,
91 .quotes = ss_clone (ss_cstr ("\"'")),
92 .soft_seps = ss_clone (ss_cstr (CC_SPACES)),
93 .hard_seps = ss_clone (ss_cstr (",")),
100 /* Destroys PARSER. */
102 data_parser_destroy (struct data_parser *parser)
108 for (i = 0; i < parser->n_fields; i++)
109 free (parser->fields[i].name);
110 free (parser->fields);
111 ss_dealloc (&parser->quotes);
112 ss_dealloc (&parser->soft_seps);
113 ss_dealloc (&parser->hard_seps);
114 ds_destroy (&parser->any_sep);
119 /* Returns the type of PARSER (either DP_DELIMITED or DP_FIXED). */
120 enum data_parser_type
121 data_parser_get_type (const struct data_parser *parser)
126 /* Sets the type of PARSER to TYPE (either DP_DELIMITED or
129 data_parser_set_type (struct data_parser *parser, enum data_parser_type type)
131 assert (parser->n_fields == 0);
132 assert (type == DP_FIXED || type == DP_DELIMITED);
136 /* Configures PARSER to skip the specified number of
137 INITIAL_RECORDS_TO_SKIP before parsing any data. By default,
138 no records are skipped. */
140 data_parser_set_skip (struct data_parser *parser, int initial_records_to_skip)
142 assert (initial_records_to_skip >= 0);
143 parser->skip_records = initial_records_to_skip;
146 /* Returns true if PARSER is configured to allow cases to span
149 data_parser_get_span (const struct data_parser *parser)
154 /* If MAY_CASES_SPAN_RECORDS is true, configures PARSER to allow
155 a single case to span multiple records and multiple cases to
156 occupy a single record. If MAY_CASES_SPAN_RECORDS is false,
157 configures PARSER to require each record to contain exactly
160 This setting affects parsing of DP_DELIMITED files only. */
162 data_parser_set_span (struct data_parser *parser, bool may_cases_span_records)
164 parser->span = may_cases_span_records;
167 /* If EMPTY_LINE_HAS_FIELD is true, configures PARSER to parse an
168 empty line as an empty field and to treat a hard delimiter
169 followed by end-of-line as an empty field. If
170 EMPTY_LINE_HAS_FIELD is false, PARSER will skip empty lines
171 and hard delimiters at the end of lines without emitting empty
174 This setting affects parsing of DP_DELIMITED files only. */
176 data_parser_set_empty_line_has_field (struct data_parser *parser,
177 bool empty_line_has_field)
179 parser->empty_line_has_field = empty_line_has_field;
183 /* If WARN_MISSING_FIELDS is true, configures PARSER to emit a warning
184 and cause an error condition when a missing field is encountered.
185 If WARN_MISSING_FIELDS is false, PARSER will silently fill such
186 fields with the system missing value.
188 This setting affects parsing of DP_DELIMITED files only. */
190 data_parser_set_warn_missing_fields (struct data_parser *parser,
191 bool warn_missing_fields)
193 parser->warn_missing_fields = warn_missing_fields;
197 /* Sets the characters that may be used for quoting field
198 contents to QUOTES. If QUOTES is empty, quoting will be
201 The caller retains ownership of QUOTES.
203 This setting affects parsing of DP_DELIMITED files only. */
205 data_parser_set_quotes (struct data_parser *parser, struct substring quotes)
207 ss_dealloc (&parser->quotes);
208 parser->quotes = ss_clone (quotes);
211 /* If ESCAPE is false (the default setting), a character used for
212 quoting cannot itself be embedded within a quoted field. If
213 ESCAPE is true, then a quote character can be embedded within
214 a quoted field by doubling it.
216 This setting affects parsing of DP_DELIMITED files only, and
217 only when at least one quote character has been set (with
218 data_parser_set_quotes). */
220 data_parser_set_quote_escape (struct data_parser *parser, bool escape)
222 parser->quote_escape = escape;
225 /* Sets PARSER's soft delimiters to DELIMITERS. Soft delimiters
226 separate fields, but consecutive soft delimiters do not yield
227 empty fields. (Ordinarily, only white space characters are
228 appropriate soft delimiters.)
230 The caller retains ownership of DELIMITERS.
232 This setting affects parsing of DP_DELIMITED files only. */
234 data_parser_set_soft_delimiters (struct data_parser *parser,
235 struct substring delimiters)
237 ss_dealloc (&parser->soft_seps);
238 parser->soft_seps = ss_clone (delimiters);
239 set_any_sep (parser);
242 /* Sets PARSER's hard delimiters to DELIMITERS. Hard delimiters
243 separate fields. A consecutive pair of hard delimiters yield
246 The caller retains ownership of DELIMITERS.
248 This setting affects parsing of DP_DELIMITED files only. */
250 data_parser_set_hard_delimiters (struct data_parser *parser,
251 struct substring delimiters)
253 ss_dealloc (&parser->hard_seps);
254 parser->hard_seps = ss_clone (delimiters);
255 set_any_sep (parser);
258 /* Returns the number of records per case. */
260 data_parser_get_records (const struct data_parser *parser)
262 return parser->records_per_case;
265 /* Sets the number of records per case to RECORDS_PER_CASE.
267 This setting affects parsing of DP_FIXED files only. */
269 data_parser_set_records (struct data_parser *parser, int records_per_case)
271 assert (records_per_case >= 0);
272 assert (records_per_case >= parser->records_per_case);
273 parser->records_per_case = records_per_case;
277 add_field (struct data_parser *p, const struct fmt_spec *format, int case_idx,
278 const char *name, int record, int first_column)
282 if (p->n_fields == p->field_allocated)
283 p->fields = x2nrealloc (p->fields, &p->field_allocated, sizeof *p->fields);
284 field = &p->fields[p->n_fields++];
285 field->format = *format;
286 field->case_idx = case_idx;
287 field->name = xstrdup (name);
288 field->record = record;
289 field->first_column = first_column;
292 /* Adds a delimited field to the field parsed by PARSER, which
293 must be configured as a DP_DELIMITED parser. The field is
294 parsed as input format FORMAT. Its data will be stored into case
295 index CASE_INDEX. Errors in input data will be reported
296 against variable NAME. */
298 data_parser_add_delimited_field (struct data_parser *parser,
299 const struct fmt_spec *format, int case_idx,
302 assert (parser->type == DP_DELIMITED);
303 add_field (parser, format, case_idx, name, 0, 0);
306 /* Adds a fixed field to the field parsed by PARSER, which
307 must be configured as a DP_FIXED parser. The field is
308 parsed as input format FORMAT. Its data will be stored into case
309 index CASE_INDEX. Errors in input data will be reported
310 against variable NAME. The field will be drawn from the
311 FORMAT->w columns in 1-based RECORD starting at 1-based
314 RECORD must be at least as great as that of any field already
315 added; that is, fields must be added in increasing order of
316 record number. If RECORD is greater than the current number
317 of records per case, the number of records per case are
318 increased as needed. */
320 data_parser_add_fixed_field (struct data_parser *parser,
321 const struct fmt_spec *format, int case_idx,
323 int record, int first_column)
325 assert (parser->type == DP_FIXED);
326 assert (parser->n_fields == 0
327 || record >= parser->fields[parser->n_fields - 1].record);
328 if (record > parser->records_per_case)
329 parser->records_per_case = record;
330 add_field (parser, format, case_idx, name, record, first_column);
333 /* Returns true if any fields have been added to PARSER, false
336 data_parser_any_fields (const struct data_parser *parser)
338 return parser->n_fields > 0;
342 set_any_sep (struct data_parser *parser)
344 ds_assign_substring (&parser->any_sep, parser->soft_seps);
345 ds_put_substring (&parser->any_sep, parser->hard_seps);
348 static bool parse_delimited_span (const struct data_parser *,
350 struct dictionary *, struct ccase *);
351 static bool parse_delimited_no_span (const struct data_parser *,
353 struct dictionary *, struct ccase *);
354 static bool parse_fixed (const struct data_parser *, struct dfm_reader *,
355 struct dictionary *, struct ccase *);
357 /* Reads a case from DFM into C, which matches dictionary DICT, parsing it with
358 PARSER. Returns true if successful, false at end of file or on I/O error.
360 Case C must not be shared. */
362 data_parser_parse (struct data_parser *parser, struct dfm_reader *reader,
363 struct dictionary *dict, struct ccase *c)
367 assert (!case_is_shared (c));
368 assert (data_parser_any_fields (parser));
370 /* Skip the requested number of records before reading the
372 for (; parser->skip_records > 0; parser->skip_records--)
374 if (dfm_eof (reader))
376 dfm_forward_record (reader);
380 if (parser->type == DP_DELIMITED)
383 retval = parse_delimited_span (parser, reader, dict, c);
385 retval = parse_delimited_no_span (parser, reader, dict, c);
388 retval = parse_fixed (parser, reader, dict, c);
394 cut_field__ (const struct data_parser *parser, const struct substring *line,
395 struct substring *p, size_t *n_columns,
396 struct string *tmp, struct substring *field)
398 bool quoted = ss_find_byte (parser->quotes, ss_first (*p)) != SIZE_MAX;
402 int quote = ss_get_byte (p);
403 if (!ss_get_until (p, quote, field))
404 msg (DW, _("Quoted string extends beyond end of line."));
405 if (parser->quote_escape && ss_first (*p) == quote)
407 ds_assign_substring (tmp, *field);
408 while (ss_match_byte (p, quote))
411 ds_put_byte (tmp, quote);
412 if (!ss_get_until (p, quote, &ss))
413 msg (DW, _("Quoted string extends beyond end of line."));
414 ds_put_substring (tmp, ss);
416 *field = ds_ss (tmp);
418 *n_columns = ss_length (*line) - ss_length (*p);
423 ss_get_bytes (p, ss_cspan (*p, ds_ss (&parser->any_sep)), field);
424 *n_columns = ss_length (*field);
427 /* Skip trailing soft separator and a single hard separator if present. */
428 size_t length_before_separators = ss_length (*p);
429 ss_ltrim (p, parser->soft_seps);
430 if (!ss_is_empty (*p)
431 && ss_find_byte (parser->hard_seps, ss_first (*p)) != SIZE_MAX)
434 ss_ltrim (p, parser->soft_seps);
437 if (!ss_is_empty (*p) && quoted && length_before_separators == ss_length (*p))
438 msg (DW, _("Missing delimiter following quoted string."));
441 /* Extracts a delimited field from the current position in the
442 current record according to PARSER, reading data from READER.
444 *FIELD is set to the field content. The caller must not or
445 destroy this constant string.
447 Sets *FIRST_COLUMN to the 1-based column number of the start of
448 the extracted field, and *LAST_COLUMN to the end of the extracted
451 Returns true on success, false on failure. */
453 cut_field (const struct data_parser *parser, struct dfm_reader *reader,
454 int *first_column, int *last_column, struct string *tmp,
455 struct substring *field)
457 struct substring line, p;
459 if (dfm_eof (reader))
461 if (ss_is_empty (parser->hard_seps))
462 dfm_expand_tabs (reader);
463 line = p = dfm_get_record (reader);
465 /* Skip leading soft separators. */
466 ss_ltrim (&p, parser->soft_seps);
468 /* Handle empty or completely consumed lines. */
471 if (!parser->empty_line_has_field || dfm_columns_past_end (reader) > 0)
476 *first_column = dfm_column_start (reader);
477 *last_column = *first_column + 1;
478 dfm_forward_columns (reader, 1);
484 cut_field__ (parser, &line, &p, &n_columns, tmp, field);
485 *first_column = dfm_column_start (reader);
486 *last_column = *first_column + n_columns;
489 dfm_forward_columns (reader, 1);
490 dfm_forward_columns (reader, ss_length (line) - ss_length (p));
496 parse_error (const struct dfm_reader *reader, const struct field *field,
497 int first_column, int last_column, char *error)
499 int line_number = dfm_get_line_number (reader);
500 struct msg_location *location = xmalloc (sizeof *location);
501 *location = (struct msg_location) {
502 .file_name = intern_new (dfm_get_file_name (reader)),
503 .start = { .line = line_number, .column = first_column },
504 .end = { .line = line_number, .column = last_column - 1 },
506 struct msg *m = xmalloc (sizeof *m);
508 .category = MSG_C_DATA,
509 .severity = MSG_S_WARNING,
510 .location = location,
511 .text = xasprintf (_("Data for variable %s is not valid as format %s: %s"),
512 field->name, fmt_name (field->format.type), error),
519 /* Reads a case from READER into C, which matches DICT, parsing it according to
520 fixed-format syntax rules in PARSER. Returns true if successful, false at
521 end of file or on I/O error. */
523 parse_fixed (const struct data_parser *parser, struct dfm_reader *reader,
524 struct dictionary *dict, struct ccase *c)
526 const char *input_encoding = dfm_reader_get_encoding (reader);
527 const char *output_encoding = dict_get_encoding (dict);
531 if (dfm_eof (reader))
535 for (row = 1; row <= parser->records_per_case; row++)
537 struct substring line;
539 if (dfm_eof (reader))
541 msg (DW, _("Partial case of %d of %d records discarded."),
542 row - 1, parser->records_per_case);
545 dfm_expand_tabs (reader);
546 line = dfm_get_record (reader);
548 for (; f < &parser->fields[parser->n_fields] && f->record == row; f++)
550 struct substring s = ss_substr (line, f->first_column - 1,
552 union value *value = case_data_rw_idx (c, f->case_idx);
553 char *error = data_in (s, input_encoding, f->format.type,
554 settings_get_fmt_settings (),
555 value, fmt_var_width (&f->format),
559 data_in_imply_decimals (s, input_encoding, f->format.type,
560 f->format.d, settings_get_fmt_settings (),
563 parse_error (reader, f, f->first_column,
564 f->first_column + f->format.w, error);
567 dfm_forward_record (reader);
573 /* Splits the data line in LINE into individual text fields and returns the
574 number of fields. If SA is nonnull, appends each field to SA; the caller
575 retains ownership of SA and its contents. */
577 data_parser_split (const struct data_parser *parser,
578 struct substring line, struct string_array *sa)
582 struct string tmp = DS_EMPTY_INITIALIZER;
585 struct substring p = line;
586 ss_ltrim (&p, parser->soft_seps);
594 struct substring field;
597 cut_field__ (parser, &line, &p, &n_columns, &tmp, &field);
601 string_array_append_nocopy (sa, ss_xstrdup (field));
607 /* Reads a case from READER into C, which matches dictionary DICT, parsing it
608 according to free-format syntax rules in PARSER. Returns true if
609 successful, false at end of file or on I/O error. */
611 parse_delimited_span (const struct data_parser *parser,
612 struct dfm_reader *reader,
613 struct dictionary *dict, struct ccase *c)
615 const char *output_encoding = dict_get_encoding (dict);
616 struct string tmp = DS_EMPTY_INITIALIZER;
619 for (f = parser->fields; f < &parser->fields[parser->n_fields]; f++)
622 int first_column, last_column;
625 /* Cut out a field and read in a new record if necessary. */
626 while (!cut_field (parser, reader,
627 &first_column, &last_column, &tmp, &s))
629 if (!dfm_eof (reader))
630 dfm_forward_record (reader);
631 if (dfm_eof (reader))
633 if (f > parser->fields)
634 msg (DW, _("Partial case discarded. The first variable "
635 "missing was %s."), f->name);
641 const char *input_encoding = dfm_reader_get_encoding (reader);
642 error = data_in (s, input_encoding, f->format.type,
643 settings_get_fmt_settings (),
644 case_data_rw_idx (c, f->case_idx),
645 fmt_var_width (&f->format), output_encoding);
647 parse_error (reader, f, first_column, last_column, error);
653 /* Reads a case from READER into C, which matches dictionary DICT, parsing it
654 according to delimited syntax rules with one case per record in PARSER.
655 Returns true if successful, false at end of file or on I/O error. */
657 parse_delimited_no_span (const struct data_parser *parser,
658 struct dfm_reader *reader,
659 struct dictionary *dict, struct ccase *c)
661 const char *output_encoding = dict_get_encoding (dict);
662 struct string tmp = DS_EMPTY_INITIALIZER;
664 struct field *f, *end;
666 if (dfm_eof (reader))
669 end = &parser->fields[parser->n_fields];
670 for (f = parser->fields; f < end; f++)
672 int first_column, last_column;
675 if (!cut_field (parser, reader, &first_column, &last_column, &tmp, &s))
677 if (f < end - 1 && settings_get_undefined () && parser->warn_missing_fields)
678 msg (DW, _("Missing value(s) for all variables from %s onward. "
679 "These will be filled with the system-missing value "
680 "or blanks, as appropriate."),
683 value_set_missing (case_data_rw_idx (c, f->case_idx),
684 fmt_var_width (&f->format));
688 const char *input_encoding = dfm_reader_get_encoding (reader);
689 error = data_in (s, input_encoding, f->format.type,
690 settings_get_fmt_settings (),
691 case_data_rw_idx (c, f->case_idx),
692 fmt_var_width (&f->format), output_encoding);
694 parse_error (reader, f, first_column, last_column, error);
697 s = dfm_get_record (reader);
698 ss_ltrim (&s, parser->soft_seps);
699 if (!ss_is_empty (s))
700 msg (DW, _("Record ends in data not part of any field."));
703 dfm_forward_record (reader);
708 /* Displays a table giving information on fixed-format variable
709 parsing on DATA LIST. */
711 dump_fixed_table (const struct data_parser *parser,
712 const struct file_handle *fh)
714 /* XXX This should not be preformatted. */
715 char *title = xasprintf (ngettext ("Reading %d record from %s.",
716 "Reading %d records from %s.",
717 parser->records_per_case),
718 parser->records_per_case, fh_get_name (fh));
719 struct pivot_table *table = pivot_table_create__ (
720 pivot_value_new_user_text (title, -1), "Fixed Data Records");
723 pivot_dimension_create (
724 table, PIVOT_AXIS_COLUMN, N_("Attributes"),
725 N_("Record"), N_("Columns"), N_("Format"));
727 struct pivot_dimension *variables = pivot_dimension_create (
728 table, PIVOT_AXIS_ROW, N_("Variable"));
729 variables->root->show_label = true;
730 for (size_t i = 0; i < parser->n_fields; i++)
732 struct field *f = &parser->fields[i];
734 /* XXX It would be better to have the actual variable here. */
735 int variable_idx = pivot_category_create_leaf (
736 variables->root, pivot_value_new_user_text (f->name, -1));
738 pivot_table_put2 (table, 0, variable_idx,
739 pivot_value_new_integer (f->record));
741 int first_column = f->first_column;
742 int last_column = f->first_column + f->format.w - 1;
743 char *columns = xasprintf ("%d-%d", first_column, last_column);
744 pivot_table_put2 (table, 1, variable_idx,
745 pivot_value_new_user_text (columns, -1));
748 char str[FMT_STRING_LEN_MAX + 1];
749 pivot_table_put2 (table, 2, variable_idx,
750 pivot_value_new_user_text (
751 fmt_to_string (&f->format, str), -1));
755 pivot_table_submit (table);
758 /* Displays a table giving information on free-format variable parsing
761 dump_delimited_table (const struct data_parser *parser,
762 const struct file_handle *fh)
764 struct pivot_table *table = pivot_table_create__ (
765 pivot_value_new_text_format (N_("Reading free-form data from %s."),
767 "Free-Form Data Records");
769 pivot_dimension_create (
770 table, PIVOT_AXIS_COLUMN, N_("Attributes"), N_("Format"));
772 struct pivot_dimension *variables = pivot_dimension_create (
773 table, PIVOT_AXIS_ROW, N_("Variable"));
774 variables->root->show_label = true;
775 for (size_t i = 0; i < parser->n_fields; i++)
777 struct field *f = &parser->fields[i];
779 /* XXX It would be better to have the actual variable here. */
780 int variable_idx = pivot_category_create_leaf (
781 variables->root, pivot_value_new_user_text (f->name, -1));
783 char str[FMT_STRING_LEN_MAX + 1];
784 pivot_table_put2 (table, 0, variable_idx,
785 pivot_value_new_user_text (
786 fmt_to_string (&f->format, str), -1));
789 pivot_table_submit (table);
792 /* Displays a table giving information on how PARSER will read
795 data_parser_output_description (struct data_parser *parser,
796 const struct file_handle *fh)
798 if (parser->type == DP_FIXED)
799 dump_fixed_table (parser, fh);
801 dump_delimited_table (parser, fh);
804 /* Data parser input program. */
805 struct data_parser_casereader
807 struct data_parser *parser; /* Parser. */
808 struct dictionary *dict; /* Dictionary. */
809 struct dfm_reader *reader; /* Data file reader. */
810 struct caseproto *proto; /* Format of cases. */
813 static const struct casereader_class data_parser_casereader_class;
815 /* Replaces DS's active dataset by an input program that reads data
816 from READER according to the rules in PARSER, using DICT as
817 the underlying dictionary. Ownership of PARSER and READER is
818 transferred to the input program, and ownership of DICT is
819 transferred to the dataset. */
821 data_parser_make_active_file (struct data_parser *parser, struct dataset *ds,
822 struct dfm_reader *reader,
823 struct dictionary *dict,
824 struct casereader* (*func)(struct casereader *,
825 const struct dictionary *,
829 struct data_parser_casereader *r;
830 struct casereader *casereader0;
831 struct casereader *casereader1;
833 r = xmalloc (sizeof *r);
835 r->dict = dict_ref (dict);
837 r->proto = caseproto_ref (dict_get_proto (dict));
838 casereader0 = casereader_create_sequential (NULL, r->proto,
840 &data_parser_casereader_class, r);
843 casereader1 = func (casereader0, dict, ud);
845 casereader1 = casereader0;
847 dataset_set_dict (ds, dict);
848 dataset_set_source (ds, casereader1);
852 static struct ccase *
853 data_parser_casereader_read (struct casereader *reader UNUSED, void *r_)
855 struct data_parser_casereader *r = r_;
856 struct ccase *c = case_create (r->proto);
857 if (data_parser_parse (r->parser, r->reader, r->dict, c))
867 data_parser_casereader_destroy (struct casereader *reader, void *r_)
869 struct data_parser_casereader *r = r_;
870 if (dfm_reader_error (r->reader))
871 casereader_force_error (reader);
872 dfm_close_reader (r->reader);
873 caseproto_unref (r->proto);
874 dict_unref (r->dict);
875 data_parser_destroy (r->parser);
879 static const struct casereader_class data_parser_casereader_class =
881 data_parser_casereader_read,
882 data_parser_casereader_destroy,