lexer: Move lex_ellipsize() into string module, as str_ellipsize().
[pspp.git] / utilities / pspp-output.c
blob3a695efa299d98dfde590e9f17c3d702cdb03b3c
1 /* PSPP - a program for statistical analysis.
2 Copyright (C) 2017, 2018 Free Software Foundation, Inc.
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.
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.
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/>. */
17 #include <config.h>
19 #include <cairo.h>
20 #include <getopt.h>
21 #include <limits.h>
22 #include <stdlib.h>
23 #include <unistd.h>
24 #include <unistr.h>
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 "libpspp/zip-reader.h"
34 #include "output/driver.h"
35 #include "output/output-item.h"
36 #include "output/pivot-table.h"
37 #include "output/page-setup.h"
38 #include "output/select.h"
39 #include "output/spv/light-binary-parser.h"
40 #include "output/spv/spv-legacy-data.h"
41 #include "output/spv/spv-light-decoder.h"
42 #include "output/spv/spv-table-look.h"
43 #include "output/spv/spv.h"
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"
51 #include <libxml/tree.h>
52 #include <libxml/xpath.h>
53 #include <libxml/xpathInternals.h>
55 #include "gettext.h"
56 #define _(msgid) gettext (msgid)
58 /* -O key=value: Output driver options. */
59 static struct string_map output_options
60 = STRING_MAP_INITIALIZER (output_options);
62 /* --member-names: Include .zip member name in "dir" output. */
63 static bool show_member_names;
65 /* --show-hidden, --select, --commands, ...: Selection criteria. */
66 static struct output_criteria *criteria;
67 static size_t n_criteria, allocated_criteria;
69 /* --or: Add new element to 'criteria' array. */
70 static bool new_criteria;
72 /* --sort: Sort members under dump-light-table, to make comparisons easier. */
73 static bool sort;
75 /* --raw: Dump raw binary data in "dump-light-table"; dump all strings in
76 "strings". */
77 static bool raw;
79 /* --no-ascii-only: Drop all-ASCII strings in "strings". */
80 static bool exclude_ascii_only;
82 /* --utf8-only: Only print strings that have UTF-8 multibyte sequences in
83 * "strings". */
84 static bool include_utf8_only;
86 /* -f, --force: Keep output file even on error. */
87 static bool force;
89 /* --table-look: TableLook to replace table style for conversion. */
90 static struct pivot_table_look *table_look;
92 /* Number of warnings issued. */
93 static size_t n_warnings;
95 static void usage (void);
96 static void developer_usage (void);
97 static void parse_options (int argc, char **argv);
99 static struct output_item *
100 annotate_member_names (const struct output_item *in)
102 if (in->type == OUTPUT_ITEM_GROUP)
104 struct output_item *out = group_item_clone_empty (in);
105 for (size_t i = 0; i < in->group.n_children; i++)
107 const struct output_item *item = in->group.children[i];
108 const char *members[4];
109 size_t n = spv_info_get_members (item->spv_info, members,
110 sizeof members / sizeof *members);
111 if (n)
113 struct string s = DS_EMPTY_INITIALIZER;
114 ds_put_cstr (&s, members[0]);
115 for (size_t i = 1; i < n; i++)
116 ds_put_format (&s, " and %s", members[i]);
117 group_item_add_child (out, text_item_create_nocopy (
118 TEXT_ITEM_TITLE, ds_steal_cstr (&s),
119 xstrdup ("Member Names")));
122 group_item_add_child (out, output_item_ref (item));
124 return out;
126 else
127 return output_item_ref (in);
130 static void
131 print_item_directory (const struct output_item *item, int level)
133 for (int i = 0; i < level; i++)
134 printf (" ");
136 printf ("- %s", output_item_type_to_string (item->type));
138 const char *label = output_item_get_label (item);
139 if (label)
140 printf (" \"%s\"", label);
142 if (item->type == OUTPUT_ITEM_TABLE)
144 char *title = pivot_value_to_string (item->table->title, item->table);
145 if (!label || strcmp (title, label))
146 printf (" title \"%s\"", title);
147 free (title);
150 if (item->command_name)
151 printf (" command \"%s\"", item->command_name);
153 char *subtype = output_item_get_subtype (item);
154 if (subtype)
156 if (!label || strcmp (label, subtype))
157 printf (" subtype \"%s\"", subtype);
158 free (subtype);
161 if (!item->show)
162 printf (" (%s)", item->type == OUTPUT_ITEM_GROUP ? "collapsed" : "hidden");
164 if (show_member_names)
166 const char *members[4];
167 size_t n = spv_info_get_members (item->spv_info, members,
168 sizeof members / sizeof *members);
170 for (size_t i = 0; i < n; i++)
171 printf (" %s %s", i == 0 ? "in" : "and", members[i]);
173 putchar ('\n');
175 if (item->type == OUTPUT_ITEM_GROUP)
176 for (size_t i = 0; i < item->group.n_children; i++)
177 print_item_directory (item->group.children[i], level + 1);
180 static void
181 run_detect (int argc UNUSED, char **argv)
183 char *err = spv_detect (argv[1]);
184 if (err)
185 error (1, 0, "%s", err);
188 static struct output_item *
189 read_and_filter_spv (const char *name, struct page_setup **psp)
191 struct output_item *root;
192 char *err = spv_read (name, &root, psp);
193 if (err)
194 error (1, 0, "%s", err);
195 return output_select (root, criteria, n_criteria);
198 static void
199 run_directory (int argc UNUSED, char **argv)
201 struct output_item *root = read_and_filter_spv (argv[1], NULL);
202 for (size_t i = 0; i < root->group.n_children; i++)
203 print_item_directory (root->group.children[i], 0);
204 output_item_unref (root);
207 static void
208 set_table_look_recursively (struct output_item *item,
209 const struct pivot_table_look *look)
211 if (item->type == OUTPUT_ITEM_TABLE)
212 pivot_table_set_look (item->table, look);
213 else if (item->type == OUTPUT_ITEM_GROUP)
214 for (size_t i = 0; i < item->group.n_children; i++)
215 set_table_look_recursively (item->group.children[i], look);
218 static void
219 run_convert (int argc UNUSED, char **argv)
221 struct page_setup *ps;
222 struct output_item *root = read_and_filter_spv (argv[1], &ps);
223 if (table_look)
224 set_table_look_recursively (root, table_look);
225 if (show_member_names)
227 struct output_item *new_root = annotate_member_names (root);
228 output_item_unref (root);
229 root = new_root;
232 output_engine_push ();
233 output_set_filename (argv[1]);
234 string_map_replace (&output_options, "output-file", argv[2]);
235 struct output_driver *driver = output_driver_create (&output_options);
236 if (!driver)
237 exit (EXIT_FAILURE);
238 output_driver_register (driver);
240 if (ps)
242 output_set_page_setup (ps);
243 page_setup_destroy (ps);
245 output_item_submit_children (root);
247 output_engine_pop ();
248 fh_done ();
250 if (n_warnings && !force)
252 /* XXX There could be other files to unlink, e.g. the ascii driver can
253 produce additional files with the charts. */
254 unlink (argv[2]);
258 static const struct pivot_table *
259 get_first_table (const struct output_item *item)
261 if (item->type == OUTPUT_ITEM_TABLE)
262 return item->table;
263 else if (item->type == OUTPUT_ITEM_GROUP)
264 for (size_t i = 0; i < item->group.n_children; i++)
266 const struct pivot_table *table
267 = get_first_table (item->group.children[i]);
268 if (table)
269 return table;
272 return NULL;
275 static void
276 run_get_table_look (int argc UNUSED, char **argv)
278 struct pivot_table_look *look;
279 if (strcmp (argv[1], "-"))
281 struct output_item *root = read_and_filter_spv (argv[1], NULL);
282 const struct pivot_table *table = get_first_table (root);
283 if (!table)
284 error (1, 0, "%s: no tables found", argv[1]);
286 look = pivot_table_look_ref (pivot_table_get_look (table));
288 output_item_unref (root);
290 else
291 look = pivot_table_look_ref (pivot_table_look_builtin_default ());
293 char *err = spv_table_look_write (argv[2], look);
294 if (err)
295 error (1, 0, "%s", err);
297 pivot_table_look_unref (look);
300 static void
301 run_convert_table_look (int argc UNUSED, char **argv)
303 struct pivot_table_look *look;
304 char *err = spv_table_look_read (argv[1], &look);
305 if (err)
306 error (1, 0, "%s", err);
308 err = spv_table_look_write (argv[2], look);
309 if (err)
310 error (1, 0, "%s", err);
312 pivot_table_look_unref (look);
313 free (look);
316 static void
317 run_dump (int argc UNUSED, char **argv)
319 struct output_item *root = read_and_filter_spv (argv[1], NULL);
320 output_item_dump (root, 0);
321 output_item_unref (root);
324 static int
325 compare_borders (const void *a_, const void *b_)
327 const struct spvlb_border *const *ap = a_;
328 const struct spvlb_border *const *bp = b_;
329 uint32_t a = (*ap)->border_type;
330 uint32_t b = (*bp)->border_type;
332 return a < b ? -1 : a > b;
335 static int
336 compare_cells (const void *a_, const void *b_)
338 const struct spvlb_cell *const *ap = a_;
339 const struct spvlb_cell *const *bp = b_;
340 uint64_t a = (*ap)->index;
341 uint64_t b = (*bp)->index;
343 return a < b ? -1 : a > b;
346 static char * WARN_UNUSED_RESULT
347 dump_raw (struct zip_reader *zr, const char *member_name)
349 void *data;
350 size_t size;
351 char *error = zip_member_read_all (zr, member_name, &data, &size);
352 if (!error)
354 fwrite (data, size, 1, stdout);
355 free (data);
357 return error;
360 static void
361 dump_light_table (const struct output_item *item)
363 char *error;
364 if (raw)
365 error = dump_raw (item->spv_info->zip_reader,
366 item->spv_info->bin_member);
367 else
369 struct spvlb_table *table;
370 error = spv_read_light_table (item->spv_info->zip_reader,
371 item->spv_info->bin_member, &table);
372 if (!error)
374 if (sort)
376 qsort (table->borders->borders, table->borders->n_borders,
377 sizeof *table->borders->borders, compare_borders);
378 qsort (table->cells->cells, table->cells->n_cells,
379 sizeof *table->cells->cells, compare_cells);
381 spvlb_print_table (item->spv_info->bin_member, 0, table);
382 spvlb_free_table (table);
385 if (error)
387 msg (ME, "%s", error);
388 free (error);
392 static void
393 run_dump_light_table (int argc UNUSED, char **argv)
395 if (raw && isatty (STDOUT_FILENO))
396 error (1, 0, "not writing binary data to tty");
398 struct output_item *root = read_and_filter_spv (argv[1], NULL);
399 struct output_iterator iter;
400 OUTPUT_ITEM_FOR_EACH (&iter, root)
401 if (iter.cur->type == OUTPUT_ITEM_TABLE && !iter.cur->spv_info->xml_member)
402 dump_light_table (iter.cur);
403 output_item_unref (root);
406 static void
407 dump_legacy_data (const struct output_item *item)
409 char *error;
410 if (raw)
411 error = dump_raw (item->spv_info->zip_reader,
412 item->spv_info->bin_member);
413 else
415 struct spv_data data;
416 error = spv_read_legacy_data (item->spv_info->zip_reader,
417 item->spv_info->bin_member, &data);
418 if (!error)
420 printf ("%s:\n", item->spv_info->bin_member);
421 spv_data_dump (&data, stdout);
422 spv_data_uninit (&data);
423 printf ("\n");
427 if (error)
429 msg (ME, "%s", error);
430 free (error);
434 static void
435 run_dump_legacy_data (int argc UNUSED, char **argv)
437 if (raw && isatty (STDOUT_FILENO))
438 error (1, 0, "not writing binary data to tty");
440 struct output_item *root = read_and_filter_spv (argv[1], NULL);
441 struct output_iterator iter;
442 OUTPUT_ITEM_FOR_EACH (&iter, root)
443 if (iter.cur->type == OUTPUT_ITEM_TABLE
444 && iter.cur->spv_info->xml_member
445 && iter.cur->spv_info->bin_member)
446 dump_legacy_data (iter.cur);
447 output_item_unref (root);
450 /* This is really bogus.
452 XPath doesn't have any notion of a default XML namespace, but all of the
453 elements in the documents we're interested in have a namespace. Thus, we'd
454 need to require the XPath expressions to have a namespace on every single
455 element: vis:sourceVariable, vis:graph, and so on. That's a pain. So,
456 instead, we remove the default namespace from everyplace it occurs. XPath
457 does support the null namespace, so this allows sourceVariable, graph,
458 etc. to work.
460 See http://plasmasturm.org/log/259/ and
461 https://mail.gnome.org/archives/xml/2003-April/msg00144.html for more
462 information.*/
463 static void
464 remove_default_xml_namespace (xmlNode *node)
466 if (node->ns && !node->ns->prefix)
467 node->ns = NULL;
469 for (xmlNode *child = node->children; child; child = child->next)
470 remove_default_xml_namespace (child);
473 static void
474 register_ns (xmlXPathContext *ctx, const char *prefix, const char *uri)
476 xmlXPathRegisterNs (ctx, CHAR_CAST (xmlChar *, prefix),
477 CHAR_CAST (xmlChar *, uri));
480 static xmlXPathContext *
481 create_xpath_context (xmlDoc *doc)
483 xmlXPathContext *ctx = xmlXPathNewContext (doc);
484 register_ns (ctx, "vgr", "http://xml.spss.com/spss/viewer/viewer-graph");
485 register_ns (ctx, "vizml", "http://xml.spss.com/visualization");
486 register_ns (ctx, "vmd", "http://xml.spss.com/spss/viewer/viewer-model");
487 register_ns (ctx, "vps", "http://xml.spss.com/spss/viewer/viewer-pagesetup");
488 register_ns (ctx, "vst", "http://xml.spss.com/spss/viewer/viewer-style");
489 register_ns (ctx, "vtb", "http://xml.spss.com/spss/viewer/viewer-table");
490 register_ns (ctx, "vtl", "http://xml.spss.com/spss/viewer/table-looks");
491 register_ns (ctx, "vtt", "http://xml.spss.com/spss/viewer/viewer-treemodel");
492 register_ns (ctx, "vtx", "http://xml.spss.com/spss/viewer/viewer-text");
493 register_ns (ctx, "xsi", "http://www.w3.org/2001/XMLSchema-instance");
494 return ctx;
497 static void
498 dump_xml (int argc, char **argv, const char *member_name,
499 char *error_s, xmlDoc *doc)
501 if (!error_s)
503 if (argc == 2)
505 printf ("<!-- %s -->\n", member_name);
506 xmlElemDump (stdout, NULL, xmlDocGetRootElement (doc));
507 putchar ('\n');
509 else
511 bool any_results = false;
513 remove_default_xml_namespace (xmlDocGetRootElement (doc));
514 for (int i = 2; i < argc; i++)
516 xmlXPathContext *xpath_ctx = create_xpath_context (doc);
517 xmlXPathSetContextNode (xmlDocGetRootElement (doc),
518 xpath_ctx);
519 xmlXPathObject *xpath_obj = xmlXPathEvalExpression(
520 CHAR_CAST (xmlChar *, argv[i]), xpath_ctx);
521 if (!xpath_obj)
522 error (1, 0, _("%s: invalid XPath expression"), argv[i]);
524 const xmlNodeSet *nodes = xpath_obj->nodesetval;
525 if (nodes && nodes->nodeNr > 0)
527 if (!any_results)
529 printf ("<!-- %s -->\n", member_name);
530 any_results = true;
532 for (size_t j = 0; j < nodes->nodeNr; j++)
534 xmlElemDump (stdout, doc, nodes->nodeTab[j]);
535 putchar ('\n');
539 xmlXPathFreeObject (xpath_obj);
540 xmlXPathFreeContext (xpath_ctx);
542 if (any_results)
543 putchar ('\n');
545 xmlFreeDoc (doc);
547 else
549 printf ("<!-- %s -->\n", member_name);
550 msg (ME, "%s", error_s);
551 free (error_s);
555 static void
556 dump_legacy_table (int argc, char **argv, const struct output_item *item)
558 xmlDoc *doc;
559 char *error_s = spv_read_xml_member (item->spv_info->zip_reader,
560 item->spv_info->xml_member,
561 false, "visualization", &doc);
562 dump_xml (argc, argv, item->spv_info->xml_member, error_s, doc);
565 static void
566 run_dump_legacy_table (int argc, char **argv)
568 struct output_item *root = read_and_filter_spv (argv[1], NULL);
569 struct output_iterator iter;
570 OUTPUT_ITEM_FOR_EACH (&iter, root)
571 if (iter.cur->type == OUTPUT_ITEM_TABLE
572 && iter.cur->spv_info->xml_member)
573 dump_legacy_table (argc, argv, iter.cur);
574 output_item_unref (root);
577 static void
578 dump_structure (int argc, char **argv, const struct output_item *item)
580 xmlDoc *doc;
581 char *error_s = spv_read_xml_member (item->spv_info->zip_reader,
582 item->spv_info->structure_member,
583 true, "heading", &doc);
584 dump_xml (argc, argv, item->spv_info->structure_member, error_s, doc);
587 static void
588 run_dump_structure (int argc, char **argv)
590 struct output_item *root = read_and_filter_spv (argv[1], NULL);
592 const char *last_structure_member = NULL;
593 struct output_iterator iter;
594 OUTPUT_ITEM_FOR_EACH (&iter, root)
596 const struct output_item *item = iter.cur;
597 if (item->spv_info->structure_member
598 && (!last_structure_member
599 || strcmp (item->spv_info->structure_member,
600 last_structure_member)))
602 last_structure_member = item->spv_info->structure_member;
603 dump_structure (argc, argv, item);
606 output_item_unref (root);
609 static bool
610 is_any_legacy (const struct output_item *item)
612 if (item->type == OUTPUT_ITEM_TABLE)
613 return item->spv_info->xml_member != NULL;
614 else if (item->type == OUTPUT_ITEM_GROUP)
615 for (size_t i = 0; i < item->group.n_children; i++)
616 if (is_any_legacy (item->group.children[i]))
617 return true;
619 return false;
622 static void
623 run_is_legacy (int argc UNUSED, char **argv)
625 struct output_item *root = read_and_filter_spv (argv[1], NULL);
626 bool is_legacy = is_any_legacy (root);
627 output_item_unref (root);
629 exit (is_legacy ? EXIT_SUCCESS : EXIT_FAILURE);
632 static bool
633 is_all_ascii (const char *s)
635 for (; *s; s++)
636 if (!encoding_guess_is_ascii_text (*s))
637 return false;
639 return true;
642 static void
643 dump_strings (const char *encoding, struct string_array *strings)
645 string_array_sort (strings);
646 string_array_uniq (strings);
648 if (raw)
650 if (exclude_ascii_only || include_utf8_only)
652 size_t i = 0;
653 for (size_t j = 0; j < strings->n; j++)
655 char *s = strings->strings[j];
656 bool is_ascii = is_all_ascii (s);
657 bool is_utf8 = !u8_check (CHAR_CAST (uint8_t *, s), strlen (s));
658 if (!is_ascii && (!include_utf8_only || is_utf8))
659 strings->strings[i++] = s;
660 else
661 free (s);
663 strings->n = i;
665 for (size_t i = 0; i < strings->n; i++)
666 puts (strings->strings[i]);
668 else
670 size_t n_nonascii = 0;
671 size_t n_utf8 = 0;
672 for (size_t i = 0; i < strings->n; i++)
674 const char *s = strings->strings[i];
675 if (!is_all_ascii (s))
677 n_nonascii++;
678 if (!u8_check (CHAR_CAST (uint8_t *, s), strlen (s)))
679 n_utf8++;
682 printf ("%s: %zu unique strings, %zu non-ASCII, %zu UTF-8.\n",
683 encoding, strings->n, n_nonascii, n_utf8);
687 struct encoded_strings
689 char *encoding;
690 struct string_array strings;
693 struct encoded_strings_table
695 struct encoded_strings *es;
696 size_t n, allocated;
699 static void
700 collect_strings (const struct output_item *item,
701 struct encoded_strings_table *t)
703 char *error;
704 struct spvlb_table *table;
705 error = spv_read_light_table (item->spv_info->zip_reader,
706 item->spv_info->bin_member, &table);
707 if (error)
709 msg (ME, "%s", error);
710 free (error);
711 return;
714 const char *table_encoding = spvlb_table_get_encoding (table);
715 size_t j = 0;
716 for (j = 0; j < t->n; j++)
717 if (!strcmp (t->es[j].encoding, table_encoding))
718 break;
719 if (j >= t->n)
721 if (t->n >= t->allocated)
722 t->es = x2nrealloc (t->es, &t->allocated, sizeof *t->es);
723 t->es[t->n++] = (struct encoded_strings) {
724 .encoding = xstrdup (table_encoding),
725 .strings = STRING_ARRAY_INITIALIZER,
728 collect_spvlb_strings (table, &t->es[j].strings);
731 static void
732 run_strings (int argc UNUSED, char **argv)
734 struct output_item *root = read_and_filter_spv (argv[1], NULL);
736 struct encoded_strings_table t = { .es = NULL };
737 struct output_iterator iter;
738 OUTPUT_ITEM_FOR_EACH (&iter, root)
740 const struct output_item *item = iter.cur;
741 if (item->type == OUTPUT_ITEM_TABLE
742 && !item->spv_info->xml_member
743 && item->spv_info->bin_member)
744 collect_strings (item, &t);
747 for (size_t i = 0; i < t.n; i++)
749 dump_strings (t.es[i].encoding, &t.es[i].strings);
750 free (t.es[i].encoding);
751 string_array_destroy (&t.es[i].strings);
753 free (t.es);
755 output_item_unref (root);
758 struct command
760 const char *name;
761 int min_args, max_args;
762 void (*run) (int argc, char **argv);
765 static const struct command commands[] =
767 { "detect", 1, 1, run_detect },
768 { "dir", 1, 1, run_directory },
769 { "convert", 2, 2, run_convert },
770 { "get-table-look", 2, 2, run_get_table_look },
771 { "convert-table-look", 2, 2, run_convert_table_look },
773 /* Undocumented commands. */
774 { "dump", 1, 1, run_dump },
775 { "dump-light-table", 1, 1, run_dump_light_table },
776 { "dump-legacy-data", 1, 1, run_dump_legacy_data },
777 { "dump-legacy-table", 1, INT_MAX, run_dump_legacy_table },
778 { "dump-structure", 1, INT_MAX, run_dump_structure },
779 { "is-legacy", 1, 1, run_is_legacy },
780 { "strings", 1, 1, run_strings },
782 static const int n_commands = sizeof commands / sizeof *commands;
784 static const struct command *
785 find_command (const char *name)
787 for (size_t i = 0; i < n_commands; i++)
789 const struct command *c = &commands[i];
790 if (!strcmp (name, c->name))
791 return c;
793 return NULL;
796 static void
797 emit_msg (const struct msg *m, void *aux UNUSED)
799 if (m->severity == MSG_S_ERROR || m->severity == MSG_S_WARNING)
800 n_warnings++;
802 char *s = msg_to_string (m);
803 fprintf (stderr, "%s\n", s);
804 free (s);
808 main (int argc, char **argv)
810 set_program_name (argv[0]);
811 msg_set_handler (emit_msg, NULL);
812 settings_init ();
813 i18n_init ();
815 parse_options (argc, argv);
817 argc -= optind;
818 argv += optind;
820 if (argc < 1)
821 error (1, 0, _("missing command name (use --help for help)"));
823 const struct command *c = find_command (argv[0]);
824 if (!c)
825 error (1, 0, _("unknown command \"%s\" (use --help for help)"), argv[0]);
827 int n_args = argc - 1;
828 if (n_args < c->min_args || n_args > c->max_args)
830 if (c->min_args == c->max_args)
832 error (1, 0,
833 ngettext ("\"%s\" command takes exactly %d argument",
834 "\"%s\" command takes exactly %d arguments",
835 c->min_args), c->name, c->min_args);
837 else if (c->max_args == INT_MAX)
839 error (1, 0,
840 ngettext ("\"%s\" command requires at least %d argument",
841 "\"%s\" command requires at least %d arguments",
842 c->min_args), c->name, c->min_args);
844 else
846 error (1, 0,
847 _("\"%s\" command requires between %d and %d arguments"),
848 c->name, c->min_args, c->max_args);
852 c->run (argc, argv);
854 pivot_table_look_unref (table_look);
855 i18n_done ();
857 return n_warnings ? EXIT_FAILURE : EXIT_SUCCESS;
860 static struct output_criteria *
861 get_criteria (void)
863 if (!n_criteria || new_criteria)
865 new_criteria = false;
866 if (n_criteria >= allocated_criteria)
867 criteria = x2nrealloc (criteria, &allocated_criteria,
868 sizeof *criteria);
869 criteria[n_criteria++]
870 = (struct output_criteria) OUTPUT_CRITERIA_INITIALIZER;
873 return &criteria[n_criteria - 1];
876 static void
877 parse_select (char *arg)
879 bool invert = arg[0] == '^';
880 arg += invert;
882 unsigned classes = 0;
883 for (char *token = strtok (arg, ","); token; token = strtok (NULL, ","))
885 if (!strcmp (arg, "all"))
886 classes = OUTPUT_ALL_CLASSES;
887 else if (!strcmp (arg, "help"))
889 puts (_("The following object classes are supported:"));
890 for (int class = 0; class < OUTPUT_N_CLASSES; class++)
891 printf ("- %s\n", output_item_class_to_string (class));
892 exit (0);
894 else
896 int class = output_item_class_from_string (token);
897 if (class == OUTPUT_N_CLASSES)
898 error (1, 0, _("unknown object class \"%s\" (use --select=help "
899 "for help)"), arg);
900 classes |= 1u << class;
904 struct output_criteria *c = get_criteria ();
905 c->classes = invert ? classes ^ OUTPUT_ALL_CLASSES : classes;
908 static struct output_criteria_match *
909 get_criteria_match (const char **arg)
911 struct output_criteria *c = get_criteria ();
912 if ((*arg)[0] == '^')
914 (*arg)++;
915 return &c->exclude;
917 else
918 return &c->include;
921 static void
922 parse_commands (const char *arg)
924 struct output_criteria_match *cm = get_criteria_match (&arg);
925 string_array_parse (&cm->commands, ss_cstr (arg), ss_cstr (","));
928 static void
929 parse_subtypes (const char *arg)
931 struct output_criteria_match *cm = get_criteria_match (&arg);
932 string_array_parse (&cm->subtypes, ss_cstr (arg), ss_cstr (","));
935 static void
936 parse_labels (const char *arg)
938 struct output_criteria_match *cm = get_criteria_match (&arg);
939 string_array_parse (&cm->labels, ss_cstr (arg), ss_cstr (","));
942 static void
943 parse_instances (char *arg)
945 struct output_criteria *c = get_criteria ();
946 size_t allocated_instances = c->n_instances;
948 for (char *token = strtok (arg, ","); token; token = strtok (NULL, ","))
950 if (c->n_instances >= allocated_instances)
951 c->instances = x2nrealloc (c->instances, &allocated_instances,
952 sizeof *c->instances);
954 c->instances[c->n_instances++] = (!strcmp (token, "last") ? -1
955 : atoi (token));
959 static void
960 parse_nth_commands (char *arg)
962 struct output_criteria *c = get_criteria ();
963 size_t allocated_commands = c->n_commands;
965 for (char *token = strtok (arg, ","); token; token = strtok (NULL, ","))
967 if (c->n_commands >= allocated_commands)
968 c->commands = x2nrealloc (c->commands, &allocated_commands,
969 sizeof *c->commands);
971 c->commands[c->n_commands++] = atoi (token);
975 static void
976 parse_members (const char *arg)
978 struct output_criteria *cm = get_criteria ();
979 string_array_parse (&cm->members, ss_cstr (arg), ss_cstr (","));
982 static void
983 parse_table_look (const char *arg)
985 pivot_table_look_unref (table_look);
987 char *error_s = pivot_table_look_read (arg, &table_look);
988 if (error_s)
989 error (1, 0, "%s", error_s);
992 static void
993 parse_options (int argc, char *argv[])
995 for (;;)
997 enum
999 OPT_MEMBER_NAMES = UCHAR_MAX + 1,
1000 OPT_SHOW_HIDDEN,
1001 OPT_SELECT,
1002 OPT_COMMANDS,
1003 OPT_NTH_COMMANDS,
1004 OPT_SUBTYPES,
1005 OPT_LABELS,
1006 OPT_INSTANCES,
1007 OPT_MEMBERS,
1008 OPT_ERRORS,
1009 OPT_OR,
1010 OPT_SORT,
1011 OPT_RAW,
1012 OPT_NO_ASCII_ONLY,
1013 OPT_UTF8_ONLY,
1014 OPT_TABLE_LOOK,
1015 OPT_HELP_DEVELOPER,
1017 static const struct option long_options[] =
1019 /* Input selection options. */
1020 { "show-hidden", no_argument, NULL, OPT_SHOW_HIDDEN },
1021 { "select", required_argument, NULL, OPT_SELECT },
1022 { "commands", required_argument, NULL, OPT_COMMANDS },
1023 { "nth-commands", required_argument, NULL, OPT_NTH_COMMANDS },
1024 { "subtypes", required_argument, NULL, OPT_SUBTYPES },
1025 { "labels", required_argument, NULL, OPT_LABELS },
1026 { "instances", required_argument, NULL, OPT_INSTANCES },
1027 { "members", required_argument, NULL, OPT_MEMBERS },
1028 { "errors", no_argument, NULL, OPT_ERRORS },
1029 { "or", no_argument, NULL, OPT_OR },
1031 /* "dir" command options. */
1032 { "member-names", no_argument, NULL, OPT_MEMBER_NAMES },
1034 /* "convert" command options. */
1035 { "force", no_argument, NULL, 'f' },
1036 { "table-look", required_argument, NULL, OPT_TABLE_LOOK },
1038 /* "dump-light-table" command options. */
1039 { "sort", no_argument, NULL, OPT_SORT },
1040 { "raw", no_argument, NULL, OPT_RAW },
1042 /* "strings" command options. */
1043 { "no-ascii-only", no_argument, NULL, OPT_NO_ASCII_ONLY },
1044 { "utf8-only", no_argument, NULL, OPT_UTF8_ONLY },
1046 { "help", no_argument, NULL, 'h' },
1047 { "help-developer", no_argument, NULL, OPT_HELP_DEVELOPER },
1048 { "version", no_argument, NULL, 'v' },
1050 { NULL, 0, NULL, 0 },
1053 int c;
1055 c = getopt_long (argc, argv, "O:hvf", long_options, NULL);
1056 if (c == -1)
1057 break;
1059 switch (c)
1061 case 'O':
1062 output_driver_parse_option (optarg, &output_options);
1063 break;
1065 case OPT_MEMBER_NAMES:
1066 show_member_names = true;
1067 break;
1069 case OPT_SHOW_HIDDEN:
1070 get_criteria ()->include_hidden = true;
1071 break;
1073 case OPT_SELECT:
1074 parse_select (optarg);
1075 break;
1077 case OPT_COMMANDS:
1078 parse_commands (optarg);
1079 break;
1081 case OPT_NTH_COMMANDS:
1082 parse_nth_commands (optarg);
1083 break;
1085 case OPT_SUBTYPES:
1086 parse_subtypes (optarg);
1087 break;
1089 case OPT_LABELS:
1090 parse_labels (optarg);
1091 break;
1093 case OPT_INSTANCES:
1094 parse_instances (optarg);
1095 break;
1097 case OPT_MEMBERS:
1098 parse_members (optarg);
1099 break;
1101 case OPT_ERRORS:
1102 get_criteria ()->error = true;
1103 break;
1105 case OPT_OR:
1106 new_criteria = true;
1107 break;
1109 case OPT_SORT:
1110 sort = true;
1111 break;
1113 case OPT_RAW:
1114 raw = true;
1115 break;
1117 case OPT_TABLE_LOOK:
1118 parse_table_look (optarg);
1119 break;
1121 case OPT_NO_ASCII_ONLY:
1122 exclude_ascii_only = true;
1123 break;
1125 case OPT_UTF8_ONLY:
1126 include_utf8_only = true;
1127 break;
1129 case 'f':
1130 force = true;
1131 break;
1133 case 'v':
1134 version_etc (stdout, "pspp-output", PACKAGE_NAME, PACKAGE_VERSION,
1135 "Ben Pfaff", "John Darrington", NULL_SENTINEL);
1136 exit (EXIT_SUCCESS);
1138 case 'h':
1139 usage ();
1140 exit (EXIT_SUCCESS);
1142 case OPT_HELP_DEVELOPER:
1143 developer_usage ();
1144 exit (EXIT_SUCCESS);
1146 default:
1147 exit (EXIT_FAILURE);
1152 static void
1153 usage (void)
1155 struct string s = DS_EMPTY_INITIALIZER;
1156 struct string_set formats = STRING_SET_INITIALIZER(formats);
1157 output_get_supported_formats (&formats);
1158 const char *format;
1159 const struct string_set_node *node;
1160 STRING_SET_FOR_EACH (format, node, &formats)
1162 if (!ds_is_empty (&s))
1163 ds_put_byte (&s, ' ');
1164 ds_put_cstr (&s, format);
1166 string_set_destroy (&formats);
1168 printf ("\
1169 %s, a utility for working with SPSS viewer (.spv) files.\n\
1170 Usage: %s [OPTION]... COMMAND ARG...\n\
1172 The following commands are available:\n\
1173 detect FILE Detect whether FILE is an SPV file.\n\
1174 dir FILE List tables and other items in FILE.\n\
1175 convert SOURCE DEST Convert .spv SOURCE to DEST.\n\
1176 get-table-look SOURCE DEST Copies first selected TableLook into DEST\n\
1177 convert-table-look SOURCE DEST Copies .tlo or .stt SOURCE into DEST\n\
1179 Input selection options for \"dir\" and \"convert\":\n\
1180 --select=CLASS... include only some kinds of objects\n\
1181 --select=help print known object classes\n\
1182 --commands=COMMAND... include only specified COMMANDs\n\
1183 --nth-commands=N... include only the Nth instance of selected commands\n\
1184 --subtypes=SUBTYPE... include only specified SUBTYPEs of output\n\
1185 --labels=LABEL... include only output objects with the given LABELs\n\
1186 --instances=INSTANCE... include only the given object INSTANCEs\n\
1187 --show-hidden include hidden output objects\n\
1188 --or separate two sets of selection options\n\
1190 \"convert\" by default infers the destination's format from its extension.\n\
1191 The known extensions are: %s\n\
1192 The following options override \"convert\" behavior:\n\
1193 -O format=FORMAT set destination format to FORMAT\n\
1194 -O OPTION=VALUE set output option\n\
1195 -f, --force keep output file even given errors\n\
1196 --table-look=FILE override tables' style with TableLook from FILE\n\
1197 Other options:\n\
1198 --help display this help and exit\n\
1199 --help-developer display help for developer commands and exit\n\
1200 --version output version information and exit\n",
1201 program_name, program_name, ds_cstr (&s));
1202 ds_destroy (&s);
1205 static void
1206 developer_usage (void)
1208 printf ("\
1209 The following developer commands are available:\n\
1210 dump FILE Dump pivot table structure\n\
1211 [--raw | --sort] dump-light-table FILE Dump light tables\n\
1212 [--raw] dump-legacy-data FILE Dump legacy table data\n\
1213 dump-legacy-table FILE [XPATH]... Dump legacy table XML\n\
1214 dump-structure FILE [XPATH]... Dump structure XML\n\
1215 is-legacy FILE Exit with status 0 if any legacy table selected\n\
1216 strings FILE Dump analysis of strings\n\
1218 Additional input selection options:\n\
1219 --members=MEMBER... include only objects with these Zip member names\n\
1220 --errors include only objects that cannot be loaded\n\
1222 Additional options for \"dir\" command:\n\
1223 --member-names show Zip member names with objects\n\
1225 Options for the \"strings\" command:\n\
1226 --raw Dump all (unique) strings\n\
1227 --raw --no-ascii-only Dump all strings that contain non-ASCII characters\n\
1228 --raw --utf8-only Dump all non-ASCII strings that are valid UTF-8\n\
1230 Other options:\n\
1231 --raw print raw binary data instead of a parsed version\n\
1232 --sort sort borders and areas for shorter \"diff\" output\n");