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