59c915dd443935e6eda189dfd6b6c12f76460adc
[pspp] / utilities / pspp-output.c
1  /* PSPP - a program for statistical analysis.
2    Copyright (C) 2017, 2018 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 <getopt.h>
20 #include <limits.h>
21 #include <stdlib.h>
22 #include <unistd.h>
23
24 #include "data/file-handle-def.h"
25 #include "data/settings.h"
26 #include "libpspp/i18n.h"
27 #include "libpspp/message.h"
28 #include "libpspp/string-map.h"
29 #include "libpspp/string-set.h"
30 #include "output/driver.h"
31 #include "output/group-item.h"
32 #include "output/page-setup-item.h"
33 #include "output/pivot-table.h"
34 #include "output/spv/light-binary-parser.h"
35 #include "output/spv/spv-legacy-data.h"
36 #include "output/spv/spv-output.h"
37 #include "output/spv/spv-select.h"
38 #include "output/spv/spv-table-look.h"
39 #include "output/spv/spv.h"
40 #include "output/table-item.h"
41 #include "output/text-item.h"
42
43 #include "gl/c-ctype.h"
44 #include "gl/error.h"
45 #include "gl/progname.h"
46 #include "gl/version-etc.h"
47 #include "gl/xalloc.h"
48
49 #include <libxml/tree.h>
50 #include <libxml/xpath.h>
51 #include <libxml/xpathInternals.h>
52
53 #include "gettext.h"
54 #define _(msgid) gettext (msgid)
55
56 /* -O key=value: Output driver options. */
57 static struct string_map output_options
58     = STRING_MAP_INITIALIZER (output_options);
59
60 /* --member-name: Include .zip member name in "dir" output. */
61 static bool show_member_names;
62
63 /* --show-hidden, --select, --commands, ...: Selection criteria. */
64 static struct spv_criteria *criteria;
65 static size_t n_criteria, allocated_criteria;
66
67 /* --or: Add new element to 'criteria' array. */
68 static bool new_criteria;
69
70 /* --sort: Sort members under dump-light-table, to make comparisons easier. */
71 static bool sort;
72
73 /* --raw: Dump raw binary data in dump-light-table. */
74 static bool raw;
75
76 /* -f, --force: Keep output file even on error. */
77 static bool force;
78
79 /* --table-look: TableLook to replace table style for conversion. */
80 static struct pivot_table_look *table_look;
81
82 /* Number of warnings issued. */
83 static size_t n_warnings;
84
85 static void usage (void);
86 static void parse_options (int argc, char **argv);
87
88 static void
89 dump_item (const struct spv_item *item)
90 {
91   if (show_member_names && (item->xml_member || item->bin_member))
92     {
93       const char *x = item->xml_member;
94       const char *b = item->bin_member;
95       char *s = (x && b
96                  ? xasprintf (_("%s and %s:"), x, b)
97                  : xasprintf ("%s:", x ? x : b));
98       text_item_submit (text_item_create_nocopy (TEXT_ITEM_TITLE, s));
99     }
100
101   switch (spv_item_get_type (item))
102     {
103     case SPV_ITEM_HEADING:
104       break;
105
106     case SPV_ITEM_TEXT:
107       spv_text_submit (item);
108       break;
109
110     case SPV_ITEM_TABLE:
111       pivot_table_submit (pivot_table_ref (spv_item_get_table (item)));
112       break;
113
114     case SPV_ITEM_GRAPH:
115       break;
116
117     case SPV_ITEM_MODEL:
118       break;
119
120     case SPV_ITEM_OBJECT:
121       break;
122
123     case SPV_ITEM_TREE:
124       break;
125
126     default:
127       abort ();
128     }
129 }
130
131 static void
132 print_item_directory (const struct spv_item *item)
133 {
134   for (int i = 1; i < spv_item_get_level (item); i++)
135     printf ("    ");
136
137   enum spv_item_type type = spv_item_get_type (item);
138   printf ("- %s", spv_item_type_to_string (type));
139
140   const char *label = spv_item_get_label (item);
141   if (label)
142     printf (" \"%s\"", label);
143
144   if (type == SPV_ITEM_TABLE)
145     {
146       const struct pivot_table *table = spv_item_get_table (item);
147       char *title = pivot_value_to_string (table->title,
148                                            SETTINGS_VALUE_SHOW_DEFAULT,
149                                            SETTINGS_VALUE_SHOW_DEFAULT);
150       if (!label || strcmp (title, label))
151         printf (" title \"%s\"", title);
152       free (title);
153     }
154
155   const char *command_id = spv_item_get_command_id (item);
156   if (command_id)
157     printf (" command \"%s\"", command_id);
158
159   const char *subtype = spv_item_get_subtype (item);
160   if (subtype && (!label || strcmp (label, subtype)))
161     printf (" subtype \"%s\"", subtype);
162
163   if (!spv_item_is_visible (item))
164     printf (" (hidden)");
165   if (show_member_names && (item->xml_member || item->bin_member))
166     {
167       if (item->xml_member && item->bin_member)
168         printf (" in %s and %s", item->xml_member, item->bin_member);
169       else if (item->xml_member)
170         printf (" in %s", item->xml_member);
171       else if (item->bin_member)
172         printf (" in %s", item->bin_member);
173     }
174   putchar ('\n');
175 }
176
177 static void
178 run_detect (int argc UNUSED, char **argv)
179 {
180   char *err = spv_detect (argv[1]);
181   if (err)
182     error (1, 0, "%s", err);
183 }
184
185 static void
186 run_directory (int argc UNUSED, char **argv)
187 {
188   struct spv_reader *spv;
189   char *err = spv_open (argv[1], &spv);
190   if (err)
191     error (1, 0, "%s", err);
192
193   struct spv_item **items;
194   size_t n_items;
195   spv_select (spv, criteria, n_criteria, &items, &n_items);
196   for (size_t i = 0; i < n_items; i++)
197     print_item_directory (items[i]);
198   free (items);
199
200   spv_close (spv);
201 }
202
203 struct item_path
204   {
205     const struct spv_item **nodes;
206     size_t n;
207
208 #define N_STUB 10
209     const struct spv_item *stub[N_STUB];
210   };
211
212 static void
213 swap_nodes (const struct spv_item **a, const struct spv_item **b)
214 {
215   const struct spv_item *tmp = *a;
216   *a = *b;
217   *b = tmp;
218 }
219
220 static void
221 get_path (const struct spv_item *item, struct item_path *path)
222 {
223   size_t allocated = 10;
224   path->nodes = path->stub;
225   path->n = 0;
226
227   while (item)
228     {
229       if (path->n >= allocated)
230         {
231           if (path->nodes == path->stub)
232             path->nodes = xmemdup (path->stub, sizeof path->stub);
233           path->nodes = x2nrealloc (path->nodes, &allocated,
234                                     sizeof *path->nodes);
235         }
236       path->nodes[path->n++] = item;
237       item = item->parent;
238     }
239
240   for (size_t i = 0; i < path->n / 2; i++)
241     swap_nodes (&path->nodes[i], &path->nodes[path->n - i - 1]);
242 }
243
244 static void
245 free_path (struct item_path *path)
246 {
247   if (path && path->nodes != path->stub)
248     free (path->nodes);
249 }
250
251 static void
252 dump_heading_transition (const struct spv_item *old,
253                          const struct spv_item *new)
254 {
255   if (old == new)
256     return;
257
258   struct item_path old_path, new_path;
259   get_path (old, &old_path);
260   get_path (new, &new_path);
261
262   size_t common = 0;
263   for (; common < old_path.n && common < new_path.n; common++)
264     if (old_path.nodes[common] != new_path.nodes[common])
265       break;
266
267   for (size_t i = common; i < old_path.n; i++)
268     group_close_item_submit (group_close_item_create ());
269   for (size_t i = common; i < new_path.n; i++)
270     group_open_item_submit (group_open_item_create (
271                               new_path.nodes[i]->command_id));
272
273   free_path (&old_path);
274   free_path (&new_path);
275 }
276
277 static void
278 run_convert (int argc UNUSED, char **argv)
279 {
280   struct spv_reader *spv;
281   char *err = spv_open (argv[1], &spv);
282   if (err)
283     error (1, 0, "%s", err);
284
285   if (table_look)
286     spv_item_set_table_look (spv_get_root (spv), table_look);
287
288   output_engine_push ();
289   output_set_filename (argv[1]);
290   string_map_replace (&output_options, "output-file", argv[2]);
291   struct output_driver *driver = output_driver_create (&output_options);
292   if (!driver)
293     exit (EXIT_FAILURE);
294   output_driver_register (driver);
295
296   const struct page_setup *ps = spv_get_page_setup (spv);
297   if (ps)
298     page_setup_item_submit (page_setup_item_create (ps));
299
300   struct spv_item **items;
301   size_t n_items;
302   spv_select (spv, criteria, n_criteria, &items, &n_items);
303   struct spv_item *prev_heading = spv_get_root (spv);
304   for (size_t i = 0; i < n_items; i++)
305     {
306       struct spv_item *heading
307         = items[i]->type == SPV_ITEM_HEADING ? items[i] : items[i]->parent;
308       dump_heading_transition (prev_heading, heading);
309       dump_item (items[i]);
310       prev_heading = heading;
311     }
312   dump_heading_transition (prev_heading, spv_get_root (spv));
313   free (items);
314
315   spv_close (spv);
316
317   output_engine_pop ();
318   fh_done ();
319
320   if (n_warnings && !force)
321     {
322       /* XXX There could be other files to unlink, e.g. the ascii driver can
323          produce additional files with the charts. */
324       unlink (argv[2]);
325     }
326 }
327
328 static const struct pivot_table *
329 get_first_table (const struct spv_reader *spv)
330 {
331   struct spv_item **items;
332   size_t n_items;
333   spv_select (spv, criteria, n_criteria, &items, &n_items);
334
335   for (size_t i = 0; i < n_items; i++)
336     if (spv_item_is_table (items[i]))
337       {
338         free (items);
339         return spv_item_get_table (items[i]);
340       }
341
342   free (items);
343   return NULL;
344 }
345
346 static void
347 run_get_table_look (int argc UNUSED, char **argv)
348 {
349   struct pivot_table_look *look;
350   if (strcmp (argv[1], "-"))
351     {
352       struct spv_reader *spv;
353       char *err = spv_open (argv[1], &spv);
354       if (err)
355         error (1, 0, "%s", err);
356
357       const struct pivot_table *table = get_first_table (spv);
358       if (!table)
359         error (1, 0, "%s: no tables found", argv[1]);
360
361       look = pivot_table_look_ref (pivot_table_get_look (table));
362
363       spv_close (spv);
364     }
365   else
366     look = pivot_table_look_ref (pivot_table_look_builtin_default ());
367
368   char *err = spv_table_look_write (argv[2], look);
369   if (err)
370     error (1, 0, "%s", err);
371
372   pivot_table_look_unref (look);
373 }
374
375 static void
376 run_convert_table_look (int argc UNUSED, char **argv)
377 {
378   struct pivot_table_look *look;
379   char *err = spv_table_look_read (argv[1], &look);
380   if (err)
381     error (1, 0, "%s", err);
382
383   err = spv_table_look_write (argv[2], look);
384   if (err)
385     error (1, 0, "%s", err);
386
387   pivot_table_look_unref (look);
388   free (look);
389 }
390
391 static void
392 run_dump (int argc UNUSED, char **argv)
393 {
394   struct spv_reader *spv;
395   char *err = spv_open (argv[1], &spv);
396   if (err)
397     error (1, 0, "%s", err);
398
399   struct spv_item **items;
400   size_t n_items;
401   spv_select (spv, criteria, n_criteria, &items, &n_items);
402   for (size_t i = 0; i < n_items; i++)
403     if (items[i]->type == SPV_ITEM_TABLE)
404       {
405         pivot_table_dump (spv_item_get_table (items[i]), 0);
406         putchar ('\n');
407       }
408   free (items);
409
410   spv_close (spv);
411 }
412
413 static int
414 compare_borders (const void *a_, const void *b_)
415 {
416   const struct spvlb_border *const *ap = a_;
417   const struct spvlb_border *const *bp = b_;
418   uint32_t a = (*ap)->border_type;
419   uint32_t b = (*bp)->border_type;
420
421   return a < b ? -1 : a > b;
422 }
423
424 static int
425 compare_cells (const void *a_, const void *b_)
426 {
427   const struct spvlb_cell *const *ap = a_;
428   const struct spvlb_cell *const *bp = b_;
429   uint64_t a = (*ap)->index;
430   uint64_t b = (*bp)->index;
431
432   return a < b ? -1 : a > b;
433 }
434
435 static void
436 run_dump_light_table (int argc UNUSED, char **argv)
437 {
438   if (raw && isatty (STDOUT_FILENO))
439     error (1, 0, "not writing binary data to tty");
440
441   struct spv_reader *spv;
442   char *err = spv_open (argv[1], &spv);
443   if (err)
444     error (1, 0, "%s", err);
445
446   struct spv_item **items;
447   size_t n_items;
448   spv_select (spv, criteria, n_criteria, &items, &n_items);
449   for (size_t i = 0; i < n_items; i++)
450     {
451       if (!spv_item_is_light_table (items[i]))
452         continue;
453
454       char *error;
455       if (raw)
456         {
457           void *data;
458           size_t size;
459           error = spv_item_get_raw_light_table (items[i], &data, &size);
460           if (!error)
461             {
462               fwrite (data, size, 1, stdout);
463               free (data);
464             }
465         }
466       else
467         {
468           struct spvlb_table *table;
469           error = spv_item_get_light_table (items[i], &table);
470           if (!error)
471             {
472               if (sort)
473                 {
474                   qsort (table->borders->borders, table->borders->n_borders,
475                          sizeof *table->borders->borders, compare_borders);
476                   qsort (table->cells->cells, table->cells->n_cells,
477                          sizeof *table->cells->cells, compare_cells);
478                 }
479               spvlb_print_table (items[i]->bin_member, 0, table);
480               spvlb_free_table (table);
481             }
482         }
483       if (error)
484         {
485           msg (ME, "%s", error);
486           free (error);
487         }
488     }
489
490   free (items);
491
492   spv_close (spv);
493 }
494
495 static void
496 run_dump_legacy_data (int argc UNUSED, char **argv)
497 {
498   struct spv_reader *spv;
499   char *err = spv_open (argv[1], &spv);
500   if (err)
501     error (1, 0, "%s", err);
502
503   struct spv_item **items;
504   size_t n_items;
505   spv_select (spv, criteria, n_criteria, &items, &n_items);
506   for (size_t i = 0; i < n_items; i++)
507     if (spv_item_is_legacy_table (items[i]))
508       {
509         struct spv_data data;
510         char *error;
511         if (raw)
512           {
513             void *data;
514             size_t size;
515             error = spv_item_get_raw_legacy_data (items[i], &data, &size);
516             if (!error)
517               {
518                 fwrite (data, size, 1, stdout);
519                 free (data);
520               }
521           }
522         else
523           {
524             error = spv_item_get_legacy_data (items[i], &data);
525             if (!error)
526               {
527                 printf ("%s:\n", items[i]->bin_member);
528                 spv_data_dump (&data, stdout);
529                 spv_data_uninit (&data);
530                 printf ("\n");
531               }
532           }
533
534         if (error)
535           {
536             msg (ME, "%s", error);
537             free (error);
538           }
539       }
540   free (items);
541
542   spv_close (spv);
543 }
544
545 /* This is really bogus.
546
547    XPath doesn't have any notion of a default XML namespace, but all of the
548    elements in the documents we're interested in have a namespace.  Thus, we'd
549    need to require the XPath expressions to have a namespace on every single
550    element: vis:sourceVariable, vis:graph, and so on.  That's a pain.  So,
551    instead, we remove the default namespace from everyplace it occurs.  XPath
552    does support the null namespace, so this allows sourceVariable, graph,
553    etc. to work.
554
555    See http://plasmasturm.org/log/259/ and
556    https://mail.gnome.org/archives/xml/2003-April/msg00144.html for more
557    information.*/
558 static void
559 remove_default_xml_namespace (xmlNode *node)
560 {
561   if (node->ns && !node->ns->prefix)
562     node->ns = NULL;
563
564   for (xmlNode *child = node->children; child; child = child->next)
565     remove_default_xml_namespace (child);
566 }
567
568 static void
569 register_ns (xmlXPathContext *ctx, const char *prefix, const char *uri)
570 {
571   xmlXPathRegisterNs (ctx, CHAR_CAST (xmlChar *, prefix),
572                       CHAR_CAST (xmlChar *, uri));
573 }
574
575 static xmlXPathContext *
576 create_xpath_context (xmlDoc *doc)
577 {
578   xmlXPathContext *ctx = xmlXPathNewContext (doc);
579   register_ns (ctx, "vgr", "http://xml.spss.com/spss/viewer/viewer-graph");
580   register_ns (ctx, "vizml", "http://xml.spss.com/visualization");
581   register_ns (ctx, "vmd", "http://xml.spss.com/spss/viewer/viewer-model");
582   register_ns (ctx, "vps", "http://xml.spss.com/spss/viewer/viewer-pagesetup");
583   register_ns (ctx, "vst", "http://xml.spss.com/spss/viewer/viewer-style");
584   register_ns (ctx, "vtb", "http://xml.spss.com/spss/viewer/viewer-table");
585   register_ns (ctx, "vtl", "http://xml.spss.com/spss/viewer/table-looks");
586   register_ns (ctx, "vtt", "http://xml.spss.com/spss/viewer/viewer-treemodel");
587   register_ns (ctx, "vtx", "http://xml.spss.com/spss/viewer/viewer-text");
588   register_ns (ctx, "xsi", "http://www.w3.org/2001/XMLSchema-instance");
589   return ctx;
590 }
591
592 static void
593 dump_xml (int argc, char **argv, const char *member_name,
594           char *error_s, xmlDoc *doc)
595 {
596   if (!error_s)
597     {
598       if (argc == 2)
599         {
600           printf ("<!-- %s -->\n", member_name);
601           xmlElemDump (stdout, NULL, xmlDocGetRootElement (doc));
602           putchar ('\n');
603         }
604       else
605         {
606           bool any_results = false;
607
608           remove_default_xml_namespace (xmlDocGetRootElement (doc));
609           for (int i = 2; i < argc; i++)
610             {
611               xmlXPathContext *xpath_ctx = create_xpath_context (doc);
612               xmlXPathSetContextNode (xmlDocGetRootElement (doc),
613                                       xpath_ctx);
614               xmlXPathObject *xpath_obj = xmlXPathEvalExpression(
615                 CHAR_CAST (xmlChar *, argv[i]), xpath_ctx);
616               if (!xpath_obj)
617                 error (1, 0, _("%s: invalid XPath expression"), argv[i]);
618
619               const xmlNodeSet *nodes = xpath_obj->nodesetval;
620               if (nodes && nodes->nodeNr > 0)
621                 {
622                   if (!any_results)
623                     {
624                       printf ("<!-- %s -->\n", member_name);
625                       any_results = true;
626                     }
627                   for (size_t j = 0; j < nodes->nodeNr; j++)
628                     {
629                       xmlElemDump (stdout, doc, nodes->nodeTab[j]);
630                       putchar ('\n');
631                     }
632                 }
633
634               xmlXPathFreeObject (xpath_obj);
635               xmlXPathFreeContext (xpath_ctx);
636             }
637           if (any_results)
638             putchar ('\n');;
639         }
640       xmlFreeDoc (doc);
641     }
642   else
643     {
644       printf ("<!-- %s -->\n", member_name);
645       msg (ME, "%s", error_s);
646       free (error_s);
647     }
648 }
649
650 static void
651 run_dump_legacy_table (int argc, char **argv)
652 {
653   struct spv_reader *spv;
654   char *err = spv_open (argv[1], &spv);
655   if (err)
656     error (1, 0, "%s", err);
657
658   struct spv_item **items;
659   size_t n_items;
660   spv_select (spv, criteria, n_criteria, &items, &n_items);
661   for (size_t i = 0; i < n_items; i++)
662     if (spv_item_is_legacy_table (items[i]))
663       {
664         xmlDoc *doc;
665         char *error_s = spv_item_get_legacy_table (items[i], &doc);
666         dump_xml (argc, argv, items[i]->xml_member, error_s, doc);
667       }
668   free (items);
669
670   spv_close (spv);
671 }
672
673 static void
674 run_dump_structure (int argc, char **argv)
675 {
676   struct spv_reader *spv;
677   char *err = spv_open (argv[1], &spv);
678   if (err)
679     error (1, 0, "%s", err);
680
681   struct spv_item **items;
682   size_t n_items;
683   spv_select (spv, criteria, n_criteria, &items, &n_items);
684   const char *last_structure_member = NULL;
685   for (size_t i = 0; i < n_items; i++)
686     if (!last_structure_member || strcmp (items[i]->structure_member,
687                                           last_structure_member))
688       {
689         last_structure_member = items[i]->structure_member;
690
691         xmlDoc *doc;
692         char *error_s = spv_item_get_structure (items[i], &doc);
693         dump_xml (argc, argv, items[i]->structure_member, error_s, doc);
694       }
695   free (items);
696
697   spv_close (spv);
698 }
699
700 static void
701 run_is_legacy (int argc UNUSED, char **argv)
702 {
703   struct spv_reader *spv;
704   char *err = spv_open (argv[1], &spv);
705   if (err)
706     error (1, 0, "%s", err);
707
708   bool is_legacy = false;
709
710   struct spv_item **items;
711   size_t n_items;
712   spv_select (spv, criteria, n_criteria, &items, &n_items);
713   for (size_t i = 0; i < n_items; i++)
714     if (spv_item_is_legacy_table (items[i]))
715       {
716         is_legacy = true;
717         break;
718       }
719   free (items);
720
721   spv_close (spv);
722
723   exit (is_legacy ? EXIT_SUCCESS : EXIT_FAILURE);
724 }
725
726 struct command
727   {
728     const char *name;
729     int min_args, max_args;
730     void (*run) (int argc, char **argv);
731   };
732
733 static const struct command commands[] =
734   {
735     { "detect", 1, 1, run_detect },
736     { "dir", 1, 1, run_directory },
737     { "convert", 2, 2, run_convert },
738     { "get-table-look", 2, 2, run_get_table_look },
739     { "convert-table-look", 2, 2, run_convert_table_look },
740
741     /* Undocumented commands. */
742     { "dump", 1, 1, run_dump },
743     { "dump-light-table", 1, 1, run_dump_light_table },
744     { "dump-legacy-data", 1, 1, run_dump_legacy_data },
745     { "dump-legacy-table", 1, INT_MAX, run_dump_legacy_table },
746     { "dump-structure", 1, INT_MAX, run_dump_structure },
747     { "is-legacy", 1, 1, run_is_legacy },
748   };
749 static const int n_commands = sizeof commands / sizeof *commands;
750
751 static const struct command *
752 find_command (const char *name)
753 {
754   for (size_t i = 0; i < n_commands; i++)
755     {
756       const struct command *c = &commands[i];
757       if (!strcmp (name, c->name))
758         return c;
759     }
760   return NULL;
761 }
762
763 static void
764 emit_msg (const struct msg *m, void *aux UNUSED)
765 {
766   if (m->severity == MSG_S_ERROR || m->severity == MSG_S_WARNING)
767     n_warnings++;
768
769   char *s = msg_to_string (m);
770   fprintf (stderr, "%s\n", s);
771   free (s);
772 }
773
774 int
775 main (int argc, char **argv)
776 {
777   set_program_name (argv[0]);
778   msg_set_handler (emit_msg, NULL);
779   settings_init ();
780   i18n_init ();
781
782   parse_options (argc, argv);
783
784   argc -= optind;
785   argv += optind;
786
787   if (argc < 1)
788     error (1, 0, _("missing command name (use --help for help)"));
789
790   const struct command *c = find_command (argv[0]);
791   if (!c)
792     error (1, 0, _("unknown command \"%s\" (use --help for help)"), argv[0]);
793
794   int n_args = argc - 1;
795   if (n_args < c->min_args || n_args > c->max_args)
796     {
797       if (c->min_args == c->max_args)
798         {
799           error (1, 0,
800                  ngettext ("\"%s\" command takes exactly %d argument",
801                            "\"%s\" command takes exactly %d arguments",
802                            c->min_args), c->name, c->min_args);
803         }
804       else if (c->max_args == INT_MAX)
805         {
806           error (1, 0,
807                  ngettext ("\"%s\" command requires at least %d argument",
808                            "\"%s\" command requires at least %d arguments",
809                            c->min_args), c->name, c->min_args);
810         }
811       else
812         {
813           error (1, 0,
814                  _("\"%s\" command requires between %d and %d arguments"),
815                  c->name, c->min_args, c->max_args);
816         }
817     }
818
819   c->run (argc, argv);
820
821   pivot_table_look_unref (table_look);
822   i18n_done ();
823
824   return n_warnings ? EXIT_FAILURE : EXIT_SUCCESS;
825 }
826
827 static struct spv_criteria *
828 get_criteria (void)
829 {
830   if (!n_criteria || new_criteria)
831     {
832       new_criteria = false;
833       if (n_criteria >= allocated_criteria)
834         criteria = x2nrealloc (criteria, &allocated_criteria,
835                                sizeof *criteria);
836       criteria[n_criteria++] = (struct spv_criteria) SPV_CRITERIA_INITIALIZER;
837     }
838
839   return &criteria[n_criteria - 1];
840 }
841
842 static void
843 parse_select (char *arg)
844 {
845   bool invert = arg[0] == '^';
846   arg += invert;
847
848   unsigned classes = 0;
849   for (char *token = strtok (arg, ","); token; token = strtok (NULL, ","))
850     {
851       if (!strcmp (arg, "all"))
852         classes = SPV_ALL_CLASSES;
853       else if (!strcmp (arg, "help"))
854         {
855           puts (_("The following object classes are supported:"));
856           for (int class = 0; class < SPV_N_CLASSES; class++)
857             printf ("- %s\n", spv_item_class_to_string (class));
858           exit (0);
859         }
860       else
861         {
862           int class = spv_item_class_from_string (token);
863           if (class == SPV_N_CLASSES)
864             error (1, 0, _("%s: unknown object class (use --select=help "
865                            "for help"), arg);
866           classes |= 1u << class;
867         }
868     }
869
870   struct spv_criteria *c = get_criteria ();
871   c->classes = invert ? classes ^ SPV_ALL_CLASSES : classes;
872 }
873
874 static struct spv_criteria_match *
875 get_criteria_match (const char **arg)
876 {
877   struct spv_criteria *c = get_criteria ();
878   if ((*arg)[0] == '^')
879     {
880       (*arg)++;
881       return &c->exclude;
882     }
883   else
884     return &c->include;
885 }
886
887 static void
888 parse_commands (const char *arg)
889 {
890   struct spv_criteria_match *cm = get_criteria_match (&arg);
891   string_array_parse (&cm->commands, ss_cstr (arg), ss_cstr (","));
892 }
893
894 static void
895 parse_subtypes (const char *arg)
896 {
897   struct spv_criteria_match *cm = get_criteria_match (&arg);
898   string_array_parse (&cm->subtypes, ss_cstr (arg), ss_cstr (","));
899 }
900
901 static void
902 parse_labels (const char *arg)
903 {
904   struct spv_criteria_match *cm = get_criteria_match (&arg);
905   string_array_parse (&cm->labels, ss_cstr (arg), ss_cstr (","));
906 }
907
908 static void
909 parse_instances (char *arg)
910 {
911   struct spv_criteria *c = get_criteria ();
912   size_t allocated_instances = c->n_instances;
913
914   for (char *token = strtok (arg, ","); token; token = strtok (NULL, ","))
915     {
916       if (c->n_instances >= allocated_instances)
917         c->instances = x2nrealloc (c->instances, &allocated_instances,
918                                    sizeof *c->instances);
919
920       c->instances[c->n_instances++] = (!strcmp (token, "last") ? -1
921                                         : atoi (token));
922     }
923 }
924
925 static void
926 parse_nth_commands (char *arg)
927 {
928   struct spv_criteria *c = get_criteria ();
929   size_t allocated_commands = c->n_commands;
930
931   for (char *token = strtok (arg, ","); token; token = strtok (NULL, ","))
932     {
933       if (c->n_commands >= allocated_commands)
934         c->commands = x2nrealloc (c->commands, &allocated_commands,
935                                    sizeof *c->commands);
936
937       c->commands[c->n_commands++] = atoi (token);
938     }
939 }
940
941 static void
942 parse_members (const char *arg)
943 {
944   struct spv_criteria *cm = get_criteria ();
945   string_array_parse (&cm->members, ss_cstr (arg), ss_cstr (","));
946 }
947
948 static void
949 parse_table_look (const char *arg)
950 {
951   pivot_table_look_unref (table_look);
952
953   char *error_s = spv_table_look_read (arg, &table_look);
954   if (error_s)
955     error (1, 0, "%s", error_s);
956 }
957
958 static void
959 parse_options (int argc, char *argv[])
960 {
961   for (;;)
962     {
963       enum
964         {
965           OPT_MEMBER_NAMES = UCHAR_MAX + 1,
966           OPT_SHOW_HIDDEN,
967           OPT_SELECT,
968           OPT_COMMANDS,
969           OPT_NTH_COMMANDS,
970           OPT_SUBTYPES,
971           OPT_LABELS,
972           OPT_INSTANCES,
973           OPT_MEMBERS,
974           OPT_ERRORS,
975           OPT_OR,
976           OPT_SORT,
977           OPT_RAW,
978           OPT_TABLE_LOOK,
979         };
980       static const struct option long_options[] =
981         {
982           /* Input selection options. */
983           { "show-hidden", no_argument, NULL, OPT_SHOW_HIDDEN },
984           { "select", required_argument, NULL, OPT_SELECT },
985           { "commands", required_argument, NULL, OPT_COMMANDS },
986           { "nth-commands", required_argument, NULL, OPT_NTH_COMMANDS },
987           { "subtypes", required_argument, NULL, OPT_SUBTYPES },
988           { "labels", required_argument, NULL, OPT_LABELS },
989           { "instances", required_argument, NULL, OPT_INSTANCES },
990           { "members", required_argument, NULL, OPT_MEMBERS },
991           { "errors", no_argument, NULL, OPT_ERRORS },
992           { "or", no_argument, NULL, OPT_OR },
993
994           /* "dir" command options. */
995           { "member-names", no_argument, NULL, OPT_MEMBER_NAMES },
996
997           /* "convert" command options. */
998           { "force", no_argument, NULL, 'f' },
999           { "table-look", required_argument, NULL, OPT_TABLE_LOOK },
1000
1001           /* "dump-light-table" command options. */
1002           { "sort", no_argument, NULL, OPT_SORT },
1003           { "raw", no_argument, NULL, OPT_RAW },
1004
1005           { "help", no_argument, NULL, 'h' },
1006           { "version", no_argument, NULL, 'v' },
1007
1008           { NULL, 0, NULL, 0 },
1009         };
1010
1011       int c;
1012
1013       c = getopt_long (argc, argv, "O:hvf", long_options, NULL);
1014       if (c == -1)
1015         break;
1016
1017       switch (c)
1018         {
1019         case 'O':
1020           output_driver_parse_option (optarg, &output_options);
1021           break;
1022
1023         case OPT_MEMBER_NAMES:
1024           show_member_names = true;
1025           break;
1026
1027         case OPT_SHOW_HIDDEN:
1028           get_criteria ()->include_hidden = true;
1029           break;
1030
1031         case OPT_SELECT:
1032           parse_select (optarg);
1033           break;
1034
1035         case OPT_COMMANDS:
1036           parse_commands (optarg);
1037           break;
1038
1039         case OPT_NTH_COMMANDS:
1040           parse_nth_commands (optarg);
1041           break;
1042
1043         case OPT_SUBTYPES:
1044           parse_subtypes (optarg);
1045           break;
1046
1047         case OPT_LABELS:
1048           parse_labels (optarg);
1049           break;
1050
1051         case OPT_INSTANCES:
1052           parse_instances (optarg);
1053           break;
1054
1055         case OPT_MEMBERS:
1056           parse_members (optarg);
1057           break;
1058
1059         case OPT_ERRORS:
1060           get_criteria ()->error = true;
1061           break;
1062
1063         case OPT_OR:
1064           new_criteria = true;
1065           break;
1066
1067         case OPT_SORT:
1068           sort = true;
1069           break;
1070
1071         case OPT_RAW:
1072           raw = true;
1073           break;
1074
1075         case OPT_TABLE_LOOK:
1076           parse_table_look (optarg);
1077           break;
1078
1079         case 'f':
1080           force = true;
1081           break;
1082
1083         case 'v':
1084           version_etc (stdout, "pspp-output", PACKAGE_NAME, PACKAGE_VERSION,
1085                        "Ben Pfaff", "John Darrington", NULL_SENTINEL);
1086           exit (EXIT_SUCCESS);
1087
1088         case 'h':
1089           usage ();
1090           exit (EXIT_SUCCESS);
1091
1092         default:
1093           exit (EXIT_FAILURE);
1094         }
1095     }
1096 }
1097
1098 static void
1099 usage (void)
1100 {
1101   struct string s = DS_EMPTY_INITIALIZER;
1102   struct string_set formats = STRING_SET_INITIALIZER(formats);
1103   output_get_supported_formats (&formats);
1104   const char *format;
1105   const struct string_set_node *node;
1106   STRING_SET_FOR_EACH (format, node, &formats)
1107     {
1108       if (!ds_is_empty (&s))
1109         ds_put_byte (&s, ' ');
1110       ds_put_cstr (&s, format);
1111     }
1112   string_set_destroy (&formats);
1113
1114   printf ("\
1115 %s, a utility for working with SPSS viewer (.spv) files.\n\
1116 Usage: %s [OPTION]... COMMAND ARG...\n\
1117 \n\
1118 The following commands are available:\n\
1119   detect FILE            Detect whether FILE is an SPV file.\n\
1120   dir FILE               List tables and other items in FILE.\n\
1121   convert SOURCE DEST    Convert .spv SOURCE to DEST.\n\
1122   get-table-look SOURCE DEST  Copies first selected TableLook into DEST\n\
1123   convert-table-look SOURCE DEST  Copies .tlo or .stt SOURCE into DEST\n\
1124 \n\
1125 Input selection options for \"dir\" and \"convert\":\n\
1126   --select=CLASS...   include only some kinds of objects\n\
1127   --select=help       print known object classes\n\
1128   --commands=COMMAND...  include only specified COMMANDs\n\
1129   --nth-commands=N...  include only the Nth instance of selected commands\n\
1130   --subtypes=SUBTYPE...  include only specified SUBTYPEs of output\n\
1131   --labels=LABEL...   include only output objects with the given LABELs\n\
1132   --instances=INSTANCE...  include only the given object INSTANCEs\n\
1133   --show-hidden       include hidden output objects\n\
1134   --or                separate two sets of selection options\n\
1135 \n\
1136 \"convert\" by default infers the destination's format from its extension.\n\
1137 The known extensions are: %s\n\
1138 The following options override \"convert\" behavior:\n\
1139   -O format=FORMAT          set destination format to FORMAT\n\
1140   -O OPTION=VALUE           set output option\n\
1141   -f, --force               keep output file even given errors\n\
1142   --table-look=FILE         override tables' style with TableLook from FILE\n\
1143 Other options:\n\
1144   --help              display this help and exit\n\
1145   --version           output version information and exit\n",
1146           program_name, program_name, ds_cstr (&s));
1147   ds_destroy (&s);
1148 }