70568c3e1d4dfe35fc10857d0aaf6ee1fe654c72
[pspp-builds.git] / src / data / csv-file-writer.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 2010 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/unlocked-io.h"
49 #include "gl/xalloc.h"
50
51 #include "gettext.h"
52 #define _(msgid) gettext (msgid)
53 #define N_(msgid) (msgid)
54
55 /* A variable in a CSV file. */
56 struct csv_var
57   {
58     int width;                     /* Variable width (0 to 32767). */
59     int case_index;                /* Index into case. */
60     struct fmt_spec format;        /* Print format. */
61     struct missing_values missing; /* User-missing values, if recoding. */
62     struct val_labs *val_labs;  /* Value labels, if any and they are in use. */
63   };
64
65 /* Comma-separated value (CSV) file writer. */
66 struct csv_writer
67   {
68     struct file_handle *fh;     /* File handle. */
69     struct fh_lock *lock;       /* Mutual exclusion for file. */
70     FILE *file;                 /* File stream. */
71     struct replace_file *rf;    /* Ticket for replacing output file. */
72
73     struct csv_writer_options opts;
74
75     char *encoding;             /* Encoding used by variables. */
76
77     /* Variables. */
78     struct csv_var *csv_vars;   /* Variables. */
79     size_t n_csv_vars;         /* Number of variables. */
80   };
81
82 static const struct casewriter_class csv_file_casewriter_class;
83
84 static void write_var_names (struct csv_writer *, const struct dictionary *);
85
86 static bool write_error (const struct csv_writer *);
87 static bool close_writer (struct csv_writer *);
88
89 /* Initializes OPTS with default options for writing a CSV file. */
90 void
91 csv_writer_options_init (struct csv_writer_options *opts)
92 {
93   opts->recode_user_missing = false;
94   opts->include_var_names = false;
95   opts->use_value_labels = false;
96   opts->use_print_formats = false;
97   opts->decimal = settings_get_decimal_char (FMT_F);
98   opts->delimiter = 0;
99   opts->qualifier = '"';
100 }
101
102 /* Opens the CSV file designated by file handle FH for writing cases from
103    dictionary DICT according to the given OPTS.
104
105    No reference to D is retained, so it may be modified or
106    destroyed at will after this function returns. */
107 struct casewriter *
108 csv_writer_open (struct file_handle *fh, const struct dictionary *dict,
109                  const struct csv_writer_options *opts)
110 {
111   struct csv_writer *w;
112   int i;
113
114   /* Create and initialize writer. */
115   w = xmalloc (sizeof *w);
116   w->fh = fh_ref (fh);
117   w->lock = NULL;
118   w->file = NULL;
119   w->rf = NULL;
120
121   w->opts = *opts;
122
123   w->encoding = (dict_get_encoding (dict)
124                  ? xstrdup (dict_get_encoding (dict))
125                  : NULL);
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[128];
284
285       switch (cv->format.type)
286         {
287         case FMT_F:
288         case FMT_COMMA:
289         case FMT_DOT:
290         case FMT_DOLLAR:
291         case FMT_PCT:
292         case FMT_E:
293         case FMT_CCA:
294         case FMT_CCB:
295         case FMT_CCC:
296         case FMT_CCD:
297         case FMT_CCE:
298         case FMT_N:
299         case FMT_Z:
300         case FMT_P:
301         case FMT_PK:
302         case FMT_IB:
303         case FMT_PIB:
304         case FMT_PIBHEX:
305         case FMT_RB:
306         case FMT_RBHEX:
307         case FMT_WKDAY:
308         case FMT_MONTH:
309           snprintf (s, sizeof s, "%.*g", DBL_DIG + 1, value->f);
310           if (w->opts.decimal != '.')
311             {
312               char *cp = strchr (s, '.');
313               if (cp != NULL)
314                 *cp = w->opts.decimal;
315             }
316           break;
317
318         case FMT_DATE:
319         case FMT_ADATE:
320         case FMT_EDATE:
321         case FMT_JDATE:
322         case FMT_SDATE:
323         case FMT_QYR:
324         case FMT_MOYR:
325         case FMT_WKYR:
326           if (value->f < 0)
327             strcpy (s, " ");
328           else
329             {
330               int y, m, d;
331
332               extract_date (value->f, &y, &m, &d);
333               snprintf (s, sizeof s, "%02d/%02d/%04d", m, d, y);
334             }
335           break;
336
337         case FMT_DATETIME:
338           if (value->f < 0)
339             strcpy (s, " ");
340           else
341             {
342               int y, m, d, M, S;
343               double H;
344
345               extract_time (extract_date (value->f, &y, &m, &d), &H, &M, &S);
346               snprintf (s, sizeof s, "%02d/%02d/%04d %02.0f:%02d:%02d",
347                         m, d, y, H, M, S);
348             }
349           break;
350
351         case FMT_TIME:
352         case FMT_DTIME:
353           {
354             double H;
355             int M, S;
356
357             extract_time (fabs (value->f), &H, &M, &S);
358             snprintf (s, sizeof s, "%s%02.0f:%02d:%02d",
359                       value->f < 0 ? "-" : "", H, M, S);
360           }
361           break;
362
363         case FMT_A:
364         case FMT_AHEX:
365           csv_output_format (w, cv, value);
366           return;
367
368         case FMT_NUMBER_OF_FORMATS:
369           NOT_REACHED ();
370         }
371       csv_output_string (w, s);
372     }
373 }
374
375 static void
376 csv_write_var (struct csv_writer *w, const struct csv_var *cv,
377                const union value *value)
378 {
379   if (mv_is_value_missing (&cv->missing, value, MV_USER))
380     {
381       union value missing;
382
383       value_init (&missing, cv->width);
384       value_set_missing (&missing, cv->width);
385       csv_write_var__ (w, cv, &missing);
386       value_destroy (&missing, cv->width);
387     }
388   else
389     csv_write_var__ (w, cv, value);
390 }
391
392 static void
393 csv_write_case (struct csv_writer *w, const struct ccase *c)
394 {
395   size_t i;
396
397   for (i = 0; i < w->n_csv_vars; i++)
398     {
399       const struct csv_var *cv = &w->csv_vars[i];
400
401       if (i > 0)
402         putc (w->opts.delimiter, w->file);
403       csv_write_var (w, cv, case_data_idx (c, cv->case_index));
404     }
405   putc ('\n', w->file);
406 }
407 \f
408 /* Writes case C to CSV file W. */
409 static void
410 csv_file_casewriter_write (struct casewriter *writer, void *w_,
411                            struct ccase *c)
412 {
413   struct csv_writer *w = w_;
414
415   if (ferror (w->file))
416     {
417       casewriter_force_error (writer);
418       case_unref (c);
419       return;
420     }
421
422   csv_write_case (w, c);
423   case_unref (c);
424 }
425
426 /* Destroys CSV file writer W. */
427 static void
428 csv_file_casewriter_destroy (struct casewriter *writer, void *w_)
429 {
430   struct csv_writer *w = w_;
431   if (!close_writer (w))
432     casewriter_force_error (writer);
433 }
434
435 /* Returns true if an I/O error has occurred on WRITER, false otherwise. */
436 bool
437 write_error (const struct csv_writer *writer)
438 {
439   return ferror (writer->file);
440 }
441
442 /* Closes a CSV file after we're done with it.
443    Returns true if successful, false if an I/O error occurred. */
444 bool
445 close_writer (struct csv_writer *w)
446 {
447   size_t i;
448   bool ok;
449
450   if (w == NULL)
451     return true;
452
453   ok = true;
454   if (w->file != NULL)
455     {
456       if (write_error (w))
457         ok = false;
458       if (fclose (w->file) == EOF)
459         ok = false;
460
461       if (!ok)
462         msg (ME, _("An I/O error occurred writing CSV file `%s'."),
463              fh_get_file_name (w->fh));
464
465       if (ok ? !replace_file_commit (w->rf) : !replace_file_abort (w->rf))
466         ok = false;
467     }
468
469   fh_unlock (w->lock);
470   fh_unref (w->fh);
471
472   free (w->encoding);
473
474   for (i = 0; i < w->n_csv_vars; i++)
475     {
476       struct csv_var *cv = &w->csv_vars[i];
477       mv_destroy (&cv->missing);
478       val_labs_destroy (cv->val_labs);
479     }
480
481   free (w->csv_vars);
482   free (w);
483
484   return ok;
485 }
486
487 /* CSV file writer casewriter class. */
488 static const struct casewriter_class csv_file_casewriter_class =
489   {
490     csv_file_casewriter_write,
491     csv_file_casewriter_destroy,
492     NULL,
493   };