pspp-output: New option --table-look.
[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 spv_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 void
329 run_dump (int argc UNUSED, char **argv)
330 {
331   struct spv_reader *spv;
332   char *err = spv_open (argv[1], &spv);
333   if (err)
334     error (1, 0, "%s", err);
335
336   struct spv_item **items;
337   size_t n_items;
338   spv_select (spv, criteria, n_criteria, &items, &n_items);
339   for (size_t i = 0; i < n_items; i++)
340     if (items[i]->type == SPV_ITEM_TABLE)
341       {
342         pivot_table_dump (spv_item_get_table (items[i]), 0);
343         putchar ('\n');
344       }
345   free (items);
346
347   spv_close (spv);
348 }
349
350 static int
351 compare_borders (const void *a_, const void *b_)
352 {
353   const struct spvlb_border *const *ap = a_;
354   const struct spvlb_border *const *bp = b_;
355   uint32_t a = (*ap)->border_type;
356   uint32_t b = (*bp)->border_type;
357
358   return a < b ? -1 : a > b;
359 }
360
361 static int
362 compare_cells (const void *a_, const void *b_)
363 {
364   const struct spvlb_cell *const *ap = a_;
365   const struct spvlb_cell *const *bp = b_;
366   uint64_t a = (*ap)->index;
367   uint64_t b = (*bp)->index;
368
369   return a < b ? -1 : a > b;
370 }
371
372 static void
373 run_dump_light_table (int argc UNUSED, char **argv)
374 {
375   if (raw && isatty (STDOUT_FILENO))
376     error (1, 0, "not writing binary data to tty");
377
378   struct spv_reader *spv;
379   char *err = spv_open (argv[1], &spv);
380   if (err)
381     error (1, 0, "%s", err);
382
383   struct spv_item **items;
384   size_t n_items;
385   spv_select (spv, criteria, n_criteria, &items, &n_items);
386   for (size_t i = 0; i < n_items; i++)
387     {
388       if (!spv_item_is_light_table (items[i]))
389         continue;
390
391       char *error;
392       if (raw)
393         {
394           void *data;
395           size_t size;
396           error = spv_item_get_raw_light_table (items[i], &data, &size);
397           if (!error)
398             {
399               fwrite (data, size, 1, stdout);
400               free (data);
401             }
402         }
403       else
404         {
405           struct spvlb_table *table;
406           error = spv_item_get_light_table (items[i], &table);
407           if (!error)
408             {
409               if (sort)
410                 {
411                   qsort (table->borders->borders, table->borders->n_borders,
412                          sizeof *table->borders->borders, compare_borders);
413                   qsort (table->cells->cells, table->cells->n_cells,
414                          sizeof *table->cells->cells, compare_cells);
415                 }
416               spvlb_print_table (items[i]->bin_member, 0, table);
417               spvlb_free_table (table);
418             }
419         }
420       if (error)
421         {
422           msg (ME, "%s", error);
423           free (error);
424         }
425     }
426
427   free (items);
428
429   spv_close (spv);
430 }
431
432 static void
433 run_dump_legacy_data (int argc UNUSED, char **argv)
434 {
435   struct spv_reader *spv;
436   char *err = spv_open (argv[1], &spv);
437   if (err)
438     error (1, 0, "%s", err);
439
440   struct spv_item **items;
441   size_t n_items;
442   spv_select (spv, criteria, n_criteria, &items, &n_items);
443   for (size_t i = 0; i < n_items; i++)
444     if (spv_item_is_legacy_table (items[i]))
445       {
446         struct spv_data data;
447         char *error;
448         if (raw)
449           {
450             void *data;
451             size_t size;
452             error = spv_item_get_raw_legacy_data (items[i], &data, &size);
453             if (!error)
454               {
455                 fwrite (data, size, 1, stdout);
456                 free (data);
457               }
458           }
459         else
460           {
461             error = spv_item_get_legacy_data (items[i], &data);
462             if (!error)
463               {
464                 printf ("%s:\n", items[i]->bin_member);
465                 spv_data_dump (&data, stdout);
466                 spv_data_uninit (&data);
467                 printf ("\n");
468               }
469           }
470
471         if (error)
472           {
473             msg (ME, "%s", error);
474             free (error);
475           }
476       }
477   free (items);
478
479   spv_close (spv);
480 }
481
482 /* This is really bogus.
483
484    XPath doesn't have any notion of a default XML namespace, but all of the
485    elements in the documents we're interested in have a namespace.  Thus, we'd
486    need to require the XPath expressions to have a namespace on every single
487    element: vis:sourceVariable, vis:graph, and so on.  That's a pain.  So,
488    instead, we remove the default namespace from everyplace it occurs.  XPath
489    does support the null namespace, so this allows sourceVariable, graph,
490    etc. to work.
491
492    See http://plasmasturm.org/log/259/ and
493    https://mail.gnome.org/archives/xml/2003-April/msg00144.html for more
494    information.*/
495 static void
496 remove_default_xml_namespace (xmlNode *node)
497 {
498   if (node->ns && !node->ns->prefix)
499     node->ns = NULL;
500
501   for (xmlNode *child = node->children; child; child = child->next)
502     remove_default_xml_namespace (child);
503 }
504
505 static void
506 register_ns (xmlXPathContext *ctx, const char *prefix, const char *uri)
507 {
508   xmlXPathRegisterNs (ctx, CHAR_CAST (xmlChar *, prefix),
509                       CHAR_CAST (xmlChar *, uri));
510 }
511
512 static xmlXPathContext *
513 create_xpath_context (xmlDoc *doc)
514 {
515   xmlXPathContext *ctx = xmlXPathNewContext (doc);
516   register_ns (ctx, "vgr", "http://xml.spss.com/spss/viewer/viewer-graph");
517   register_ns (ctx, "vizml", "http://xml.spss.com/visualization");
518   register_ns (ctx, "vmd", "http://xml.spss.com/spss/viewer/viewer-model");
519   register_ns (ctx, "vps", "http://xml.spss.com/spss/viewer/viewer-pagesetup");
520   register_ns (ctx, "vst", "http://xml.spss.com/spss/viewer/viewer-style");
521   register_ns (ctx, "vtb", "http://xml.spss.com/spss/viewer/viewer-table");
522   register_ns (ctx, "vtl", "http://xml.spss.com/spss/viewer/table-looks");
523   register_ns (ctx, "vtt", "http://xml.spss.com/spss/viewer/viewer-treemodel");
524   register_ns (ctx, "vtx", "http://xml.spss.com/spss/viewer/viewer-text");
525   register_ns (ctx, "xsi", "http://www.w3.org/2001/XMLSchema-instance");
526   return ctx;
527 }
528
529 static void
530 dump_xml (int argc, char **argv, const char *member_name,
531           char *error_s, xmlDoc *doc)
532 {
533   if (!error_s)
534     {
535       if (argc == 2)
536         {
537           printf ("<!-- %s -->\n", member_name);
538           xmlElemDump (stdout, NULL, xmlDocGetRootElement (doc));
539           putchar ('\n');
540         }
541       else
542         {
543           bool any_results = false;
544
545           remove_default_xml_namespace (xmlDocGetRootElement (doc));
546           for (int i = 2; i < argc; i++)
547             {
548               xmlXPathContext *xpath_ctx = create_xpath_context (doc);
549               xmlXPathSetContextNode (xmlDocGetRootElement (doc),
550                                       xpath_ctx);
551               xmlXPathObject *xpath_obj = xmlXPathEvalExpression(
552                 CHAR_CAST (xmlChar *, argv[i]), xpath_ctx);
553               if (!xpath_obj)
554                 error (1, 0, _("%s: invalid XPath expression"), argv[i]);
555
556               const xmlNodeSet *nodes = xpath_obj->nodesetval;
557               if (nodes && nodes->nodeNr > 0)
558                 {
559                   if (!any_results)
560                     {
561                       printf ("<!-- %s -->\n", member_name);
562                       any_results = true;
563                     }
564                   for (size_t j = 0; j < nodes->nodeNr; j++)
565                     {
566                       xmlElemDump (stdout, doc, nodes->nodeTab[j]);
567                       putchar ('\n');
568                     }
569                 }
570
571               xmlXPathFreeObject (xpath_obj);
572               xmlXPathFreeContext (xpath_ctx);
573             }
574           if (any_results)
575             putchar ('\n');;
576         }
577       xmlFreeDoc (doc);
578     }
579   else
580     {
581       printf ("<!-- %s -->\n", member_name);
582       msg (ME, "%s", error_s);
583       free (error_s);
584     }
585 }
586
587 static void
588 run_dump_legacy_table (int argc, char **argv)
589 {
590   struct spv_reader *spv;
591   char *err = spv_open (argv[1], &spv);
592   if (err)
593     error (1, 0, "%s", err);
594
595   struct spv_item **items;
596   size_t n_items;
597   spv_select (spv, criteria, n_criteria, &items, &n_items);
598   for (size_t i = 0; i < n_items; i++)
599     if (spv_item_is_legacy_table (items[i]))
600       {
601         xmlDoc *doc;
602         char *error_s = spv_item_get_legacy_table (items[i], &doc);
603         dump_xml (argc, argv, items[i]->xml_member, error_s, doc);
604       }
605   free (items);
606
607   spv_close (spv);
608 }
609
610 static void
611 run_dump_structure (int argc, char **argv)
612 {
613   struct spv_reader *spv;
614   char *err = spv_open (argv[1], &spv);
615   if (err)
616     error (1, 0, "%s", err);
617
618   struct spv_item **items;
619   size_t n_items;
620   spv_select (spv, criteria, n_criteria, &items, &n_items);
621   const char *last_structure_member = NULL;
622   for (size_t i = 0; i < n_items; i++)
623     if (!last_structure_member || strcmp (items[i]->structure_member,
624                                           last_structure_member))
625       {
626         last_structure_member = items[i]->structure_member;
627
628         xmlDoc *doc;
629         char *error_s = spv_item_get_structure (items[i], &doc);
630         dump_xml (argc, argv, items[i]->structure_member, error_s, doc);
631       }
632   free (items);
633
634   spv_close (spv);
635 }
636
637 static void
638 run_is_legacy (int argc UNUSED, char **argv)
639 {
640   struct spv_reader *spv;
641   char *err = spv_open (argv[1], &spv);
642   if (err)
643     error (1, 0, "%s", err);
644
645   bool is_legacy = false;
646
647   struct spv_item **items;
648   size_t n_items;
649   spv_select (spv, criteria, n_criteria, &items, &n_items);
650   for (size_t i = 0; i < n_items; i++)
651     if (spv_item_is_legacy_table (items[i]))
652       {
653         is_legacy = true;
654         break;
655       }
656   free (items);
657
658   spv_close (spv);
659
660   exit (is_legacy ? EXIT_SUCCESS : EXIT_FAILURE);
661 }
662
663 struct command
664   {
665     const char *name;
666     int min_args, max_args;
667     void (*run) (int argc, char **argv);
668   };
669
670 static const struct command commands[] =
671   {
672     { "detect", 1, 1, run_detect },
673     { "dir", 1, 1, run_directory },
674     { "convert", 2, 2, run_convert },
675
676     /* Undocumented commands. */
677     { "dump", 1, 1, run_dump },
678     { "dump-light-table", 1, 1, run_dump_light_table },
679     { "dump-legacy-data", 1, 1, run_dump_legacy_data },
680     { "dump-legacy-table", 1, INT_MAX, run_dump_legacy_table },
681     { "dump-structure", 1, INT_MAX, run_dump_structure },
682     { "is-legacy", 1, 1, run_is_legacy },
683   };
684 static const int n_commands = sizeof commands / sizeof *commands;
685
686 static const struct command *
687 find_command (const char *name)
688 {
689   for (size_t i = 0; i < n_commands; i++)
690     {
691       const struct command *c = &commands[i];
692       if (!strcmp (name, c->name))
693         return c;
694     }
695   return NULL;
696 }
697
698 static void
699 emit_msg (const struct msg *m, void *aux UNUSED)
700 {
701   if (m->severity == MSG_S_ERROR || m->severity == MSG_S_WARNING)
702     n_warnings++;
703
704   char *s = msg_to_string (m);
705   fprintf (stderr, "%s\n", s);
706   free (s);
707 }
708
709 int
710 main (int argc, char **argv)
711 {
712   set_program_name (argv[0]);
713   msg_set_handler (emit_msg, NULL);
714   settings_init ();
715   i18n_init ();
716
717   parse_options (argc, argv);
718
719   argc -= optind;
720   argv += optind;
721
722   if (argc < 1)
723     error (1, 0, _("missing command name (use --help for help)"));
724
725   const struct command *c = find_command (argv[0]);
726   if (!c)
727     error (1, 0, _("unknown command \"%s\" (use --help for help)"), argv[0]);
728
729   int n_args = argc - 1;
730   if (n_args < c->min_args || n_args > c->max_args)
731     {
732       if (c->min_args == c->max_args)
733         {
734           error (1, 0,
735                  ngettext ("\"%s\" command takes exactly %d argument",
736                            "\"%s\" command takes exactly %d arguments",
737                            c->min_args), c->name, c->min_args);
738         }
739       else if (c->max_args == INT_MAX)
740         {
741           error (1, 0,
742                  ngettext ("\"%s\" command requires at least %d argument",
743                            "\"%s\" command requires at least %d arguments",
744                            c->min_args), c->name, c->min_args);
745         }
746       else
747         {
748           error (1, 0,
749                  _("\"%s\" command requires between %d and %d arguments"),
750                  c->name, c->min_args, c->max_args);
751         }
752     }
753
754   c->run (argc, argv);
755
756   spv_table_look_destroy (table_look);
757   i18n_done ();
758
759   return n_warnings ? EXIT_FAILURE : EXIT_SUCCESS;
760 }
761
762 static struct spv_criteria *
763 get_criteria (void)
764 {
765   if (!n_criteria || new_criteria)
766     {
767       new_criteria = false;
768       if (n_criteria >= allocated_criteria)
769         criteria = x2nrealloc (criteria, &allocated_criteria,
770                                sizeof *criteria);
771       criteria[n_criteria++] = (struct spv_criteria) SPV_CRITERIA_INITIALIZER;
772     }
773
774   return &criteria[n_criteria - 1];
775 }
776
777 static void
778 parse_select (char *arg)
779 {
780   bool invert = arg[0] == '^';
781   arg += invert;
782
783   unsigned classes = 0;
784   for (char *token = strtok (arg, ","); token; token = strtok (NULL, ","))
785     {
786       if (!strcmp (arg, "all"))
787         classes = SPV_ALL_CLASSES;
788       else if (!strcmp (arg, "help"))
789         {
790           puts (_("The following object classes are supported:"));
791           for (int class = 0; class < SPV_N_CLASSES; class++)
792             printf ("- %s\n", spv_item_class_to_string (class));
793           exit (0);
794         }
795       else
796         {
797           int class = spv_item_class_from_string (token);
798           if (class == SPV_N_CLASSES)
799             error (1, 0, _("%s: unknown object class (use --select=help "
800                            "for help"), arg);
801           classes |= 1u << class;
802         }
803     }
804
805   struct spv_criteria *c = get_criteria ();
806   c->classes = invert ? classes ^ SPV_ALL_CLASSES : classes;
807 }
808
809 static struct spv_criteria_match *
810 get_criteria_match (const char **arg)
811 {
812   struct spv_criteria *c = get_criteria ();
813   if ((*arg)[0] == '^')
814     {
815       (*arg)++;
816       return &c->exclude;
817     }
818   else
819     return &c->include;
820 }
821
822 static void
823 parse_commands (const char *arg)
824 {
825   struct spv_criteria_match *cm = get_criteria_match (&arg);
826   string_array_parse (&cm->commands, ss_cstr (arg), ss_cstr (","));
827 }
828
829 static void
830 parse_subtypes (const char *arg)
831 {
832   struct spv_criteria_match *cm = get_criteria_match (&arg);
833   string_array_parse (&cm->subtypes, ss_cstr (arg), ss_cstr (","));
834 }
835
836 static void
837 parse_labels (const char *arg)
838 {
839   struct spv_criteria_match *cm = get_criteria_match (&arg);
840   string_array_parse (&cm->labels, ss_cstr (arg), ss_cstr (","));
841 }
842
843 static void
844 parse_instances (char *arg)
845 {
846   struct spv_criteria *c = get_criteria ();
847   size_t allocated_instances = c->n_instances;
848
849   for (char *token = strtok (arg, ","); token; token = strtok (NULL, ","))
850     {
851       if (c->n_instances >= allocated_instances)
852         c->instances = x2nrealloc (c->instances, &allocated_instances,
853                                    sizeof *c->instances);
854
855       c->instances[c->n_instances++] = (!strcmp (token, "last") ? -1
856                                         : atoi (token));
857     }
858 }
859
860 static void
861 parse_nth_commands (char *arg)
862 {
863   struct spv_criteria *c = get_criteria ();
864   size_t allocated_commands = c->n_commands;
865
866   for (char *token = strtok (arg, ","); token; token = strtok (NULL, ","))
867     {
868       if (c->n_commands >= allocated_commands)
869         c->commands = x2nrealloc (c->commands, &allocated_commands,
870                                    sizeof *c->commands);
871
872       c->commands[c->n_commands++] = atoi (token);
873     }
874 }
875
876 static void
877 parse_members (const char *arg)
878 {
879   struct spv_criteria *cm = get_criteria ();
880   string_array_parse (&cm->members, ss_cstr (arg), ss_cstr (","));
881 }
882
883 static void
884 parse_table_look (const char *arg)
885 {
886   spv_table_look_destroy (table_look);
887   char *error_s = spv_table_look_read (arg, &table_look);
888   if (error_s)
889     error (1, 0, "%s", error_s);
890 }
891
892 static void
893 parse_options (int argc, char *argv[])
894 {
895   for (;;)
896     {
897       enum
898         {
899           OPT_MEMBER_NAMES = UCHAR_MAX + 1,
900           OPT_SHOW_HIDDEN,
901           OPT_SELECT,
902           OPT_COMMANDS,
903           OPT_NTH_COMMANDS,
904           OPT_SUBTYPES,
905           OPT_LABELS,
906           OPT_INSTANCES,
907           OPT_MEMBERS,
908           OPT_ERRORS,
909           OPT_OR,
910           OPT_SORT,
911           OPT_RAW,
912           OPT_TABLE_LOOK,
913         };
914       static const struct option long_options[] =
915         {
916           /* Input selection options. */
917           { "show-hidden", no_argument, NULL, OPT_SHOW_HIDDEN },
918           { "select", required_argument, NULL, OPT_SELECT },
919           { "commands", required_argument, NULL, OPT_COMMANDS },
920           { "nth-commands", required_argument, NULL, OPT_NTH_COMMANDS },
921           { "subtypes", required_argument, NULL, OPT_SUBTYPES },
922           { "labels", required_argument, NULL, OPT_LABELS },
923           { "instances", required_argument, NULL, OPT_INSTANCES },
924           { "members", required_argument, NULL, OPT_MEMBERS },
925           { "errors", no_argument, NULL, OPT_ERRORS },
926           { "or", no_argument, NULL, OPT_OR },
927
928           /* "dir" command options. */
929           { "member-names", no_argument, NULL, OPT_MEMBER_NAMES },
930
931           /* "convert" command options. */
932           { "force", no_argument, NULL, 'f' },
933           { "table-look", required_argument, NULL, OPT_TABLE_LOOK },
934
935           /* "dump-light-table" command options. */
936           { "sort", no_argument, NULL, OPT_SORT },
937           { "raw", no_argument, NULL, OPT_RAW },
938
939           { "help", no_argument, NULL, 'h' },
940           { "version", no_argument, NULL, 'v' },
941
942           { NULL, 0, NULL, 0 },
943         };
944
945       int c;
946
947       c = getopt_long (argc, argv, "O:hvf", long_options, NULL);
948       if (c == -1)
949         break;
950
951       switch (c)
952         {
953         case 'O':
954           output_driver_parse_option (optarg, &output_options);
955           break;
956
957         case OPT_MEMBER_NAMES:
958           show_member_names = true;
959           break;
960
961         case OPT_SHOW_HIDDEN:
962           get_criteria ()->include_hidden = true;
963           break;
964
965         case OPT_SELECT:
966           parse_select (optarg);
967           break;
968
969         case OPT_COMMANDS:
970           parse_commands (optarg);
971           break;
972
973         case OPT_NTH_COMMANDS:
974           parse_nth_commands (optarg);
975           break;
976
977         case OPT_SUBTYPES:
978           parse_subtypes (optarg);
979           break;
980
981         case OPT_LABELS:
982           parse_labels (optarg);
983           break;
984
985         case OPT_INSTANCES:
986           parse_instances (optarg);
987           break;
988
989         case OPT_MEMBERS:
990           parse_members (optarg);
991           break;
992
993         case OPT_ERRORS:
994           get_criteria ()->error = true;
995           break;
996
997         case OPT_OR:
998           new_criteria = true;
999           break;
1000
1001         case OPT_SORT:
1002           sort = true;
1003           break;
1004
1005         case OPT_RAW:
1006           raw = true;
1007           break;
1008
1009         case OPT_TABLE_LOOK:
1010           parse_table_look (optarg);
1011           break;
1012
1013         case 'f':
1014           force = true;
1015           break;
1016
1017         case 'v':
1018           version_etc (stdout, "pspp-output", PACKAGE_NAME, PACKAGE_VERSION,
1019                        "Ben Pfaff", "John Darrington", NULL_SENTINEL);
1020           exit (EXIT_SUCCESS);
1021
1022         case 'h':
1023           usage ();
1024           exit (EXIT_SUCCESS);
1025
1026         default:
1027           exit (EXIT_FAILURE);
1028         }
1029     }
1030 }
1031
1032 static void
1033 usage (void)
1034 {
1035   struct string s = DS_EMPTY_INITIALIZER;
1036   struct string_set formats = STRING_SET_INITIALIZER(formats);
1037   output_get_supported_formats (&formats);
1038   const char *format;
1039   const struct string_set_node *node;
1040   STRING_SET_FOR_EACH (format, node, &formats)
1041     {
1042       if (!ds_is_empty (&s))
1043         ds_put_byte (&s, ' ');
1044       ds_put_cstr (&s, format);
1045     }
1046   string_set_destroy (&formats);
1047
1048   printf ("\
1049 %s, a utility for working with SPSS viewer (.spv) files.\n\
1050 Usage: %s [OPTION]... COMMAND ARG...\n\
1051 \n\
1052 The following commands are available:\n\
1053   detect FILE            Detect whether FILE is an SPV file.\n\
1054   dir FILE               List tables and other items in FILE.\n\
1055   convert SOURCE DEST    Convert .spv SOURCE to DEST.\n\
1056 \n\
1057 Input selection options for \"dir\" and \"convert\":\n\
1058   --select=CLASS...   include only some kinds of objects\n\
1059   --select=help       print known object classes\n\
1060   --commands=COMMAND...  include only specified COMMANDs\n\
1061   --nth-commands=N...  include only the Nth instance of selected commands\n\
1062   --subtypes=SUBTYPE...  include only specified SUBTYPEs of output\n\
1063   --labels=LABEL...   include only output objects with the given LABELs\n\
1064   --instances=INSTANCE...  include only the given object INSTANCEs\n\
1065   --show-hidden       include hidden output objects\n\
1066   --or                separate two sets of selection options\n\
1067 \n\
1068 \"convert\" by default infers the destination's format from its extension.\n\
1069 The known extensions are: %s\n\
1070 The following options override \"convert\" behavior:\n\
1071   -O format=FORMAT          set destination format to FORMAT\n\
1072   -O OPTION=VALUE           set output option\n\
1073   -f, --force               keep output file even given errors\n\
1074   --table-look=FILE         override tables' style with TableLook from FILE\n\
1075 Other options:\n\
1076   --help              display this help and exit\n\
1077   --version           output version information and exit\n",
1078           program_name, program_name, ds_cstr (&s));
1079   ds_destroy (&s);
1080 }