d918496e8d44b0449bb094dd7018fbb1ee67f6bc
[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   if (raw && isatty (STDOUT_FILENO))
517     error (1, 0, "not writing binary data to tty");
518
519   struct spv_item **items;
520   size_t n_items;
521   spv_select (spv, criteria, n_criteria, &items, &n_items);
522   for (size_t i = 0; i < n_items; i++)
523     if (spv_item_is_legacy_table (items[i]))
524       {
525         struct spv_data data;
526         char *error;
527         if (raw)
528           {
529             void *data;
530             size_t size;
531             error = spv_item_get_raw_legacy_data (items[i], &data, &size);
532             if (!error)
533               {
534                 fwrite (data, size, 1, stdout);
535                 free (data);
536               }
537           }
538         else
539           {
540             error = spv_item_get_legacy_data (items[i], &data);
541             if (!error)
542               {
543                 printf ("%s:\n", items[i]->bin_member);
544                 spv_data_dump (&data, stdout);
545                 spv_data_uninit (&data);
546                 printf ("\n");
547               }
548           }
549
550         if (error)
551           {
552             msg (ME, "%s", error);
553             free (error);
554           }
555       }
556   free (items);
557
558   spv_close (spv);
559 }
560
561 /* This is really bogus.
562
563    XPath doesn't have any notion of a default XML namespace, but all of the
564    elements in the documents we're interested in have a namespace.  Thus, we'd
565    need to require the XPath expressions to have a namespace on every single
566    element: vis:sourceVariable, vis:graph, and so on.  That's a pain.  So,
567    instead, we remove the default namespace from everyplace it occurs.  XPath
568    does support the null namespace, so this allows sourceVariable, graph,
569    etc. to work.
570
571    See http://plasmasturm.org/log/259/ and
572    https://mail.gnome.org/archives/xml/2003-April/msg00144.html for more
573    information.*/
574 static void
575 remove_default_xml_namespace (xmlNode *node)
576 {
577   if (node->ns && !node->ns->prefix)
578     node->ns = NULL;
579
580   for (xmlNode *child = node->children; child; child = child->next)
581     remove_default_xml_namespace (child);
582 }
583
584 static void
585 register_ns (xmlXPathContext *ctx, const char *prefix, const char *uri)
586 {
587   xmlXPathRegisterNs (ctx, CHAR_CAST (xmlChar *, prefix),
588                       CHAR_CAST (xmlChar *, uri));
589 }
590
591 static xmlXPathContext *
592 create_xpath_context (xmlDoc *doc)
593 {
594   xmlXPathContext *ctx = xmlXPathNewContext (doc);
595   register_ns (ctx, "vgr", "http://xml.spss.com/spss/viewer/viewer-graph");
596   register_ns (ctx, "vizml", "http://xml.spss.com/visualization");
597   register_ns (ctx, "vmd", "http://xml.spss.com/spss/viewer/viewer-model");
598   register_ns (ctx, "vps", "http://xml.spss.com/spss/viewer/viewer-pagesetup");
599   register_ns (ctx, "vst", "http://xml.spss.com/spss/viewer/viewer-style");
600   register_ns (ctx, "vtb", "http://xml.spss.com/spss/viewer/viewer-table");
601   register_ns (ctx, "vtl", "http://xml.spss.com/spss/viewer/table-looks");
602   register_ns (ctx, "vtt", "http://xml.spss.com/spss/viewer/viewer-treemodel");
603   register_ns (ctx, "vtx", "http://xml.spss.com/spss/viewer/viewer-text");
604   register_ns (ctx, "xsi", "http://www.w3.org/2001/XMLSchema-instance");
605   return ctx;
606 }
607
608 static void
609 dump_xml (int argc, char **argv, const char *member_name,
610           char *error_s, xmlDoc *doc)
611 {
612   if (!error_s)
613     {
614       if (argc == 2)
615         {
616           printf ("<!-- %s -->\n", member_name);
617           xmlElemDump (stdout, NULL, xmlDocGetRootElement (doc));
618           putchar ('\n');
619         }
620       else
621         {
622           bool any_results = false;
623
624           remove_default_xml_namespace (xmlDocGetRootElement (doc));
625           for (int i = 2; i < argc; i++)
626             {
627               xmlXPathContext *xpath_ctx = create_xpath_context (doc);
628               xmlXPathSetContextNode (xmlDocGetRootElement (doc),
629                                       xpath_ctx);
630               xmlXPathObject *xpath_obj = xmlXPathEvalExpression(
631                 CHAR_CAST (xmlChar *, argv[i]), xpath_ctx);
632               if (!xpath_obj)
633                 error (1, 0, _("%s: invalid XPath expression"), argv[i]);
634
635               const xmlNodeSet *nodes = xpath_obj->nodesetval;
636               if (nodes && nodes->nodeNr > 0)
637                 {
638                   if (!any_results)
639                     {
640                       printf ("<!-- %s -->\n", member_name);
641                       any_results = true;
642                     }
643                   for (size_t j = 0; j < nodes->nodeNr; j++)
644                     {
645                       xmlElemDump (stdout, doc, nodes->nodeTab[j]);
646                       putchar ('\n');
647                     }
648                 }
649
650               xmlXPathFreeObject (xpath_obj);
651               xmlXPathFreeContext (xpath_ctx);
652             }
653           if (any_results)
654             putchar ('\n');;
655         }
656       xmlFreeDoc (doc);
657     }
658   else
659     {
660       printf ("<!-- %s -->\n", member_name);
661       msg (ME, "%s", error_s);
662       free (error_s);
663     }
664 }
665
666 static void
667 run_dump_legacy_table (int argc, char **argv)
668 {
669   struct spv_reader *spv;
670   char *err = spv_open (argv[1], &spv);
671   if (err)
672     error (1, 0, "%s", err);
673
674   struct spv_item **items;
675   size_t n_items;
676   spv_select (spv, criteria, n_criteria, &items, &n_items);
677   for (size_t i = 0; i < n_items; i++)
678     if (spv_item_is_legacy_table (items[i]))
679       {
680         xmlDoc *doc;
681         char *error_s = spv_item_get_legacy_table (items[i], &doc);
682         dump_xml (argc, argv, items[i]->xml_member, error_s, doc);
683       }
684   free (items);
685
686   spv_close (spv);
687 }
688
689 static void
690 run_dump_structure (int argc, char **argv)
691 {
692   struct spv_reader *spv;
693   char *err = spv_open (argv[1], &spv);
694   if (err)
695     error (1, 0, "%s", err);
696
697   struct spv_item **items;
698   size_t n_items;
699   spv_select (spv, criteria, n_criteria, &items, &n_items);
700   const char *last_structure_member = NULL;
701   for (size_t i = 0; i < n_items; i++)
702     if (!last_structure_member || strcmp (items[i]->structure_member,
703                                           last_structure_member))
704       {
705         last_structure_member = items[i]->structure_member;
706
707         xmlDoc *doc;
708         char *error_s = spv_item_get_structure (items[i], &doc);
709         dump_xml (argc, argv, items[i]->structure_member, error_s, doc);
710       }
711   free (items);
712
713   spv_close (spv);
714 }
715
716 static void
717 run_is_legacy (int argc UNUSED, char **argv)
718 {
719   struct spv_reader *spv;
720   char *err = spv_open (argv[1], &spv);
721   if (err)
722     error (1, 0, "%s", err);
723
724   bool is_legacy = false;
725
726   struct spv_item **items;
727   size_t n_items;
728   spv_select (spv, criteria, n_criteria, &items, &n_items);
729   for (size_t i = 0; i < n_items; i++)
730     if (spv_item_is_legacy_table (items[i]))
731       {
732         is_legacy = true;
733         break;
734       }
735   free (items);
736
737   spv_close (spv);
738
739   exit (is_legacy ? EXIT_SUCCESS : EXIT_FAILURE);
740 }
741
742 struct command
743   {
744     const char *name;
745     int min_args, max_args;
746     void (*run) (int argc, char **argv);
747   };
748
749 static const struct command commands[] =
750   {
751     { "detect", 1, 1, run_detect },
752     { "dir", 1, 1, run_directory },
753     { "convert", 2, 2, run_convert },
754     { "get-table-look", 2, 2, run_get_table_look },
755     { "convert-table-look", 2, 2, run_convert_table_look },
756
757     /* Undocumented commands. */
758     { "dump", 1, 1, run_dump },
759     { "dump-light-table", 1, 1, run_dump_light_table },
760     { "dump-legacy-data", 1, 1, run_dump_legacy_data },
761     { "dump-legacy-table", 1, INT_MAX, run_dump_legacy_table },
762     { "dump-structure", 1, INT_MAX, run_dump_structure },
763     { "is-legacy", 1, 1, run_is_legacy },
764   };
765 static const int n_commands = sizeof commands / sizeof *commands;
766
767 static const struct command *
768 find_command (const char *name)
769 {
770   for (size_t i = 0; i < n_commands; i++)
771     {
772       const struct command *c = &commands[i];
773       if (!strcmp (name, c->name))
774         return c;
775     }
776   return NULL;
777 }
778
779 static void
780 emit_msg (const struct msg *m, void *aux UNUSED)
781 {
782   if (m->severity == MSG_S_ERROR || m->severity == MSG_S_WARNING)
783     n_warnings++;
784
785   char *s = msg_to_string (m);
786   fprintf (stderr, "%s\n", s);
787   free (s);
788 }
789
790 int
791 main (int argc, char **argv)
792 {
793   set_program_name (argv[0]);
794   msg_set_handler (emit_msg, NULL);
795   settings_init ();
796   i18n_init ();
797
798   parse_options (argc, argv);
799
800   argc -= optind;
801   argv += optind;
802
803   if (argc < 1)
804     error (1, 0, _("missing command name (use --help for help)"));
805
806   const struct command *c = find_command (argv[0]);
807   if (!c)
808     error (1, 0, _("unknown command \"%s\" (use --help for help)"), argv[0]);
809
810   int n_args = argc - 1;
811   if (n_args < c->min_args || n_args > c->max_args)
812     {
813       if (c->min_args == c->max_args)
814         {
815           error (1, 0,
816                  ngettext ("\"%s\" command takes exactly %d argument",
817                            "\"%s\" command takes exactly %d arguments",
818                            c->min_args), c->name, c->min_args);
819         }
820       else if (c->max_args == INT_MAX)
821         {
822           error (1, 0,
823                  ngettext ("\"%s\" command requires at least %d argument",
824                            "\"%s\" command requires at least %d arguments",
825                            c->min_args), c->name, c->min_args);
826         }
827       else
828         {
829           error (1, 0,
830                  _("\"%s\" command requires between %d and %d arguments"),
831                  c->name, c->min_args, c->max_args);
832         }
833     }
834
835   c->run (argc, argv);
836
837   pivot_table_look_unref (table_look);
838   i18n_done ();
839
840   return n_warnings ? EXIT_FAILURE : EXIT_SUCCESS;
841 }
842
843 static struct spv_criteria *
844 get_criteria (void)
845 {
846   if (!n_criteria || new_criteria)
847     {
848       new_criteria = false;
849       if (n_criteria >= allocated_criteria)
850         criteria = x2nrealloc (criteria, &allocated_criteria,
851                                sizeof *criteria);
852       criteria[n_criteria++] = (struct spv_criteria) SPV_CRITERIA_INITIALIZER;
853     }
854
855   return &criteria[n_criteria - 1];
856 }
857
858 static void
859 parse_select (char *arg)
860 {
861   bool invert = arg[0] == '^';
862   arg += invert;
863
864   unsigned classes = 0;
865   for (char *token = strtok (arg, ","); token; token = strtok (NULL, ","))
866     {
867       if (!strcmp (arg, "all"))
868         classes = SPV_ALL_CLASSES;
869       else if (!strcmp (arg, "help"))
870         {
871           puts (_("The following object classes are supported:"));
872           for (int class = 0; class < SPV_N_CLASSES; class++)
873             printf ("- %s\n", spv_item_class_to_string (class));
874           exit (0);
875         }
876       else
877         {
878           int class = spv_item_class_from_string (token);
879           if (class == SPV_N_CLASSES)
880             error (1, 0, _("%s: unknown object class (use --select=help "
881                            "for help"), arg);
882           classes |= 1u << class;
883         }
884     }
885
886   struct spv_criteria *c = get_criteria ();
887   c->classes = invert ? classes ^ SPV_ALL_CLASSES : classes;
888 }
889
890 static struct spv_criteria_match *
891 get_criteria_match (const char **arg)
892 {
893   struct spv_criteria *c = get_criteria ();
894   if ((*arg)[0] == '^')
895     {
896       (*arg)++;
897       return &c->exclude;
898     }
899   else
900     return &c->include;
901 }
902
903 static void
904 parse_commands (const char *arg)
905 {
906   struct spv_criteria_match *cm = get_criteria_match (&arg);
907   string_array_parse (&cm->commands, ss_cstr (arg), ss_cstr (","));
908 }
909
910 static void
911 parse_subtypes (const char *arg)
912 {
913   struct spv_criteria_match *cm = get_criteria_match (&arg);
914   string_array_parse (&cm->subtypes, ss_cstr (arg), ss_cstr (","));
915 }
916
917 static void
918 parse_labels (const char *arg)
919 {
920   struct spv_criteria_match *cm = get_criteria_match (&arg);
921   string_array_parse (&cm->labels, ss_cstr (arg), ss_cstr (","));
922 }
923
924 static void
925 parse_instances (char *arg)
926 {
927   struct spv_criteria *c = get_criteria ();
928   size_t allocated_instances = c->n_instances;
929
930   for (char *token = strtok (arg, ","); token; token = strtok (NULL, ","))
931     {
932       if (c->n_instances >= allocated_instances)
933         c->instances = x2nrealloc (c->instances, &allocated_instances,
934                                    sizeof *c->instances);
935
936       c->instances[c->n_instances++] = (!strcmp (token, "last") ? -1
937                                         : atoi (token));
938     }
939 }
940
941 static void
942 parse_nth_commands (char *arg)
943 {
944   struct spv_criteria *c = get_criteria ();
945   size_t allocated_commands = c->n_commands;
946
947   for (char *token = strtok (arg, ","); token; token = strtok (NULL, ","))
948     {
949       if (c->n_commands >= allocated_commands)
950         c->commands = x2nrealloc (c->commands, &allocated_commands,
951                                    sizeof *c->commands);
952
953       c->commands[c->n_commands++] = atoi (token);
954     }
955 }
956
957 static void
958 parse_members (const char *arg)
959 {
960   struct spv_criteria *cm = get_criteria ();
961   string_array_parse (&cm->members, ss_cstr (arg), ss_cstr (","));
962 }
963
964 static void
965 parse_table_look (const char *arg)
966 {
967   pivot_table_look_unref (table_look);
968
969   char *error_s = spv_table_look_read (arg, &table_look);
970   if (error_s)
971     error (1, 0, "%s", error_s);
972 }
973
974 static void
975 parse_options (int argc, char *argv[])
976 {
977   for (;;)
978     {
979       enum
980         {
981           OPT_MEMBER_NAMES = UCHAR_MAX + 1,
982           OPT_SHOW_HIDDEN,
983           OPT_SELECT,
984           OPT_COMMANDS,
985           OPT_NTH_COMMANDS,
986           OPT_SUBTYPES,
987           OPT_LABELS,
988           OPT_INSTANCES,
989           OPT_MEMBERS,
990           OPT_ERRORS,
991           OPT_OR,
992           OPT_SORT,
993           OPT_RAW,
994           OPT_TABLE_LOOK,
995           OPT_HELP_DEVELOPER,
996         };
997       static const struct option long_options[] =
998         {
999           /* Input selection options. */
1000           { "show-hidden", no_argument, NULL, OPT_SHOW_HIDDEN },
1001           { "select", required_argument, NULL, OPT_SELECT },
1002           { "commands", required_argument, NULL, OPT_COMMANDS },
1003           { "nth-commands", required_argument, NULL, OPT_NTH_COMMANDS },
1004           { "subtypes", required_argument, NULL, OPT_SUBTYPES },
1005           { "labels", required_argument, NULL, OPT_LABELS },
1006           { "instances", required_argument, NULL, OPT_INSTANCES },
1007           { "members", required_argument, NULL, OPT_MEMBERS },
1008           { "errors", no_argument, NULL, OPT_ERRORS },
1009           { "or", no_argument, NULL, OPT_OR },
1010
1011           /* "dir" command options. */
1012           { "member-names", no_argument, NULL, OPT_MEMBER_NAMES },
1013
1014           /* "convert" command options. */
1015           { "force", no_argument, NULL, 'f' },
1016           { "table-look", required_argument, NULL, OPT_TABLE_LOOK },
1017
1018           /* "dump-light-table" command options. */
1019           { "sort", no_argument, NULL, OPT_SORT },
1020           { "raw", no_argument, NULL, OPT_RAW },
1021
1022           { "help", no_argument, NULL, 'h' },
1023           { "help-developer", no_argument, NULL, OPT_HELP_DEVELOPER },
1024           { "version", no_argument, NULL, 'v' },
1025
1026           { NULL, 0, NULL, 0 },
1027         };
1028
1029       int c;
1030
1031       c = getopt_long (argc, argv, "O:hvf", long_options, NULL);
1032       if (c == -1)
1033         break;
1034
1035       switch (c)
1036         {
1037         case 'O':
1038           output_driver_parse_option (optarg, &output_options);
1039           break;
1040
1041         case OPT_MEMBER_NAMES:
1042           show_member_names = true;
1043           break;
1044
1045         case OPT_SHOW_HIDDEN:
1046           get_criteria ()->include_hidden = true;
1047           break;
1048
1049         case OPT_SELECT:
1050           parse_select (optarg);
1051           break;
1052
1053         case OPT_COMMANDS:
1054           parse_commands (optarg);
1055           break;
1056
1057         case OPT_NTH_COMMANDS:
1058           parse_nth_commands (optarg);
1059           break;
1060
1061         case OPT_SUBTYPES:
1062           parse_subtypes (optarg);
1063           break;
1064
1065         case OPT_LABELS:
1066           parse_labels (optarg);
1067           break;
1068
1069         case OPT_INSTANCES:
1070           parse_instances (optarg);
1071           break;
1072
1073         case OPT_MEMBERS:
1074           parse_members (optarg);
1075           break;
1076
1077         case OPT_ERRORS:
1078           get_criteria ()->error = true;
1079           break;
1080
1081         case OPT_OR:
1082           new_criteria = true;
1083           break;
1084
1085         case OPT_SORT:
1086           sort = true;
1087           break;
1088
1089         case OPT_RAW:
1090           raw = true;
1091           break;
1092
1093         case OPT_TABLE_LOOK:
1094           parse_table_look (optarg);
1095           break;
1096
1097         case 'f':
1098           force = true;
1099           break;
1100
1101         case 'v':
1102           version_etc (stdout, "pspp-output", PACKAGE_NAME, PACKAGE_VERSION,
1103                        "Ben Pfaff", "John Darrington", NULL_SENTINEL);
1104           exit (EXIT_SUCCESS);
1105
1106         case 'h':
1107           usage ();
1108           exit (EXIT_SUCCESS);
1109
1110         case OPT_HELP_DEVELOPER:
1111           developer_usage ();
1112           exit (EXIT_SUCCESS);
1113
1114         default:
1115           exit (EXIT_FAILURE);
1116         }
1117     }
1118 }
1119
1120 static void
1121 usage (void)
1122 {
1123   struct string s = DS_EMPTY_INITIALIZER;
1124   struct string_set formats = STRING_SET_INITIALIZER(formats);
1125   output_get_supported_formats (&formats);
1126   const char *format;
1127   const struct string_set_node *node;
1128   STRING_SET_FOR_EACH (format, node, &formats)
1129     {
1130       if (!ds_is_empty (&s))
1131         ds_put_byte (&s, ' ');
1132       ds_put_cstr (&s, format);
1133     }
1134   string_set_destroy (&formats);
1135
1136   printf ("\
1137 %s, a utility for working with SPSS viewer (.spv) files.\n\
1138 Usage: %s [OPTION]... COMMAND ARG...\n\
1139 \n\
1140 The following commands are available:\n\
1141   detect FILE            Detect whether FILE is an SPV file.\n\
1142   dir FILE               List tables and other items in FILE.\n\
1143   convert SOURCE DEST    Convert .spv SOURCE to DEST.\n\
1144   get-table-look SOURCE DEST  Copies first selected TableLook into DEST\n\
1145   convert-table-look SOURCE DEST  Copies .tlo or .stt SOURCE into DEST\n\
1146 \n\
1147 Input selection options for \"dir\" and \"convert\":\n\
1148   --select=CLASS...   include only some kinds of objects\n\
1149   --select=help       print known object classes\n\
1150   --commands=COMMAND...  include only specified COMMANDs\n\
1151   --nth-commands=N...  include only the Nth instance of selected commands\n\
1152   --subtypes=SUBTYPE...  include only specified SUBTYPEs of output\n\
1153   --labels=LABEL...   include only output objects with the given LABELs\n\
1154   --instances=INSTANCE...  include only the given object INSTANCEs\n\
1155   --show-hidden       include hidden output objects\n\
1156   --or                separate two sets of selection options\n\
1157 \n\
1158 \"convert\" by default infers the destination's format from its extension.\n\
1159 The known extensions are: %s\n\
1160 The following options override \"convert\" behavior:\n\
1161   -O format=FORMAT          set destination format to FORMAT\n\
1162   -O OPTION=VALUE           set output option\n\
1163   -f, --force               keep output file even given errors\n\
1164   --table-look=FILE         override tables' style with TableLook from FILE\n\
1165 Other options:\n\
1166   --help              display this help and exit\n\
1167   --help-developer    display help for developer commands and exit\n\
1168   --version           output version information and exit\n",
1169           program_name, program_name, ds_cstr (&s));
1170   ds_destroy (&s);
1171 }
1172
1173 static void
1174 developer_usage (void)
1175 {
1176   printf ("\
1177 The following developer commands are available:\n\
1178   dump FILE              Dump pivot table structure\n\
1179   [--raw | --sort] dump-light-table FILE  Dump light tables\n\
1180   [--raw] dump-legacy-data FILE  Dump legacy table data\n\
1181   dump-legacy-table FILE [XPATH]...  Dump legacy table XML\n\
1182   dump-structure FILE [XPATH]...  Dump structure XML\n\
1183   is-legacy FILE         Exit with status 0 if any legacy table selected\n\
1184 \n\
1185 Additional input selection options:\n\
1186   --members=MEMBER...    include only objects with these Zip member names\n\
1187   --errors               include only objects that cannot be loaded\n\
1188 \n\
1189 Additional options for \"dir\" command:\n\
1190   --member-names         show Zip member names with objects\n\
1191 \n\
1192 Other options:\n\
1193   --raw                  print raw binary data instead of a parsed version\n\
1194   --sort                 sort borders and areas for shorter \"diff\" output\n");
1195 }