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