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