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