csv-file-writer: Use comma as default delimiter.
[pspp] / src / data / csv-file-writer.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 2010, 2011, 2012, 2013 Free Software Foundation, Inc.
3
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.
8
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.
13
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/>. */
16
17 #include <config.h>
18
19 #include "data/csv-file-writer.h"
20
21 #include <ctype.h>
22 #include <errno.h>
23 #include <math.h>
24 #include <stdint.h>
25 #include <stdlib.h>
26 #include <sys/stat.h>
27 #include <time.h>
28
29 #include "data/calendar.h"
30 #include "data/case.h"
31 #include "data/casewriter-provider.h"
32 #include "data/casewriter.h"
33 #include "data/data-out.h"
34 #include "data/dictionary.h"
35 #include "data/file-handle-def.h"
36 #include "data/file-name.h"
37 #include "data/format.h"
38 #include "data/make-file.h"
39 #include "data/missing-values.h"
40 #include "data/settings.h"
41 #include "data/value-labels.h"
42 #include "data/variable.h"
43 #include "libpspp/assertion.h"
44 #include "libpspp/i18n.h"
45 #include "libpspp/message.h"
46 #include "libpspp/str.h"
47
48 #include "gl/ftoastr.h"
49 #include "gl/minmax.h"
50 #include "gl/unlocked-io.h"
51 #include "gl/xalloc.h"
52
53 #include "gettext.h"
54 #define _(msgid) gettext (msgid)
55 #define N_(msgid) (msgid)
56
57 /* A variable in a CSV file. */
58 struct csv_var
59   {
60     int width;                     /* Variable width (0 to 32767). */
61     int case_index;                /* Index into case. */
62     struct fmt_spec format;        /* Print format. */
63     struct missing_values missing; /* User-missing values, if recoding. */
64     struct val_labs *val_labs;  /* Value labels, if any and they are in use. */
65   };
66
67 /* Comma-separated value (CSV) file writer. */
68 struct csv_writer
69   {
70     struct file_handle *fh;     /* File handle. */
71     struct fh_lock *lock;       /* Mutual exclusion for file. */
72     FILE *file;                 /* File stream. */
73     struct replace_file *rf;    /* Ticket for replacing output file. */
74
75     struct csv_writer_options opts;
76
77     char *encoding;             /* Encoding used by variables. */
78
79     /* Variables. */
80     struct csv_var *csv_vars;   /* Variables. */
81     size_t n_csv_vars;         /* Number of variables. */
82   };
83
84 static const struct casewriter_class csv_file_casewriter_class;
85
86 static void write_var_names (struct csv_writer *, const struct dictionary *);
87
88 static bool write_error (const struct csv_writer *);
89 static bool close_writer (struct csv_writer *);
90
91 /* Initializes OPTS with default options for writing a CSV file. */
92 void
93 csv_writer_options_init (struct csv_writer_options *opts)
94 {
95   opts->recode_user_missing = false;
96   opts->include_var_names = false;
97   opts->use_value_labels = false;
98   opts->use_print_formats = false;
99   opts->decimal = settings_get_decimal_char (FMT_F);
100   opts->delimiter = ',';
101   opts->qualifier = '"';
102 }
103
104 /* Opens the CSV file designated by file handle FH for writing cases from
105    dictionary DICT according to the given OPTS.
106
107    No reference to D is retained, so it may be modified or
108    destroyed at will after this function returns. */
109 struct casewriter *
110 csv_writer_open (struct file_handle *fh, const struct dictionary *dict,
111                  const struct csv_writer_options *opts)
112 {
113   struct csv_writer *w;
114   int i;
115
116   /* Create and initialize writer. */
117   w = xmalloc (sizeof *w);
118   w->fh = fh_ref (fh);
119   w->lock = NULL;
120   w->file = NULL;
121   w->rf = NULL;
122
123   w->opts = *opts;
124
125   w->encoding = xstrdup (dict_get_encoding (dict));
126
127   w->n_csv_vars = dict_get_var_cnt (dict);
128   w->csv_vars = xnmalloc (w->n_csv_vars, sizeof *w->csv_vars);
129   for (i = 0; i < w->n_csv_vars; i++)
130     {
131       const struct variable *var = dict_get_var (dict, i);
132       struct csv_var *cv = &w->csv_vars[i];
133
134       cv->width = var_get_width (var);
135       cv->case_index = var_get_case_index (var);
136
137       cv->format = *var_get_print_format (var);
138       if (opts->recode_user_missing)
139         mv_copy (&cv->missing, var_get_missing_values (var));
140       else
141         mv_init (&cv->missing, cv->width);
142
143       if (opts->use_value_labels)
144         cv->val_labs = val_labs_clone (var_get_value_labels (var));
145       else
146         cv->val_labs = NULL;
147     }
148
149   /* Open file handle as an exclusive writer. */
150   /* TRANSLATORS: this fragment will be interpolated into messages in fh_lock()
151      that identify types of files. */
152   w->lock = fh_lock (fh, FH_REF_FILE, N_("CSV file"), FH_ACC_WRITE, true);
153   if (w->lock == NULL)
154     goto error;
155
156   /* Create the file on disk. */
157   w->rf = replace_file_start (fh_get_file_name (fh), "w", 0666,
158                               &w->file, NULL);
159   if (w->rf == NULL)
160     {
161       msg (ME, _("Error opening `%s' for writing as a system file: %s."),
162            fh_get_file_name (fh), strerror (errno));
163       goto error;
164     }
165
166   if (opts->include_var_names)
167     write_var_names (w, dict);
168
169   if (write_error (w))
170     goto error;
171
172   return casewriter_create (dict_get_proto (dict),
173                             &csv_file_casewriter_class, w);
174
175 error:
176   close_writer (w);
177   return NULL;
178 }
179
180 static bool
181 csv_field_needs_quoting (struct csv_writer *w, const char *s, size_t len)
182 {
183   const char *p;
184
185   for (p = s; p < &s[len]; p++)
186     if (*p == w->opts.qualifier || *p == w->opts.delimiter
187         || *p == '\n' || *p == '\r')
188       return true;
189
190   return false;
191 }
192
193 static void
194 csv_output_buffer (struct csv_writer *w, const char *s, size_t len)
195 {
196   if (csv_field_needs_quoting (w, s, len))
197     {
198       const char *p;
199
200       putc (w->opts.qualifier, w->file);
201       for (p = s; p < &s[len]; p++)
202         {
203           if (*p == w->opts.qualifier)
204             putc (w->opts.qualifier, w->file);
205           putc (*p, w->file);
206         }
207       putc (w->opts.qualifier, w->file);
208     }
209   else
210     fwrite (s, 1, len, w->file);
211 }
212
213 static void
214 csv_output_string (struct csv_writer *w, const char *s)
215 {
216   csv_output_buffer (w, s, strlen (s));
217 }
218
219 static void
220 write_var_names (struct csv_writer *w, const struct dictionary *d)
221 {
222   size_t i;
223
224   for (i = 0; i < w->n_csv_vars; i++)
225     {
226       if (i > 0)
227         putc (w->opts.delimiter, w->file);
228       csv_output_string (w, var_get_name (dict_get_var (d, i)));
229     }
230   putc ('\n', w->file);
231 }
232
233 static void
234 csv_output_format (struct csv_writer *w, const struct csv_var *cv,
235                    const union value *value)
236 {
237   char *s = data_out (value, w->encoding, &cv->format);
238   struct substring ss = ss_cstr (s);
239   if (cv->format.type != FMT_A)
240     ss_trim (&ss, ss_cstr (" "));
241   else
242     ss_rtrim (&ss, ss_cstr (" "));
243   csv_output_buffer (w, ss.string, ss.length);
244   free (s);
245 }
246
247 static double
248 extract_date (double number, int *y, int *m, int *d)
249 {
250   int yd;
251
252   calendar_offset_to_gregorian (number / 60. / 60. / 24., y, m, d, &yd);
253   return fmod (number, 60. * 60. * 24.);
254 }
255
256 static void
257 extract_time (double number, double *H, int *M, int *S)
258 {
259   *H = floor (number / 60. / 60.);
260   number = fmod (number, 60. * 60.);
261
262   *M = floor (number / 60.);
263   number = fmod (number, 60.);
264
265   *S = floor (number);
266 }
267
268 static void
269 csv_write_var__ (struct csv_writer *w, const struct csv_var *cv,
270                  const union value *value)
271 {
272   const char *label;
273
274   label = val_labs_find (cv->val_labs, value);
275   if (label != NULL)
276     csv_output_string (w, label);
277   else if (cv->width == 0 && value->f == SYSMIS)
278     csv_output_buffer (w, " ", 1);
279   else if (w->opts.use_print_formats)
280     csv_output_format (w, cv, value);
281   else
282     {
283       char s[MAX (DBL_STRLEN_BOUND, 128)];
284       char *cp;
285
286       switch (cv->format.type)
287         {
288         case FMT_F:
289         case FMT_COMMA:
290         case FMT_DOT:
291         case FMT_DOLLAR:
292         case FMT_PCT:
293         case FMT_E:
294         case FMT_CCA:
295         case FMT_CCB:
296         case FMT_CCC:
297         case FMT_CCD:
298         case FMT_CCE:
299         case FMT_N:
300         case FMT_Z:
301         case FMT_P:
302         case FMT_PK:
303         case FMT_IB:
304         case FMT_PIB:
305         case FMT_PIBHEX:
306         case FMT_RB:
307         case FMT_RBHEX:
308         case FMT_WKDAY:
309         case FMT_MONTH:
310           dtoastr (s, sizeof s, 0, 0, value->f);
311           cp = strpbrk (s, ".,");
312           if (cp != NULL)
313             *cp = w->opts.decimal;
314           break;
315
316         case FMT_DATE:
317         case FMT_ADATE:
318         case FMT_EDATE:
319         case FMT_JDATE:
320         case FMT_SDATE:
321         case FMT_QYR:
322         case FMT_MOYR:
323         case FMT_WKYR:
324           if (value->f < 0)
325             strcpy (s, " ");
326           else
327             {
328               int y, m, d;
329
330               extract_date (value->f, &y, &m, &d);
331               snprintf (s, sizeof s, "%02d/%02d/%04d", m, d, y);
332             }
333           break;
334
335         case FMT_DATETIME:
336           if (value->f < 0)
337             strcpy (s, " ");
338           else
339             {
340               int y, m, d, M, S;
341               double H;
342
343               extract_time (extract_date (value->f, &y, &m, &d), &H, &M, &S);
344               snprintf (s, sizeof s, "%02d/%02d/%04d %02.0f:%02d:%02d",
345                         m, d, y, H, M, S);
346             }
347           break;
348
349         case FMT_TIME:
350         case FMT_DTIME:
351           {
352             double H;
353             int M, S;
354
355             extract_time (fabs (value->f), &H, &M, &S);
356             snprintf (s, sizeof s, "%s%02.0f:%02d:%02d",
357                       value->f < 0 ? "-" : "", H, M, S);
358           }
359           break;
360
361         case FMT_A:
362         case FMT_AHEX:
363           csv_output_format (w, cv, value);
364           return;
365
366         case FMT_NUMBER_OF_FORMATS:
367           NOT_REACHED ();
368         }
369       csv_output_string (w, s);
370     }
371 }
372
373 static void
374 csv_write_var (struct csv_writer *w, const struct csv_var *cv,
375                const union value *value)
376 {
377   if (mv_is_value_missing (&cv->missing, value, MV_USER))
378     {
379       union value missing;
380
381       value_init (&missing, cv->width);
382       value_set_missing (&missing, cv->width);
383       csv_write_var__ (w, cv, &missing);
384       value_destroy (&missing, cv->width);
385     }
386   else
387     csv_write_var__ (w, cv, value);
388 }
389
390 static void
391 csv_write_case (struct csv_writer *w, const struct ccase *c)
392 {
393   size_t i;
394
395   for (i = 0; i < w->n_csv_vars; i++)
396     {
397       const struct csv_var *cv = &w->csv_vars[i];
398
399       if (i > 0)
400         putc (w->opts.delimiter, w->file);
401       csv_write_var (w, cv, case_data_idx (c, cv->case_index));
402     }
403   putc ('\n', w->file);
404 }
405 \f
406 /* Writes case C to CSV file W. */
407 static void
408 csv_file_casewriter_write (struct casewriter *writer, void *w_,
409                            struct ccase *c)
410 {
411   struct csv_writer *w = w_;
412
413   if (ferror (w->file))
414     {
415       casewriter_force_error (writer);
416       case_unref (c);
417       return;
418     }
419
420   csv_write_case (w, c);
421   case_unref (c);
422 }
423
424 /* Destroys CSV file writer W. */
425 static void
426 csv_file_casewriter_destroy (struct casewriter *writer, void *w_)
427 {
428   struct csv_writer *w = w_;
429   if (!close_writer (w))
430     casewriter_force_error (writer);
431 }
432
433 /* Returns true if an I/O error has occurred on WRITER, false otherwise. */
434 bool
435 write_error (const struct csv_writer *writer)
436 {
437   return ferror (writer->file);
438 }
439
440 /* Closes a CSV file after we're done with it.
441    Returns true if successful, false if an I/O error occurred. */
442 bool
443 close_writer (struct csv_writer *w)
444 {
445   size_t i;
446   bool ok;
447
448   if (w == NULL)
449     return true;
450
451   ok = true;
452   if (w->file != NULL)
453     {
454       if (write_error (w))
455         ok = false;
456       if (fclose (w->file) == EOF)
457         ok = false;
458
459       if (!ok)
460         msg (ME, _("An I/O error occurred writing CSV file `%s'."),
461              fh_get_file_name (w->fh));
462
463       if (ok ? !replace_file_commit (w->rf) : !replace_file_abort (w->rf))
464         ok = false;
465     }
466
467   fh_unlock (w->lock);
468   fh_unref (w->fh);
469
470   free (w->encoding);
471
472   for (i = 0; i < w->n_csv_vars; i++)
473     {
474       struct csv_var *cv = &w->csv_vars[i];
475       mv_destroy (&cv->missing);
476       val_labs_destroy (cv->val_labs);
477     }
478
479   free (w->csv_vars);
480   free (w);
481
482   return ok;
483 }
484
485 /* CSV file writer casewriter class. */
486 static const struct casewriter_class csv_file_casewriter_class =
487   {
488     csv_file_casewriter_write,
489     csv_file_casewriter_destroy,
490     NULL,
491   };