csv: Open output file immediately, instead of delaying.
[pspp-builds.git] / src / output / csv.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 2009, 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 <errno.h>
20 #include <stdlib.h>
21
22 #include <data/file-name.h>
23 #include <libpspp/assertion.h>
24 #include <libpspp/compiler.h>
25 #include <libpspp/string-map.h>
26 #include <output/text-item.h>
27 #include <output/driver-provider.h>
28 #include <output/options.h>
29 #include <output/table-item.h>
30 #include <output/table-provider.h>
31
32 #include "gl/error.h"
33 #include "gl/xalloc.h"
34 #include "gl/xvasprintf.h"
35
36 #include "gettext.h"
37 #define _(msgid) gettext (msgid)
38
39 /* Comma-separated value output driver. */
40 struct csv_driver
41   {
42     struct output_driver driver;
43
44     char *separator;            /* Comma or tab. */
45     char *file_name;            /* Output file name. */
46     FILE *file;                 /* Output file. */
47     int n_items;                /* Number of items output so far. */
48   };
49
50 static struct csv_driver *
51 csv_driver_cast (struct output_driver *driver)
52 {
53   assert (driver->class == &csv_class);
54   return UP_CAST (driver, struct csv_driver, driver);
55 }
56
57 static struct driver_option *
58 opt (struct output_driver *d, struct string_map *options, const char *key,
59      const char *default_value)
60 {
61   return driver_option_get (d, options, key, default_value);
62 }
63
64 static struct output_driver *
65 csv_create (const char *name, enum output_device_type device_type,
66             struct string_map *o)
67 {
68   struct output_driver *d;
69   struct csv_driver *csv;
70
71   csv = xzalloc (sizeof *csv);
72   d = &csv->driver;
73   output_driver_init (&csv->driver, &csv_class, name, device_type);
74
75   csv->separator = parse_string (opt (d, o, "separator", ","));
76   csv->file_name = parse_string (opt (d, o, "output-file", "pspp.csv"));
77   csv->file = fn_open (csv->file_name, "w");
78   csv->n_items = 0;
79
80   if (csv->file == NULL)
81     {
82       error (0, errno, _("csv: opening output file \"%s\""), csv->file_name);
83       output_driver_destroy (d);
84       return NULL;
85     }
86
87   return d;
88 }
89
90 static void
91 csv_destroy (struct output_driver *driver)
92 {
93   struct csv_driver *csv = csv_driver_cast (driver);
94
95   free (csv->separator);
96   free (csv->file_name);
97   if (csv->file != NULL)
98     fclose (csv->file);
99   free (csv);
100 }
101
102 static void
103 csv_flush (struct output_driver *driver)
104 {
105   struct csv_driver *csv = csv_driver_cast (driver);
106   if (csv->file != NULL)
107     fflush (csv->file);
108 }
109
110 static void
111 csv_output_field (FILE *file, const char *field)
112 {
113   while (*field == ' ')
114     field++;
115
116   if (field[strcspn (field, "\"\n\r,\t")])
117     {
118       const char *p;
119
120       putc ('"', file);
121       for (p = field; *p != '\0'; p++)
122         {
123           if (*p == '"')
124             putc ('"', file);
125           putc (*p, file);
126         }
127       putc ('"', file);
128     }
129   else
130     fputs (field, file);
131 }
132
133 static void
134 csv_output_field_format (FILE *file, const char *format, ...)
135   PRINTF_FORMAT (2, 3);
136
137 static void
138 csv_output_field_format (FILE *file, const char *format, ...)
139 {
140   va_list args;
141   char *s;
142
143   va_start (args, format);
144   s = xvasprintf (format, args);
145   va_end (args);
146
147   csv_output_field (file, s);
148   free (s);
149 }
150
151 static void
152 csv_put_separator (struct csv_driver *csv)
153 {
154   if (csv->n_items++ > 0)
155     putc ('\n', csv->file);
156 }
157
158 static void
159 csv_submit (struct output_driver *driver,
160             const struct output_item *output_item)
161 {
162   struct csv_driver *csv = csv_driver_cast (driver);
163
164   if (is_table_item (output_item))
165     {
166       struct table_item *table_item = to_table_item (output_item);
167       const char *caption = table_item_get_caption (table_item);
168       const struct table *t = table_item_get_table (table_item);
169       int x, y;
170
171       csv_put_separator (csv);
172
173       if (caption != NULL)
174         {
175           csv_output_field_format (csv->file, "Table: %s", caption);
176           putc ('\n', csv->file);
177         }
178
179       for (y = 0; y < table_nr (t); y++)
180         {
181           for (x = 0; x < table_nc (t); x++)
182             {
183               struct table_cell cell;
184
185               table_get_cell (t, x, y, &cell);
186
187               if (x > 0)
188                 fputs (csv->separator, csv->file);
189
190               if (x != cell.d[TABLE_HORZ][0] || y != cell.d[TABLE_VERT][0])
191                 csv_output_field (csv->file, "");
192               else
193                 csv_output_field (csv->file, cell.contents);
194
195               table_cell_free (&cell);
196             }
197           putc ('\n', csv->file);
198         }
199     }
200   else if (is_text_item (output_item))
201     {
202       const struct text_item *text_item = to_text_item (output_item);
203       enum text_item_type type = text_item_get_type (text_item);
204       const char *text = text_item_get_text (text_item);
205
206       if (type == TEXT_ITEM_COMMAND_OPEN || type == TEXT_ITEM_COMMAND_CLOSE
207           || type == TEXT_ITEM_SYNTAX)
208         return;
209
210       csv_put_separator (csv);
211       switch (type)
212         {
213         case TEXT_ITEM_TITLE:
214           csv_output_field_format (csv->file, "Title: %s", text);
215           break;
216
217         case TEXT_ITEM_SUBTITLE:
218           csv_output_field_format (csv->file, "Subtitle: %s", text);
219           break;
220
221         default:
222           csv_output_field (csv->file, text);
223           break;
224         }
225       putc ('\n', csv->file);
226     }
227 }
228
229 const struct output_driver_class csv_class =
230   {
231     "csv",
232     csv_create,
233     csv_destroy,
234     csv_submit,
235     csv_flush,
236   };