spv-writer: Consistently use label from output_item.
[pspp] / utilities / pspp-output.c
index 8362ac57ff252ef78392c2c29fd33b809f52fbdd..5332f2f1a52e46e18d21fdf4da18030bf7ee72ad 100644 (file)
 
 #include <config.h>
 
 
 #include <config.h>
 
+#include <cairo.h>
 #include <getopt.h>
 #include <limits.h>
 #include <stdlib.h>
 #include <unistd.h>
 #include <getopt.h>
 #include <limits.h>
 #include <stdlib.h>
 #include <unistd.h>
+#include <unistr.h>
 
 #include "data/file-handle-def.h"
 #include "data/settings.h"
 
 #include "data/file-handle-def.h"
 #include "data/settings.h"
+#include "libpspp/encoding-guesser.h"
 #include "libpspp/i18n.h"
 #include "libpspp/message.h"
 #include "libpspp/string-map.h"
 #include "libpspp/string-set.h"
 #include "output/driver.h"
 #include "output/group-item.h"
 #include "libpspp/i18n.h"
 #include "libpspp/message.h"
 #include "libpspp/string-map.h"
 #include "libpspp/string-set.h"
 #include "output/driver.h"
 #include "output/group-item.h"
+#include "output/image-item.h"
 #include "output/page-setup-item.h"
 #include "output/pivot-table.h"
 #include "output/spv/light-binary-parser.h"
 #include "output/spv/spv-legacy-data.h"
 #include "output/page-setup-item.h"
 #include "output/pivot-table.h"
 #include "output/spv/light-binary-parser.h"
 #include "output/spv/spv-legacy-data.h"
+#include "output/spv/spv-light-decoder.h"
 #include "output/spv/spv-output.h"
 #include "output/spv/spv-select.h"
 #include "output/spv/spv-table-look.h"
 #include "output/spv/spv-output.h"
 #include "output/spv/spv-select.h"
 #include "output/spv/spv-table-look.h"
@@ -57,7 +62,7 @@
 static struct string_map output_options
     = STRING_MAP_INITIALIZER (output_options);
 
 static struct string_map output_options
     = STRING_MAP_INITIALIZER (output_options);
 
-/* --member-name: Include .zip member name in "dir" output. */
+/* --member-names: Include .zip member name in "dir" output. */
 static bool show_member_names;
 
 /* --show-hidden, --select, --commands, ...: Selection criteria. */
 static bool show_member_names;
 
 /* --show-hidden, --select, --commands, ...: Selection criteria. */
@@ -70,19 +75,28 @@ static bool new_criteria;
 /* --sort: Sort members under dump-light-table, to make comparisons easier. */
 static bool sort;
 
 /* --sort: Sort members under dump-light-table, to make comparisons easier. */
 static bool sort;
 
-/* --raw: Dump raw binary data in dump-light-table. */
+/* --raw: Dump raw binary data in "dump-light-table"; dump all strings in
+     "strings". */
 static bool raw;
 
 static bool raw;
 
+/* --no-ascii-only: Drop all-ASCII strings in "strings". */
+static bool exclude_ascii_only;
+
+/* --utf8-only: Only print strings that have UTF-8 multibyte sequences in
+ * "strings". */
+static bool include_utf8_only;
+
 /* -f, --force: Keep output file even on error. */
 static bool force;
 
 /* --table-look: TableLook to replace table style for conversion. */
 /* -f, --force: Keep output file even on error. */
 static bool force;
 
 /* --table-look: TableLook to replace table style for conversion. */
-static struct spv_table_look *table_look;
+static struct pivot_table_look *table_look;
 
 /* Number of warnings issued. */
 static size_t n_warnings;
 
 static void usage (void);
 
 /* Number of warnings issued. */
 static size_t n_warnings;
 
 static void usage (void);
+static void developer_usage (void);
 static void parse_options (int argc, char **argv);
 
 static void
 static void parse_options (int argc, char **argv);
 
 static void
@@ -92,10 +106,14 @@ dump_item (const struct spv_item *item)
     {
       const char *x = item->xml_member;
       const char *b = item->bin_member;
     {
       const char *x = item->xml_member;
       const char *b = item->bin_member;
+
+      /* The strings below are not marked for translation because they are only
+         useful to developers. */
       char *s = (x && b
       char *s = (x && b
-                 ? xasprintf (_("%s and %s:"), x, b)
+                 ? xasprintf ("%s and %s:", x, b)
                  : xasprintf ("%s:", x ? x : b));
                  : xasprintf ("%s:", x ? x : b));
-      text_item_submit (text_item_create_nocopy (TEXT_ITEM_TITLE, s));
+      text_item_submit (text_item_create_nocopy (TEXT_ITEM_TITLE, s,
+                                                 xstrdup ("Member Names")));
     }
 
   switch (spv_item_get_type (item))
     }
 
   switch (spv_item_get_type (item))
@@ -117,7 +135,9 @@ dump_item (const struct spv_item *item)
     case SPV_ITEM_MODEL:
       break;
 
     case SPV_ITEM_MODEL:
       break;
 
-    case SPV_ITEM_OBJECT:
+    case SPV_ITEM_IMAGE:
+      image_item_submit (image_item_create (cairo_surface_reference (
+                                              spv_item_get_image (item))));
       break;
 
     case SPV_ITEM_TREE:
       break;
 
     case SPV_ITEM_TREE:
@@ -144,9 +164,7 @@ print_item_directory (const struct spv_item *item)
   if (type == SPV_ITEM_TABLE)
     {
       const struct pivot_table *table = spv_item_get_table (item);
   if (type == SPV_ITEM_TABLE)
     {
       const struct pivot_table *table = spv_item_get_table (item);
-      char *title = pivot_value_to_string (table->title,
-                                           SETTINGS_VALUE_SHOW_DEFAULT,
-                                           SETTINGS_VALUE_SHOW_DEFAULT);
+      char *title = pivot_value_to_string (table->title, table);
       if (!label || strcmp (title, label))
         printf (" title \"%s\"", title);
       free (title);
       if (!label || strcmp (title, label))
         printf (" title \"%s\"", title);
       free (title);
@@ -162,14 +180,19 @@ print_item_directory (const struct spv_item *item)
 
   if (!spv_item_is_visible (item))
     printf (" (hidden)");
 
   if (!spv_item_is_visible (item))
     printf (" (hidden)");
-  if (show_member_names && (item->xml_member || item->bin_member))
+
+  if (show_member_names)
     {
     {
-      if (item->xml_member && item->bin_member)
-        printf (" in %s and %s", item->xml_member, item->bin_member);
-      else if (item->xml_member)
-        printf (" in %s", item->xml_member);
-      else if (item->bin_member)
-        printf (" in %s", item->bin_member);
+      const char *members[] = {
+        item->xml_member,
+        item->bin_member,
+        item->png_member,
+      };
+      size_t n = 0;
+
+      for (size_t i = 0; i < sizeof members / sizeof *members; i++)
+        if (members[i])
+          printf (" %s %s", n++ == 0 ? "in" : "and", members[i]);
     }
   putchar ('\n');
 }
     }
   putchar ('\n');
 }
@@ -268,7 +291,8 @@ dump_heading_transition (const struct spv_item *old,
     group_close_item_submit (group_close_item_create ());
   for (size_t i = common; i < new_path.n; i++)
     group_open_item_submit (group_open_item_create (
     group_close_item_submit (group_close_item_create ());
   for (size_t i = common; i < new_path.n; i++)
     group_open_item_submit (group_open_item_create (
-                              new_path.nodes[i]->command_id));
+                              new_path.nodes[i]->command_id,
+                              new_path.nodes[i]->label));
 
   free_path (&old_path);
   free_path (&new_path);
 
   free_path (&old_path);
   free_path (&new_path);
@@ -325,6 +349,69 @@ run_convert (int argc UNUSED, char **argv)
     }
 }
 
     }
 }
 
+static const struct pivot_table *
+get_first_table (const struct spv_reader *spv)
+{
+  struct spv_item **items;
+  size_t n_items;
+  spv_select (spv, criteria, n_criteria, &items, &n_items);
+
+  for (size_t i = 0; i < n_items; i++)
+    if (spv_item_is_table (items[i]))
+      {
+        free (items);
+        return spv_item_get_table (items[i]);
+      }
+
+  free (items);
+  return NULL;
+}
+
+static void
+run_get_table_look (int argc UNUSED, char **argv)
+{
+  struct pivot_table_look *look;
+  if (strcmp (argv[1], "-"))
+    {
+      struct spv_reader *spv;
+      char *err = spv_open (argv[1], &spv);
+      if (err)
+        error (1, 0, "%s", err);
+
+      const struct pivot_table *table = get_first_table (spv);
+      if (!table)
+        error (1, 0, "%s: no tables found", argv[1]);
+
+      look = pivot_table_look_ref (pivot_table_get_look (table));
+
+      spv_close (spv);
+    }
+  else
+    look = pivot_table_look_ref (pivot_table_look_builtin_default ());
+
+  char *err = spv_table_look_write (argv[2], look);
+  if (err)
+    error (1, 0, "%s", err);
+
+  pivot_table_look_unref (look);
+}
+
+static void
+run_convert_table_look (int argc UNUSED, char **argv)
+{
+  struct pivot_table_look *look;
+  char *err = spv_table_look_read (argv[1], &look);
+  if (err)
+    error (1, 0, "%s", err);
+
+  err = spv_table_look_write (argv[2], look);
+  if (err)
+    error (1, 0, "%s", err);
+
+  pivot_table_look_unref (look);
+  free (look);
+}
+
 static void
 run_dump (int argc UNUSED, char **argv)
 {
 static void
 run_dump (int argc UNUSED, char **argv)
 {
@@ -437,6 +524,9 @@ run_dump_legacy_data (int argc UNUSED, char **argv)
   if (err)
     error (1, 0, "%s", err);
 
   if (err)
     error (1, 0, "%s", err);
 
+  if (raw && isatty (STDOUT_FILENO))
+    error (1, 0, "not writing binary data to tty");
+
   struct spv_item **items;
   size_t n_items;
   spv_select (spv, criteria, n_criteria, &items, &n_items);
   struct spv_item **items;
   size_t n_items;
   spv_select (spv, criteria, n_criteria, &items, &n_items);
@@ -572,7 +662,7 @@ dump_xml (int argc, char **argv, const char *member_name,
               xmlXPathFreeContext (xpath_ctx);
             }
           if (any_results)
               xmlXPathFreeContext (xpath_ctx);
             }
           if (any_results)
-            putchar ('\n');;
+            putchar ('\n');
         }
       xmlFreeDoc (doc);
     }
         }
       xmlFreeDoc (doc);
     }
@@ -660,6 +750,125 @@ run_is_legacy (int argc UNUSED, char **argv)
   exit (is_legacy ? EXIT_SUCCESS : EXIT_FAILURE);
 }
 
   exit (is_legacy ? EXIT_SUCCESS : EXIT_FAILURE);
 }
 
+static bool
+is_all_ascii (const char *s)
+{
+  for (; *s; s++)
+    if (!encoding_guess_is_ascii_text (*s))
+      return false;
+
+  return true;
+}
+
+static void
+dump_strings (const char *encoding, struct string_array *strings)
+{
+  string_array_sort (strings);
+  string_array_uniq (strings);
+
+  if (raw)
+    {
+      if (exclude_ascii_only || include_utf8_only)
+        {
+          size_t i = 0;
+          for (size_t j = 0; j < strings->n; j++)
+            {
+              char *s = strings->strings[j];
+              bool is_ascii = is_all_ascii (s);
+              bool is_utf8 = !u8_check (CHAR_CAST (uint8_t *, s), strlen (s));
+              if (!is_ascii && (!include_utf8_only || is_utf8))
+                strings->strings[i++] = s;
+              else
+                free (s);
+            }
+          strings->n = i;
+        }
+      for (size_t i = 0; i < strings->n; i++)
+        puts (strings->strings[i]);
+    }
+  else
+    {
+      size_t n_nonascii = 0;
+      size_t n_utf8 = 0;
+      for (size_t i = 0; i < strings->n; i++)
+        {
+          const char *s = strings->strings[i];
+          if (!is_all_ascii (s))
+            {
+              n_nonascii++;
+              if (!u8_check (CHAR_CAST (uint8_t *, s), strlen (s)))
+                n_utf8++;
+            }
+        }
+      printf ("%s: %zu unique strings, %zu non-ASCII, %zu UTF-8.\n",
+              encoding, strings->n, n_nonascii, n_utf8);
+    }
+}
+
+static void
+run_strings (int argc UNUSED, char **argv)
+{
+  struct spv_reader *spv;
+  char *err = spv_open (argv[1], &spv);
+  if (err)
+    error (1, 0, "%s", err);
+
+  struct encoded_strings
+    {
+      char *encoding;
+      struct string_array strings;
+    }
+  *es = NULL;
+  size_t n_es = 0;
+  size_t allocated_es = 0;
+
+  struct spv_item **items;
+  size_t n_items;
+  spv_select (spv, criteria, n_criteria, &items, &n_items);
+  for (size_t i = 0; i < n_items; i++)
+    {
+      if (!spv_item_is_light_table (items[i]))
+        continue;
+
+      char *error;
+      struct spvlb_table *table;
+      error = spv_item_get_light_table (items[i], &table);
+      if (error)
+        {
+          msg (ME, "%s", error);
+          free (error);
+          continue;
+        }
+
+      const char *table_encoding = spvlb_table_get_encoding (table);
+      size_t j = 0;
+      for (j = 0; j < n_es; j++)
+        if (!strcmp (es[j].encoding, table_encoding))
+          break;
+      if (j >= n_es)
+        {
+          if (n_es >= allocated_es)
+            es = x2nrealloc (es, &allocated_es, sizeof *es);
+          es[n_es++] = (struct encoded_strings) {
+            .encoding = xstrdup (table_encoding),
+            .strings = STRING_ARRAY_INITIALIZER,
+          };
+        }
+      collect_spvlb_strings (table, &es[j].strings);
+    }
+  free (items);
+
+  for (size_t i = 0; i < n_es; i++)
+    {
+      dump_strings (es[i].encoding, &es[i].strings);
+      free (es[i].encoding);
+      string_array_destroy (&es[i].strings);
+    }
+  free (es);
+
+  spv_close (spv);
+}
+
 struct command
   {
     const char *name;
 struct command
   {
     const char *name;
@@ -672,6 +881,8 @@ static const struct command commands[] =
     { "detect", 1, 1, run_detect },
     { "dir", 1, 1, run_directory },
     { "convert", 2, 2, run_convert },
     { "detect", 1, 1, run_detect },
     { "dir", 1, 1, run_directory },
     { "convert", 2, 2, run_convert },
+    { "get-table-look", 2, 2, run_get_table_look },
+    { "convert-table-look", 2, 2, run_convert_table_look },
 
     /* Undocumented commands. */
     { "dump", 1, 1, run_dump },
 
     /* Undocumented commands. */
     { "dump", 1, 1, run_dump },
@@ -680,6 +891,7 @@ static const struct command commands[] =
     { "dump-legacy-table", 1, INT_MAX, run_dump_legacy_table },
     { "dump-structure", 1, INT_MAX, run_dump_structure },
     { "is-legacy", 1, 1, run_is_legacy },
     { "dump-legacy-table", 1, INT_MAX, run_dump_legacy_table },
     { "dump-structure", 1, INT_MAX, run_dump_structure },
     { "is-legacy", 1, 1, run_is_legacy },
+    { "strings", 1, 1, run_strings },
   };
 static const int n_commands = sizeof commands / sizeof *commands;
 
   };
 static const int n_commands = sizeof commands / sizeof *commands;
 
@@ -753,7 +965,7 @@ main (int argc, char **argv)
 
   c->run (argc, argv);
 
 
   c->run (argc, argv);
 
-  spv_table_look_destroy (table_look);
+  pivot_table_look_unref (table_look);
   i18n_done ();
 
   return n_warnings ? EXIT_FAILURE : EXIT_SUCCESS;
   i18n_done ();
 
   return n_warnings ? EXIT_FAILURE : EXIT_SUCCESS;
@@ -883,7 +1095,8 @@ parse_members (const char *arg)
 static void
 parse_table_look (const char *arg)
 {
 static void
 parse_table_look (const char *arg)
 {
-  spv_table_look_destroy (table_look);
+  pivot_table_look_unref (table_look);
+
   char *error_s = spv_table_look_read (arg, &table_look);
   if (error_s)
     error (1, 0, "%s", error_s);
   char *error_s = spv_table_look_read (arg, &table_look);
   if (error_s)
     error (1, 0, "%s", error_s);
@@ -909,7 +1122,10 @@ parse_options (int argc, char *argv[])
           OPT_OR,
           OPT_SORT,
           OPT_RAW,
           OPT_OR,
           OPT_SORT,
           OPT_RAW,
+          OPT_NO_ASCII_ONLY,
+          OPT_UTF8_ONLY,
           OPT_TABLE_LOOK,
           OPT_TABLE_LOOK,
+          OPT_HELP_DEVELOPER,
         };
       static const struct option long_options[] =
         {
         };
       static const struct option long_options[] =
         {
@@ -936,7 +1152,12 @@ parse_options (int argc, char *argv[])
           { "sort", no_argument, NULL, OPT_SORT },
           { "raw", no_argument, NULL, OPT_RAW },
 
           { "sort", no_argument, NULL, OPT_SORT },
           { "raw", no_argument, NULL, OPT_RAW },
 
+          /* "strings" command options. */
+          { "no-ascii-only", no_argument, NULL, OPT_NO_ASCII_ONLY },
+          { "utf8-only", no_argument, NULL, OPT_UTF8_ONLY },
+
           { "help", no_argument, NULL, 'h' },
           { "help", no_argument, NULL, 'h' },
+          { "help-developer", no_argument, NULL, OPT_HELP_DEVELOPER },
           { "version", no_argument, NULL, 'v' },
 
           { NULL, 0, NULL, 0 },
           { "version", no_argument, NULL, 'v' },
 
           { NULL, 0, NULL, 0 },
@@ -1010,6 +1231,14 @@ parse_options (int argc, char *argv[])
           parse_table_look (optarg);
           break;
 
           parse_table_look (optarg);
           break;
 
+        case OPT_NO_ASCII_ONLY:
+          exclude_ascii_only = true;
+          break;
+
+        case OPT_UTF8_ONLY:
+          include_utf8_only = true;
+          break;
+
         case 'f':
           force = true;
           break;
         case 'f':
           force = true;
           break;
@@ -1023,6 +1252,10 @@ parse_options (int argc, char *argv[])
           usage ();
           exit (EXIT_SUCCESS);
 
           usage ();
           exit (EXIT_SUCCESS);
 
+        case OPT_HELP_DEVELOPER:
+          developer_usage ();
+          exit (EXIT_SUCCESS);
+
         default:
           exit (EXIT_FAILURE);
         }
         default:
           exit (EXIT_FAILURE);
         }
@@ -1053,6 +1286,8 @@ The following commands are available:\n\
   detect FILE            Detect whether FILE is an SPV file.\n\
   dir FILE               List tables and other items in FILE.\n\
   convert SOURCE DEST    Convert .spv SOURCE to DEST.\n\
   detect FILE            Detect whether FILE is an SPV file.\n\
   dir FILE               List tables and other items in FILE.\n\
   convert SOURCE DEST    Convert .spv SOURCE to DEST.\n\
+  get-table-look SOURCE DEST  Copies first selected TableLook into DEST\n\
+  convert-table-look SOURCE DEST  Copies .tlo or .stt SOURCE into DEST\n\
 \n\
 Input selection options for \"dir\" and \"convert\":\n\
   --select=CLASS...   include only some kinds of objects\n\
 \n\
 Input selection options for \"dir\" and \"convert\":\n\
   --select=CLASS...   include only some kinds of objects\n\
@@ -1074,7 +1309,38 @@ The following options override \"convert\" behavior:\n\
   --table-look=FILE         override tables' style with TableLook from FILE\n\
 Other options:\n\
   --help              display this help and exit\n\
   --table-look=FILE         override tables' style with TableLook from FILE\n\
 Other options:\n\
   --help              display this help and exit\n\
+  --help-developer    display help for developer commands and exit\n\
   --version           output version information and exit\n",
           program_name, program_name, ds_cstr (&s));
   ds_destroy (&s);
 }
   --version           output version information and exit\n",
           program_name, program_name, ds_cstr (&s));
   ds_destroy (&s);
 }
+
+static void
+developer_usage (void)
+{
+  printf ("\
+The following developer commands are available:\n\
+  dump FILE              Dump pivot table structure\n\
+  [--raw | --sort] dump-light-table FILE  Dump light tables\n\
+  [--raw] dump-legacy-data FILE  Dump legacy table data\n\
+  dump-legacy-table FILE [XPATH]...  Dump legacy table XML\n\
+  dump-structure FILE [XPATH]...  Dump structure XML\n\
+  is-legacy FILE         Exit with status 0 if any legacy table selected\n\
+  strings FILE           Dump analysis of strings\n\
+\n\
+Additional input selection options:\n\
+  --members=MEMBER...    include only objects with these Zip member names\n\
+  --errors               include only objects that cannot be loaded\n\
+\n\
+Additional options for \"dir\" command:\n\
+  --member-names         show Zip member names with objects\n\
+\n\
+Options for the \"strings\" command:\n\
+  --raw                  Dump all (unique) strings\n\
+  --raw --no-ascii-only  Dump all strings that contain non-ASCII characters\n\
+  --raw --utf8-only      Dump all non-ASCII strings that are valid UTF-8\n\
+\n\
+Other options:\n\
+  --raw                  print raw binary data instead of a parsed version\n\
+  --sort                 sort borders and areas for shorter \"diff\" output\n");
+}