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