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