Add support for PNG images in .spv files.
[pspp] / utilities / pspp-output.c
index c57dda8e6dbd43e0ba75fbeb89c721b9467cdb09..56a46f001d876c835936ecc172041730b3006197 100644 (file)
@@ -1,4 +1,4 @@
-/* PSPP - a program for statistical analysis.
+ /* PSPP - a program for statistical analysis.
    Copyright (C) 2017, 2018 Free Software Foundation, Inc.
 
    This program is free software: you can redistribute it and/or modify
@@ -16,6 +16,9 @@
 
 #include <config.h>
 
+#ifdef HAVE_CAIRO
+#include <cairo.h>
+#endif
 #include <getopt.h>
 #include <limits.h>
 #include <stdlib.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/spv/spv-output.h"
 #include "output/spv/spv-select.h"
+#include "output/spv/spv-table-look.h"
 #include "output/spv/spv.h"
 #include "output/table-item.h"
 #include "output/text-item.h"
@@ -75,6 +80,9 @@ static bool raw;
 /* -f, --force: Keep output file even on error. */
 static bool force;
 
+/* --table-look: TableLook to replace table style for conversion. */
+static struct pivot_table_look *table_look;
+
 /* Number of warnings issued. */
 static size_t n_warnings;
 
@@ -88,10 +96,14 @@ dump_item (const struct spv_item *item)
     {
       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
-                 ? xasprintf (_("%s and %s:"), x, b)
+                 ? xasprintf ("%s and %s:", 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))
@@ -113,7 +125,11 @@ dump_item (const struct spv_item *item)
     case SPV_ITEM_MODEL:
       break;
 
-    case SPV_ITEM_OBJECT:
+    case SPV_ITEM_IMAGE:
+#ifdef HAVE_CAIRO
+      image_item_submit (image_item_create (cairo_surface_reference (
+                                              spv_item_get_image (item))));
+#endif
       break;
 
     case SPV_ITEM_TREE:
@@ -140,9 +156,7 @@ print_item_directory (const struct spv_item *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);
@@ -158,14 +172,19 @@ print_item_directory (const struct spv_item *item)
 
   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');
 }
@@ -264,7 +283,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 (
-                              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);
@@ -278,6 +298,9 @@ run_convert (int argc UNUSED, char **argv)
   if (err)
     error (1, 0, "%s", err);
 
+  if (table_look)
+    spv_item_set_table_look (spv_get_root (spv), table_look);
+
   output_engine_push ();
   output_set_filename (argv[1]);
   string_map_replace (&output_options, "output-file", argv[2]);
@@ -318,6 +341,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)
 {
@@ -665,6 +751,8 @@ static const struct command commands[] =
     { "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 },
@@ -723,18 +811,30 @@ main (int argc, char **argv)
   if (n_args < c->min_args || n_args > c->max_args)
     {
       if (c->min_args == c->max_args)
-        error (1, 0, _("\"%s\" command takes exactly %d argument%s"),
-               c->name, c->min_args, c->min_args ? "s" : "");
+        {
+          error (1, 0,
+                 ngettext ("\"%s\" command takes exactly %d argument",
+                           "\"%s\" command takes exactly %d arguments",
+                           c->min_args), c->name, c->min_args);
+        }
       else if (c->max_args == INT_MAX)
-        error (1, 0, _("\"%s\" command requires at least %d argument%s"),
-               c->name, c->min_args, c->min_args ? "s" : "");
+        {
+          error (1, 0,
+                 ngettext ("\"%s\" command requires at least %d argument",
+                           "\"%s\" command requires at least %d arguments",
+                           c->min_args), c->name, c->min_args);
+        }
       else
-        error (1, 0, _("\"%s\" command requires between %d and %d arguments"),
-               c->name, c->min_args, c->max_args);
+        {
+          error (1, 0,
+                 _("\"%s\" command requires between %d and %d arguments"),
+                 c->name, c->min_args, c->max_args);
+        }
     }
 
   c->run (argc, argv);
 
+  pivot_table_look_unref (table_look);
   i18n_done ();
 
   return n_warnings ? EXIT_FAILURE : EXIT_SUCCESS;
@@ -838,6 +938,22 @@ parse_instances (char *arg)
     }
 }
 
+static void
+parse_nth_commands (char *arg)
+{
+  struct spv_criteria *c = get_criteria ();
+  size_t allocated_commands = c->n_commands;
+
+  for (char *token = strtok (arg, ","); token; token = strtok (NULL, ","))
+    {
+      if (c->n_commands >= allocated_commands)
+        c->commands = x2nrealloc (c->commands, &allocated_commands,
+                                   sizeof *c->commands);
+
+      c->commands[c->n_commands++] = atoi (token);
+    }
+}
+
 static void
 parse_members (const char *arg)
 {
@@ -845,6 +961,16 @@ parse_members (const char *arg)
   string_array_parse (&cm->members, ss_cstr (arg), ss_cstr (","));
 }
 
+static void
+parse_table_look (const char *arg)
+{
+  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);
+}
+
 static void
 parse_options (int argc, char *argv[])
 {
@@ -856,6 +982,7 @@ parse_options (int argc, char *argv[])
           OPT_SHOW_HIDDEN,
           OPT_SELECT,
           OPT_COMMANDS,
+          OPT_NTH_COMMANDS,
           OPT_SUBTYPES,
           OPT_LABELS,
           OPT_INSTANCES,
@@ -864,6 +991,7 @@ parse_options (int argc, char *argv[])
           OPT_OR,
           OPT_SORT,
           OPT_RAW,
+          OPT_TABLE_LOOK,
         };
       static const struct option long_options[] =
         {
@@ -871,6 +999,7 @@ parse_options (int argc, char *argv[])
           { "show-hidden", no_argument, NULL, OPT_SHOW_HIDDEN },
           { "select", required_argument, NULL, OPT_SELECT },
           { "commands", required_argument, NULL, OPT_COMMANDS },
+          { "nth-commands", required_argument, NULL, OPT_NTH_COMMANDS },
           { "subtypes", required_argument, NULL, OPT_SUBTYPES },
           { "labels", required_argument, NULL, OPT_LABELS },
           { "instances", required_argument, NULL, OPT_INSTANCES },
@@ -883,6 +1012,7 @@ parse_options (int argc, char *argv[])
 
           /* "convert" command options. */
           { "force", no_argument, NULL, 'f' },
+          { "table-look", required_argument, NULL, OPT_TABLE_LOOK },
 
           /* "dump-light-table" command options. */
           { "sort", no_argument, NULL, OPT_SORT },
@@ -922,6 +1052,10 @@ parse_options (int argc, char *argv[])
           parse_commands (optarg);
           break;
 
+        case OPT_NTH_COMMANDS:
+          parse_nth_commands (optarg);
+          break;
+
         case OPT_SUBTYPES:
           parse_subtypes (optarg);
           break;
@@ -954,6 +1088,10 @@ parse_options (int argc, char *argv[])
           raw = true;
           break;
 
+        case OPT_TABLE_LOOK:
+          parse_table_look (optarg);
+          break;
+
         case 'f':
           force = true;
           break;
@@ -997,11 +1135,14 @@ 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\
+  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\
   --select=help       print known object classes\n\
   --commands=COMMAND...  include only specified COMMANDs\n\
+  --nth-commands=N...  include only the Nth instance of selected commands\n\
   --subtypes=SUBTYPE...  include only specified SUBTYPEs of output\n\
   --labels=LABEL...   include only output objects with the given LABELs\n\
   --instances=INSTANCE...  include only the given object INSTANCEs\n\
@@ -1014,6 +1155,7 @@ The following options override \"convert\" behavior:\n\
   -O format=FORMAT          set destination format to FORMAT\n\
   -O OPTION=VALUE           set output option\n\
   -f, --force               keep output file even given errors\n\
+  --table-look=FILE         override tables' style with TableLook from FILE\n\
 Other options:\n\
   --help              display this help and exit\n\
   --version           output version information and exit\n",