CROSSTABS: Handle case where all cases in a crosstabulation are missing.
[pspp] / src / language / data-io / save.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 1997-9, 2000, 2006, 2007, 2008, 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 <stdlib.h>
20
21 #include <data/any-writer.h>
22 #include <data/case-map.h>
23 #include <data/case.h>
24 #include <data/casereader.h>
25 #include <data/casewriter.h>
26 #include <data/dictionary.h>
27 #include <data/por-file-writer.h>
28 #include <data/procedure.h>
29 #include <data/sys-file-writer.h>
30 #include <data/transformations.h>
31 #include <data/variable.h>
32 #include <language/command.h>
33 #include <language/data-io/file-handle.h>
34 #include <language/data-io/trim.h>
35 #include <language/lexer/lexer.h>
36 #include <libpspp/assertion.h>
37 #include <libpspp/compiler.h>
38
39 #include "xalloc.h"
40
41 #include "gettext.h"
42 #define _(msgid) gettext (msgid)
43
44 /* Writing system and portable files. */
45
46 /* Type of output file. */
47 enum writer_type
48   {
49     SYSFILE_WRITER,     /* System file. */
50     PORFILE_WRITER      /* Portable file. */
51   };
52
53 /* Type of a command. */
54 enum command_type
55   {
56     XFORM_CMD,          /* Transformation. */
57     PROC_CMD            /* Procedure. */
58   };
59
60 static int parse_output_proc (struct lexer *, struct dataset *,
61                               enum writer_type);
62 static int parse_output_trns (struct lexer *, struct dataset *,
63                               enum writer_type);
64
65 int
66 cmd_save (struct lexer *lexer, struct dataset *ds)
67 {
68   return parse_output_proc (lexer, ds, SYSFILE_WRITER);
69 }
70
71 int
72 cmd_export (struct lexer *lexer, struct dataset *ds)
73 {
74   return parse_output_proc (lexer, ds, PORFILE_WRITER);
75 }
76
77 int
78 cmd_xsave (struct lexer *lexer, struct dataset *ds)
79 {
80   return parse_output_trns (lexer, ds, SYSFILE_WRITER);
81 }
82
83 int
84 cmd_xexport (struct lexer *lexer, struct dataset *ds)
85 {
86   return parse_output_trns (lexer, ds, PORFILE_WRITER);
87 }
88 \f
89 struct output_trns
90   {
91     struct casewriter *writer;          /* Writer. */
92   };
93
94 static trns_proc_func output_trns_proc;
95 static trns_free_func output_trns_free;
96 static struct casewriter *parse_write_command (struct lexer *,
97                                                struct dataset *,
98                                                enum writer_type,
99                                                enum command_type,
100                                                bool *retain_unselected);
101
102 /* Parses and performs the SAVE or EXPORT procedure. */
103 static int
104 parse_output_proc (struct lexer *lexer, struct dataset *ds,
105                    enum writer_type writer_type)
106 {
107   bool retain_unselected;
108   struct casewriter *output;
109   bool ok;
110
111   output = parse_write_command (lexer, ds, writer_type, PROC_CMD,
112                                 &retain_unselected);
113   if (output == NULL)
114     return CMD_CASCADING_FAILURE;
115
116   casereader_transfer (proc_open_filtering (ds, !retain_unselected), output);
117   ok = casewriter_destroy (output);
118   ok = proc_commit (ds) && ok;
119
120   return ok ? CMD_SUCCESS : CMD_CASCADING_FAILURE;
121 }
122
123 /* Parses the XSAVE or XEXPORT transformation command. */
124 static int
125 parse_output_trns (struct lexer *lexer, struct dataset *ds, enum writer_type writer_type)
126 {
127   struct output_trns *t = xmalloc (sizeof *t);
128   t->writer = parse_write_command (lexer, ds, writer_type, XFORM_CMD, NULL);
129   if (t->writer == NULL)
130     {
131       free (t);
132       return CMD_CASCADING_FAILURE;
133     }
134
135   add_transformation (ds, output_trns_proc, output_trns_free, t);
136   return CMD_SUCCESS;
137 }
138
139 /* Parses SAVE or XSAVE or EXPORT or XEXPORT command.
140    WRITER_TYPE identifies the type of file to write,
141    and COMMAND_TYPE identifies the type of command.
142
143    On success, returns a writer.
144    For procedures only, sets *RETAIN_UNSELECTED to true if cases
145    that would otherwise be excluded by FILTER or USE should be
146    included.
147
148    On failure, returns a null pointer. */
149 static struct casewriter *
150 parse_write_command (struct lexer *lexer, struct dataset *ds,
151                      enum writer_type writer_type,
152                      enum command_type command_type,
153                      bool *retain_unselected)
154 {
155   /* Common data. */
156   struct file_handle *handle; /* Output file. */
157   struct dictionary *dict;    /* Dictionary for output file. */
158   struct casewriter *writer;  /* Writer. */
159   struct case_map *map;       /* Map from input data to data for writer. */
160
161   /* Common options. */
162   bool print_map;             /* Print map?  TODO. */
163   bool print_short_names;     /* Print long-to-short name map.  TODO. */
164   struct sfm_write_options sysfile_opts;
165   struct pfm_write_options porfile_opts;
166
167   assert (writer_type == SYSFILE_WRITER || writer_type == PORFILE_WRITER);
168   assert (command_type == XFORM_CMD || command_type == PROC_CMD);
169   assert ((retain_unselected != NULL) == (command_type == PROC_CMD));
170
171   if (command_type == PROC_CMD)
172     *retain_unselected = true;
173
174   handle = NULL;
175   dict = dict_clone (dataset_dict (ds));
176   writer = NULL;
177   map = NULL;
178   print_map = false;
179   print_short_names = false;
180   sysfile_opts = sfm_writer_default_options ();
181   porfile_opts = pfm_writer_default_options ();
182
183   case_map_prepare_dict (dict);
184   dict_delete_scratch_vars (dict);
185
186   lex_match (lexer, '/');
187   for (;;)
188     {
189       if (lex_match_id (lexer, "OUTFILE"))
190         {
191           if (handle != NULL)
192             {
193               lex_sbc_only_once ("OUTFILE");
194               goto error;
195             }
196
197           lex_match (lexer, '=');
198
199           handle = fh_parse (lexer, FH_REF_FILE | FH_REF_SCRATCH);
200           if (handle == NULL)
201             goto error;
202         }
203       else if (lex_match_id (lexer, "NAMES"))
204         print_short_names = true;
205       else if (lex_match_id (lexer, "PERMISSIONS"))
206         {
207           bool cw;
208
209           lex_match (lexer, '=');
210           if (lex_match_id (lexer, "READONLY"))
211             cw = false;
212           else if (lex_match_id (lexer, "WRITEABLE"))
213             cw = true;
214           else
215             {
216               lex_error (lexer, _("expecting %s or %s"),
217                          "READONLY", "WRITEABLE");
218               goto error;
219             }
220           sysfile_opts.create_writeable = porfile_opts.create_writeable = cw;
221         }
222       else if (command_type == PROC_CMD && lex_match_id (lexer, "UNSELECTED"))
223         {
224           lex_match (lexer, '=');
225           if (lex_match_id (lexer, "RETAIN"))
226             *retain_unselected = true;
227           else if (lex_match_id (lexer, "DELETE"))
228             *retain_unselected = false;
229           else
230             {
231               lex_error (lexer, _("expecting %s or %s"), "RETAIN", "DELETE");
232               goto error;
233             }
234         }
235       else if (writer_type == SYSFILE_WRITER
236                && lex_match_id (lexer, "COMPRESSED"))
237         sysfile_opts.compress = true;
238       else if (writer_type == SYSFILE_WRITER
239                && lex_match_id (lexer, "UNCOMPRESSED"))
240         sysfile_opts.compress = false;
241       else if (writer_type == SYSFILE_WRITER
242                && lex_match_id (lexer, "VERSION"))
243         {
244           lex_match (lexer, '=');
245           if (!lex_force_int (lexer))
246             goto error;
247           sysfile_opts.version = lex_integer (lexer);
248           lex_get (lexer);
249         }
250       else if (writer_type == PORFILE_WRITER && lex_match_id (lexer, "TYPE"))
251         {
252           lex_match (lexer, '=');
253           if (lex_match_id (lexer, "COMMUNICATIONS"))
254             porfile_opts.type = PFM_COMM;
255           else if (lex_match_id (lexer, "TAPE"))
256             porfile_opts.type = PFM_TAPE;
257           else
258             {
259               lex_error (lexer, _("expecting %s or %s"), "COMM", "TAPE");
260               goto error;
261             }
262         }
263       else if (writer_type == PORFILE_WRITER && lex_match_id (lexer, "DIGITS"))
264         {
265           lex_match (lexer, '=');
266           if (!lex_force_int (lexer))
267             goto error;
268           porfile_opts.digits = lex_integer (lexer);
269           lex_get (lexer);
270         }
271       else if (!parse_dict_trim (lexer, dict))
272         goto error;
273
274       if (!lex_match (lexer, '/'))
275         break;
276     }
277   if (lex_end_of_command (lexer) != CMD_SUCCESS)
278     goto error;
279
280   if (handle == NULL)
281     {
282       lex_sbc_missing (lexer, "OUTFILE");
283       goto error;
284     }
285
286   dict_delete_scratch_vars (dict);
287   dict_compact_values (dict);
288
289   if (fh_get_referent (handle) == FH_REF_FILE)
290     {
291       switch (writer_type)
292         {
293         case SYSFILE_WRITER:
294           writer = sfm_open_writer (handle, dict, sysfile_opts);
295           break;
296         case PORFILE_WRITER:
297           writer = pfm_open_writer (handle, dict, porfile_opts);
298           break;
299         }
300     }
301   else
302     writer = any_writer_open (handle, dict);
303   if (writer == NULL)
304     goto error;
305
306   map = case_map_from_dict (dict);
307   if (map != NULL)
308     writer = case_map_create_output_translator (map, writer);
309   dict_destroy (dict);
310
311   fh_unref (handle);
312   return writer;
313
314  error:
315   fh_unref (handle);
316   casewriter_destroy (writer);
317   dict_destroy (dict);
318   case_map_destroy (map);
319   return NULL;
320 }
321
322 /* Writes case *C to the system file specified on XSAVE or XEXPORT. */
323 static int
324 output_trns_proc (void *trns_, struct ccase **c, casenumber case_num UNUSED)
325 {
326   struct output_trns *t = trns_;
327   casewriter_write (t->writer, case_ref (*c));
328   return TRNS_CONTINUE;
329 }
330
331 /* Frees an XSAVE or XEXPORT transformation.
332    Returns true if successful, false if an I/O error occurred. */
333 static bool
334 output_trns_free (void *trns_)
335 {
336   struct output_trns *t = trns_;
337   bool ok = casewriter_destroy (t->writer);
338   free (t);
339   return ok;
340 }