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